We've fixed diagonal movement together for now! :D (i.e. Player getting stuck)

Started by ShiverMeSideways, Mon 07/11/2011 20:48:24

Previous topic - Next topic

ShiverMeSideways

Hello again, technical forum and its awesome inhabitants. As per usual, I seem to having another scripting issue (actually, many more, but I'll try and fix those myself). And again, as per usual, it seems the devil is in the logic department.

So, in Zombie Attack, I've got objects which may or may not block the player movement. But, like in all good games, I want the player, if he/she is moving diagonally and hugging the object, to keep moving.

For example, if he/she's coming from the top-left, going towards the bottom-right, and there is an object to he/she's left, then the player should keep moving downwards. Likewise, if there is an object beneath the player, he/she should keep moving to the left. Currently, the player just gets stuck.

The code I have thus far is this:
Code: ags

    // m = the distance the player travels in pixels.
    int a;
    a=((Player1.room-1)*50)+1;
    while (a<((Player1.room-1)*50)+51)
    {
      if ((Obj[a].sprite!=0) && (Obj[a].solid))
      {
        if ((cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) && (cEgo.y>=Obj[a].y1) && (cEgo.y-9<=Obj[a].y2) && (Obj[a].sprite!=0) && (Obj[a].solid))
        {
          if (Player1.direction==left) cEgo.x=cEgo.x+m;
          if (Player1.direction==right) cEgo.x=cEgo.x-m;
          if (Player1.direction==up) cEgo.y=cEgo.y+m;
          if (Player1.direction==down) cEgo.y=cEgo.y-m;
          if (Player1.direction==downleft) 
          { 
            if ((cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) && (cEgo.y>=Obj[a].y1) && (cEgo.y-9<=Obj[a].y2)) cEgo.x=cEgo.x+m; 
            if ((cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) && (cEgo.y>=Obj[a].y1) && (cEgo.y-9<=Obj[a].y2)) cEgo.y=cEgo.y-m; 
          }
          if (Player1.direction==downright) 
          {
            if ((cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) && (cEgo.y>=Obj[a].y1) && (cEgo.y-9<=Obj[a].y2)) cEgo.x=cEgo.x-m; 
            if ((cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) && (cEgo.y>=Obj[a].y1) && (cEgo.y-9<=Obj[a].y2)) cEgo.y=cEgo.y-m; 
          }
          if (Player1.direction==upleft) 
          { 
            if ((cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) && (cEgo.y>=Obj[a].y1) && (cEgo.y-9<=Obj[a].y2)) cEgo.x=cEgo.x+m; 
            if ((cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) && (cEgo.y>=Obj[a].y1) && (cEgo.y-9<=Obj[a].y2)) cEgo.y=cEgo.y+m; 
          }
          if (Player1.direction==upright) 
          {
            if ((cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) && (cEgo.y>=Obj[a].y1) && (cEgo.y-9<=Obj[a].y2)) cEgo.x=cEgo.x-m; 
            if ((cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) && (cEgo.y>=Obj[a].y1) && (cEgo.y-9<=Obj[a].y2)) cEgo.y=cEgo.y+m; 
          }
        }
      }
      a=a+1;
    }


Now, this only works if the object is to the left or the right of the player, but if it's beneath or above the player, the player just gets stuck.

Thanks in advance for your help, and please let me know if I should be more clear, or if the code is un-decipherable :) .

DoorKnobHandle

It's generally a good idea to separate the axises of movement in these cases.

I tend to give what I call my entities (ie. all things in the game that can move, the player, maybe other players in hotseat-mode, ai-controlled allies, enemies, maybe projectiles) a MoveX and MoveY function. The code in these functions is very similar. They are called with a parameter 'float distance' and simply try to move the entity that distance - horizontally for MoveX, vertically for MoveY. But before they move the entity they check for a collision - but only in their 'dimension' (ie. along their axis/direction, MoveX is never concerned with anything height/y-related). Then if the up-key is pressed you move the player by calling MoveY(-1.4), down=MoveY(1.4), left=MoveX(-1.4), right=MoveX(1.5).

Don't have more time to explain right now, sorry for not giving actual code but follow this logic and you'll have it set up working nicely in no time! Ask if something remained unclear or doesn't work in your implementation!

And cheers with your projects, been watching all your dev diaries with much enjoyment!

Khris

Handling x and y movement separately is fine (at least using a 2-dimensional movement vector is), but I'm not sure if this'll work out with collision detection.

What about this case: o# with o representing the player and # an obstacle? Moving down and to the right should work fine with maybe some hugging, but the x movement check will report back that movement is blocked after a distance of say 0.3. So how should we deal with that?
Or consider this:
# o
   #
The player should be able to pass in between the objects, but both movement checks will report a collision, so the player will at least slow down or stutter when he doesn't have to.

When I tried to code this once using pixel-perfect walkable areas, I had to go through every surrounding pixel, using an angle from -90° to 90° relative to the intended movement direction. I finally managed to get the player to hug any kind of slope. If there's a faster way, I'm all ears :)

DoorKnobHandle

I'm not 100% sure I understand the problem you mention there, Khris, but if you add control points to all 4 corners of your entity and then, when moving down you check the left and right lower corner, if you move left you check the upper and lower left corner and so on. If either of the two control points you're checking reports a collision, you don't move (but jump to the reported collision wall). That logic works 100% without problems, I have made use of it for several projects before!

Khris

I guess it comes down to the movement's resolution vs. the entities' resolution.

Take this case:


If I'm not mistaken, your method will move the object along the red vector so the control point ends up at B, while proper diagonal movement should result in the control point ending up at A.

This is of course quite pedantic and usually the person playing the game won't notice the difference. When it comes to hugging though, it might be noticeable.

Just move the blue box up a bit. Proper movement should go at 45° until the obstacle is hit, hug it by going only down, then continue to move at 45°.
Your method won't make the box hug the obstacle but leave some space while moving past it.

One way to overcome this is to shorten both the x and the y movement as soon as one causes a collision, so the movement direction isn't altered. The downside is of course that movement will slow down. The only way in turn to fix that which I can see is keep moving the object until it has moved the proper distance, which means calculating how it's going to hug the obstacle.

DoorKnobHandle

I'm still not sure what the problem is, but I'm pretty tired and it's getting late... :D

My MoveX/Y function does collision detection by checking if the end position along the x or y axis is in an obstacle and if it is, it will move the entity right next to that obstacle. So if you move down and right and hit a vertical wall, at some point the MoveX function will report that both right control corners would be in the wall, so the distance to that wall is calculated and, instead of moving the entity into the wall it actually moves right up to it, hugging it. If the down and right keys are still held down, the MoveY function will keep on reporting that both lower control points are not in obstacles and the entity will keep on moving down, hugging the vertical wall.

You mention resolution and that's of course a valid problem. If you work with tiles for obstacles and your entity is smaller or equal in size to a tile then corner control points work fine. If it's larger you need to simply add control points. 1 tile or less in size means 2 points, 2 tiles or less means 3 points and so on.

Khris

I outlined a rare case in which your way gives an incorrect result, if only for one or two frames.

ShiverMeSideways:
Could you clarify some issues I've noticed in your code?

In the while loop you're checking whether the object is visible and solid (A), in the next block you're checking the player's coordinates versus the object's (B) && (A) again. You don't need to check (A) again, but that doesn't break the code.
Then in there, you're checking for the direction, and in the diagonal movement blocks, you're checking (B) again, which -again- we know is already true, otherwise we'd never get here.
(B) is in there a total of 9 times.

So basically as soon as A and B are true, the player is moved. No other condition is ever checked, regardless of the direction.

Which brings me to the details of diagonal movement: what you call 'downleft' ends up doing:
Code: ags
  cEgo.x=cEgo.x+m; 
  cEgo.y=cEgo.y-m;

Isn't this is up and right (unless your m is negative)?

Not sure why this works even in part; as far as I can see, if the direction is one of the four diagonals, the player is either moved diagonally or not at all.

As for the condition: (cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) ... similar for y
This looks like it should only be true if the player is actually already inside the object.
Could you give an example of what the coordinates for one object actually are in-game? How big are the sprites? like 10x10?

ShiverMeSideways

Ok, guys, firstly, thank you so very-very much, I think I've got it working now!

I've implemented dkh's idea and it seems to be working beautifully - although I still have to work on the collision detection, the player sometimes seems to fly over a monster! :D

(However, this is just a matter of tweaking, at least the movement works now!)

This is the code I'm using now:
Code: ags
function MoveX(int howmuch, int movewhat)
{
  int asd1;
  bool nein;
  if (movewhat==0)
  {
    nein=false;
    asd1=1;
    if (EnemiesPresent>0)
    {
      while (asd1<EnemiesPresent+1)
      {
        if ((cEgo.x+howmuch>=Enemy[asd1].x+1) && (cEgo.x+howmuch<=Enemy[asd1].x+Enemy[asd1].sizex-1) && (cEgo.y-8>=Enemy[asd1].y+1) && (cEgo.y-8<=Enemy[asd1].y+Enemy[asd1].sizey-1) && (!Enemy[asd1].dead))
        { nein=true; }
        asd1=asd1+1;
      }
    }
    asd1=((Player1.room-1)*50)+1;
    while (asd1<((Player1.room-1)*50)+51)
    {
      if ((cEgo.x+howmuch>=Obj[asd1].x1-2) && (cEgo.x+howmuch<=Obj[asd1].x2+2) && (cEgo.y-9>=Obj[asd1].y1-2) && (cEgo.y-9<=Obj[asd1].y2+2) && (Obj[asd1].solid))
      { nein=true; }
      asd1=asd1+1;
    }
    if (!nein) cEgo.x=cEgo.x+howmuch;
  }
}
function MoveY(int howmuch, int movewhat)
{
  int asd1;
  bool nein;
  if (movewhat==0)
  {
    nein=false;
    asd1=1;
    if (EnemiesPresent>0)
    {
      while (asd1<EnemiesPresent+1)
      {
        if ((cEgo.x>=Enemy[asd1].x+1) && (cEgo.x<=Enemy[asd1].x+Enemy[asd1].sizex-1) && (cEgo.y-8+howmuch>=Enemy[asd1].y+1) && (cEgo.y-8+howmuch<=Enemy[asd1].y+Enemy[asd1].sizey-1) && (!Enemy[asd1].dead))
        { nein=true; }
        asd1=asd1+1;
      }
    }
    asd1=((Player1.room-1)*50)+1;
    while (asd1<((Player1.room-1)*50)+51)
    {
      if ((cEgo.x>=Obj[asd1].x1-2) && (cEgo.x<=Obj[asd1].x2+2) && (cEgo.y-9+howmuch>=Obj[asd1].y1-2) && (cEgo.y-9+howmuch<=Obj[asd1].y2+2) && (Obj[asd1].solid))
      { nein=true; }
      asd1=asd1+1;
    }
    if (!nein) cEgo.y=cEgo.y+howmuch;
  }
}


Quote from: Khris on Tue 08/11/2011 00:53:24ShiverMeSideways:
Could you clarify some issues I've noticed in your code?

In the while loop you're checking whether the object is visible and solid (A), in the next block you're checking the player's coordinates versus the object's (B) && (A) again. You don't need to check (A) again, but that doesn't break the code.
Then in there, you're checking for the direction, and in the diagonal movement blocks, you're checking (B) again, which -again- we know is already true, otherwise we'd never get here.
(B) is in there a total of 9 times.

So basically as soon as A and B are true, the player is moved. No other condition is ever checked, regardless of the direction.

Yes, I never said I was good at coding, and I frequently do redundant and pointless things like this, thanks so much for pointing it out, but I've completely removed that and now it should be cleaner in the separate functions.

QuoteWhich brings me to the details of diagonal movement: what you call 'downleft' ends up doing:
Code: ags
  cEgo.x=cEgo.x+m; 
  cEgo.y=cEgo.y-m;

Isn't this is up and right (unless your m is negative)?

Not sure why this works even in part; as far as I can see, if the direction is one of the four diagonals, the player is either moved diagonally or not at all.

The part of the code which was before the fragment I put in basically stated that the m (for movement) variable was applied to cEgo.x and/or cEgo.y. My reasoning was that the object checking happens after that, and if there is something in the way, I just subtract the added m to x and/or y. Do not ask why, such mysterious forces cannot be easily explained.

QuoteAs for the condition: (cEgo.x+4>=Obj[a].x1) && (cEgo.x-4<=Obj[a].x2) ... similar for y
This looks like it should only be true if the player is actually already inside the object.
Could you give an example of what the coordinates for one object actually are in-game? How big are the sprites? like 10x10?

It depends on the object, some are bigger, some are smaller, thus the need for x1, y1, x2, y2 variables. And yeah, isn't that the point, if the x and y are inside the hitbox rectangle (or whatever it's called), then that should signal a collision, no?

Khris

Alright, it makes a lot more sense now. So you moved the player, then did the collision check and reverted if there was one.
I didn't realize that and thought that the code you posted does 1. collision check. 2. if clear, move.

Glad you got the code working, it's not really optimized though and doesn't move the player at all if moving all the way causes a collison (as opposed to moving it part of the way).

Maybe I'll whip something up, I'm pretty tired though.

SMF spam blocked by CleanTalk