A small problem with collision detection.

Started by Scavenger, Tue 16/09/2014 02:21:29

Previous topic - Next topic

Scavenger

I'm currently working on a simple platformer using Pixelformer as a base, and everything's working pretty well, with the exception of the collision detection. It's fine for big, meaty platforms, but when you fall at a shallow angle on a thin platform, the character falls straight through more often than not. This would be incredibly bad for a platformer (as having all big meaty platforms would make it less fun), so it needs to be fixed, but I have no idea what went wrong!

I enclose the project here: DOWNLOAD

The collision code:
Code: ags

void HandleCollisions()
{
  mPlayer.onGround = false;
  int x = FloatToInt(mPlayer.x, eRoundNearest);
  int y = FloatToInt(mPlayer.y, eRoundNearest);
  
   while((Mapper.Blocked(x + 2, y) || Mapper.Blocked(x - 2, y))) // Ground impact
   {
      //if (mPlayer.vely > terminalVel - 0.01 && MoveDir() == KeyDir() && IsKeyPressed(eKeyDownArrow)) mPlayer.inRoll = true;
      mPlayer.onGround = true;
      mPlayer.hasDblJumped = false;
      mPlayer.jumped = false;
      mPlayer.vely = 0.0;
      mPlayer.y = IntToFloat(y); 
      y --;
      
   }
   if (!mPlayer.ignoringdwcollisions)
   {
     while (mPlayer.vely >= 0.0 && ((Mapper.DownwardBlocked (x + 2, y) || Mapper.DownwardBlocked (x -2, y)) || Mapper.DownwardBlocked (x, y+1)))
     {
        mPlayer.onGround = true;
        mPlayer.hasDblJumped = false;
        mPlayer.jumped = false;
        mPlayer.vely = 0.0;
        mPlayer.y = IntToFloat(y); 
        y --;
     }
     Object *movingplatform = Object.GetAtScreenXY (x - GetViewportX (), y - GetViewportY ());
     while (movingplatform != null && mPlayer.vely >= 0.0 && (Mapper.IsObjectPlatform (movingplatform) || Mapper.IsObjectBlock (movingplatform) )) //Moving Platform Impact
     {
       
       mPlayer.onGround = true;
       mPlayer.hasDblJumped = false;
       mPlayer.jumped = false;
       mPlayer.vely = 0.0;
       mPlayer.y = IntToFloat(y); 
       mPlayer.movplat_x_offset = x - movingplatform.X;
       mPlayer.movplat_x_prev = movingplatform.X;
       mPlayer.onMovingPlatform = true;
       movingplatform = Object.GetAtScreenXY (x - GetViewportX (), y - GetViewportY ());
       y --;
     }
   }
   else
   {
     if (!Mapper.DownwardBlocked (x + 2, y) && !Mapper.DownwardBlocked (x -2, y) && !Mapper.DownwardBlocked (x, y+1)) 
     {
       mPlayer.ignoringdwcollisions = false;
     }
   }
     
     
   if (Mapper.Blocked(x, y - 24)) //Platform ceiling.
   {
     y ++;
     mPlayer.vely = 1.0;
     mPlayer.jumpCounter = 40;
     mPlayer.releasedJump = true;
   }
   
   Object *block = Object.GetAtScreenXY (x - GetViewportX (), y - GetViewportY () - 24);
   if (block != null &&  Mapper.IsObjectBlock (block)) //BLOCK object ceiling.
   {
     y ++;
     mPlayer.vely = 1.0;
     mPlayer.jumpCounter = 40;
     mPlayer.releasedJump = true;
     block = Object.GetAtScreenXY (x - GetViewportX (), y - GetViewportY ());
   }
  bool failedtomove = false;
   if (Mapper.Blocked(x + 5, y - 4) || Mapper.Blocked(x + 5, y - (20 / (mPlayer.ducking + 1))) || Mapper.Blocked(x + 5, y - 12)){
     
     if (!failedtomove) x --;
     else x++;
     if (x < -100) failedtomove = true;
     mPlayer.x = IntToFloat(x);
     mPlayer.velx = 0.0;
   }
   
   Object *block1 = Object.GetAtScreenXY (x - GetViewportX () + 5, y - GetViewportY () -4);
   Object *block2 = Object.GetAtScreenXY (x - GetViewportX () + 5, y - GetViewportY () -20);
   Object *block3 = Object.GetAtScreenXY (x - GetViewportX () + 5, y - GetViewportY () -12);
   
   if ((block1 != null && Mapper.IsObjectBlock (block1)) && (block2 != null && Mapper.IsObjectBlock (block2)) && (block3 != null && Mapper.IsObjectBlock (block3)) ){
     x --;
     mPlayer.x = IntToFloat(x);
     mPlayer.velx = 0.0;
     block1 = Object.GetAtScreenXY (x - GetViewportX () + 5, y - GetViewportY () -4);
     block2 = Object.GetAtScreenXY (x - GetViewportX () + 5, y - GetViewportY () -20);
     block3 = Object.GetAtScreenXY (x - GetViewportX () + 5, y - GetViewportY () -12);
   }
   
   failedtomove = false;
   if ((Mapper.Blocked(x - 5, y - 4) || Mapper.Blocked(x - 5, y - (20 / (mPlayer.ducking + 1))) || Mapper.Blocked(x - 5, y - 12)))
   {
     if (!failedtomove) x ++;
     else x--;
     if (x > Room.Width + 100) failedtomove = true;
     mPlayer.x = IntToFloat(x);
     mPlayer.velx = 0.0;
     
   }
   block1 = Object.GetAtScreenXY (x - GetViewportX () - 5, y - GetViewportY () -4);
   block2 = Object.GetAtScreenXY (x - GetViewportX () - 5, y - GetViewportY () -20);
   block3 = Object.GetAtScreenXY (x - GetViewportX () - 5, y - GetViewportY () -12);
   
   while ((block1 != null && Mapper.IsObjectBlock (block1)) && (block2 != null && Mapper.IsObjectBlock (block2)) && (block3 != null && Mapper.IsObjectBlock (block3)) ){
     x ++;
     mPlayer.x = IntToFloat(x);
     mPlayer.velx = 0.0;
     block1 = Object.GetAtScreenXY (x - GetViewportX () - 5, y - GetViewportY () -4);
   block2 = Object.GetAtScreenXY (x - GetViewportX () - 5, y - GetViewportY () -20);
   block3 = Object.GetAtScreenXY (x - GetViewportX () - 5, y - GetViewportY () -12);
   }
  mPlayer.state = eFalling;
  if (Mapper.Blocked(x - 6, y - 4) && Mapper.Blocked(x - 6, y - 22) && Mapper.IsAreaGrippable (x - 6, y - 22) && mPlayer.direction == eLeft){ mPlayer.state = eOnWall; mPlayer.hasDblJumped = false;}
if (Mapper.Blocked(x + 6, y - 4) && Mapper.Blocked(x + 6, y - 22) && Mapper.IsAreaGrippable (x + 6, y - 22) && mPlayer.direction == eRight){ mPlayer.state = eOnWall; mPlayer.hasDblJumped = false;
} 
}


The Mapper class literally just polls the room's regions for what counts as blocking, and looks for objects named PLATFORM or BLOCK. I've tried a couple of things, like moving the player's collision area, but to no avail. Can any of you spot anything that I'm missing?

Thank you in advance.

Khris

I'm just guessing here, but the simplest explanation is that the falling character moves a certain amount of pixels down per frame near the end of a jump, and if this distance if bigger than the platform's height and the character happens to fall just right, they'll fall through.

Skimming the code, I don't see much lines that add something to the y coordinates, which you would need for a thorough collision check with ground.

Calin Leafshade

Ahhhh that pixelformer code is rubbbbbiiisssshhhh. I really need to make a new version.

But yea, what you need to do is broadly called Euler Integration. Which is basically addition.

Instead of adding all the vy at once you step it by a reasonable degree. So if you're falling at a rate of 5 px per frame you step it one at at a time and calculate the collisions for each step.

Pixelformer used a bitmask (region) for the collision map which is about the most complicated form of collisions to handle but broadly it goes like this:

1 - Integrate acceleration and velocity to compute the desired difference in position (how far you move that frame)
2 - Step each axis separately, a pixel at a time, starting with the one with the largest absolute difference.
3 - For the horizontal movement, offset the player hitbox by 3 pixels to the top, so he can climb slopes.
4 - Scan ahead, by checking against all valid obstacles and the bitmask itself, to determine how many pixels it is able to move before hitting an obstacle. Move to this new position.
5 - If this was horizontal movement, move as many pixels up as necessary (which should be up to 3) to make up for slope.
6 - If, at the end of the movement, any pixel of the character is overlaping with any obstacle, undo the movement on this axis.
7 - Regardless of result of last condition, proceed to do the same for the other axis.

Hope that helps.

Scavenger

I added a step-through collision step, but I'm not sure I'm doing it right:

Code: ags

function StepThroughCollision ()
{
  if (mPlayer.velx != 0.0)
  {
    float xprog = 0.0;
    bool done = true;
    if (mPlayer.velx < 1.0 || mPlayer.velx > -1.0)
      {
        mPlayer.x = ClampFloat(mPlayer.x + mPlayer.velx, 0.0, IntToFloat(Room.Width));
        HandleCollisions ();
        done = true;
      }
    else
      {
        if (mPlayer.velx >= 1.0)
        {
          while (xprog < mPlayer.velx || done == false)
            {
                if (mPlayer.velx - xprog > 1.0) 
                {
                  xprog += 1.0;
                  mPlayer.x = ClampFloat(mPlayer.x + 1.0, 0.0, IntToFloat(Room.Width));
                  HandleCollisions ();
                  if (mPlayer.velx == 0.0) done = true;
                }
                else
                {
                  mPlayer.x = ClampFloat(mPlayer.x + (mPlayer.velx - xprog), 0.0, IntToFloat(Room.Width));
                  HandleCollisions ();
                  if (mPlayer.velx == 0.0) done = true;
                }
            }
         }
        else if (mPlayer.velx <= -1.0)
        {
          while (xprog > mPlayer.velx || done == false)
            {
                if (mPlayer.velx - xprog < -1.0) 
                {
                  xprog -= 1.0;
                  mPlayer.x = ClampFloat(mPlayer.x - 1.0, 0.0, IntToFloat(Room.Width));
                  HandleCollisions ();
                  if (mPlayer.velx == 0.0) done = true;
                }
                else
                {
                  mPlayer.x = ClampFloat(mPlayer.x + (mPlayer.velx - xprog), 0.0, IntToFloat(Room.Width));
                  HandleCollisions ();
                  if (mPlayer.velx == 0.0) done = true;
                }
            }
        }
      }
  }
  if (mPlayer.vely != 0.0)
  {
    float yprog = 0.0;
    bool done = true;
    if (mPlayer.vely < 1.0 || mPlayer.vely > -1.0)
      {
        mPlayer.y = ClampFloat(mPlayer.y + mPlayer.vely, 0.0, IntToFloat(Room.Width));
        HandleCollisions ();
        done = true;
      }
    else
      {
        if (mPlayer.vely >= 1.0)
        {
          while (yprog < mPlayer.vely || done == false)
            {
                if (mPlayer.vely - yprog > 1.0) 
                {
                  yprog += 1.0;
                  mPlayer.y = ClampFloat(mPlayer.y + 1.0, 0.0, IntToFloat(Room.Width));
                  HandleCollisions ();
                  if (mPlayer.vely == 0.0) done = true;
                }
                else
                {
                  mPlayer.y = ClampFloat(mPlayer.y + (mPlayer.vely -yprog), 0.0, IntToFloat(Room.Width));
                  HandleCollisions ();
                  if (mPlayer.vely == 0.0) done = true;
                }
            }
         }
        else if (mPlayer.vely <= -1.0)
        {
          while (yprog > mPlayer.vely || done == false)
            {
                if (mPlayer.vely - yprog < -1.0) 
                {
                  yprog -= 1.0;
                  mPlayer.y = ClampFloat(mPlayer.y - 1.0, 0.0, IntToFloat(Room.Width));
                  HandleCollisions ();
                  if (mPlayer.vely == 0.0) done = true;
                }
                else
                {
                  mPlayer.y = ClampFloat(mPlayer.y + (mPlayer.vely - yprog), 0.0, IntToFloat(Room.Width));
                  HandleCollisions ();
                  if (mPlayer.vely == 0.0) done = true;
                }
            }
        }
      }
  }
  
  //mPlayer.x = ClampFloat(mPlayer.x + mPlayer.velx, 0.0, IntToFloat(Room.Width));
  //mPlayer.y = ClampFloat(mPlayer.y + mPlayer.vely, 0.0, IntToFloat (Room.Height));
}


I mean, it works, sort of, but now moving platforms are broken and jumping while running doesn't work, and the character still falls through thin platforms.. I also don't think I'm quite understanding this right. :<

Calin Leafshade

very quick observation that I dont know is the cause but your conditionals are wrong.

Take this line:

Code: ags

while (xprog < mPlayer.velx || done == false)


That should be a boolean AND not OR.

If you set done to true and xprog is still less than mPlayer.velx then the statement still evaluates as true so setting done to true does nothing.

If that's just a performance optimisation then it wont matter but I thought I'd mention it since I noticed it immediately.


Calin Leafshade

Sorry for the bump.

I wrote an improved algorithm for this in AGS. It's in AGS-Lua so it might not be of use to you but i thought i'd post it anyway.

source: https://dl.dropboxusercontent.com/u/27247158/pixelformer2src.zip
compiled: https://dl.dropboxusercontent.com/u/27247158/platformer.zip

SMF spam blocked by CleanTalk