Collisions on y-axis in a side-scroller - debugging!

EDIT: Please, if you’ve been here without being able to solve this, or don’t want to because it’s too long or my code is horrible to look at, please say so! Try to at least leave something I can use, even if you haven’t found the philosophers stone 8)

Hey guys.

I’m writing a sidescroller with JumpingJack from KGPJ as reference.
I’m stuck with two types of collisions:

  1. Jumping, and hitting your head against something.
  2. Falling, and stopping when hitting ground below you. (this one works, but I think
    the way I’m doing it is wrong - you’ll see what I mean).

I’ll just go over the structure of this, so you know enough to consider the problem.

Think of Mario. My world is made of bricks in the same way as Mario. You walk around on
these. The blocks are stored in an ArrayList[column] array.
In each of these ArrayLists are the blocks found in that columns, but in no order.

A block has it’s own class, and instance where it’s x-World-coord (I’ll explain this!),
and it’s y-coord (that is just the y-coord on screen (the coordinate system used
when rendering stuff)), because the world will only scroll with the x-axis.

The world has it’s own set of pixel-coordinates, but this is not important right now as
the world doesn’t scroll so they are equal to the on-screen-coordinates.

Here’s the problem:
When falling, the sprite has an increasing speed, so it might move with more than 1 pixel per update.
I need to do a check, if the sprite will land inside a block in the next planned move.
If it will, I need to calculate how long it WILL travel, so it will land on it’s feet on top of the block.
The same basicly applies to jumping.

I got a working method for checking if a coordinate is inside a block.

Here’s the solution provided in KGPJ for JumpingJack:


public int checkBrickTop(int xWorld, int yWorld, int step)
    /* The sprite is moving downwards. It checks its next position
    (xWorld, yWorld) to see if it will enter a brick from above.

    If it does, then its step value is reduced to smallStep
    so it will only drop enough to touch the top of the brick.
     */
    {
        if (insideBrick(xWorld, yWorld)) {
            int yMapWorld = yWorld - (pHeight - height);
            int mapY = (int) (yMapWorld / Constants.brickHeight);  // map y- index
            int topOffset = yMapWorld - (mapY * Constants.brickHeight);
            int smallStep = step - topOffset;
            // System.out.println("top smallStep: " + smallStep);
            return smallStep;
        }
        return step;   // no change
    }  // end of checkBrickTop()

I changed it a little bit to match my own game, but the logic is the same.
This works as intended (falling, and the method above).

Now, when jumping and moving upwards I should be able to use almost the same method right?
This is the one rewritten from JumpingJack, to match my game just as the above method.


public int checkBrickBase(int xWorld, int yWorld, int step)
    /* The sprite is moving upwards. It checks its next position
    (xWorld, yWorld) to see if it will enter a brick from below.

    If it does, then its step value is reduced to smallStep
    so it will only rise to touch the base of the brick.*/
    {
        if (insideBrick(xWorld, yWorld)) {
            int yMapWorld = yWorld - (pHeight - height);
            int mapY = (int) (yMapWorld / Constants.brickHeight);  // map y- index
            int topOffset = yMapWorld - (mapY * Constants.brickHeight);
            int smallStep = step - (Constants.brickHeight - topOffset);
            // System.out.println("base smallStep: " + smallStep);*/
            return smallStep;//smallStep;
        }
        return step;   // no change
    }  // end of checkBrickBase()

This one however does not work. Depending on how, and when I’m calling this I get different results

  • all wrong (not stopping the jump when hitting as it should).
    This makes me think that either both these methods are wrong, or my updating of the jumping.

Here is how I use the methods:


public void update(int delta) {
        if (isMovingRight) {
            xWorld += moveSize;
            locX += moveSize;
        } else if (isMovingLeft) {
            xWorld -= moveSize;
            locX -= moveSize;
        }
        if (!isRising) {
            if (!bricksMan.insideBrick(xWorld + (getWidth() / 2),
                    yWorld + getHeight()))
                /* If this happens then there is not ground under Karl */ {
                // increase falling speed
                yVelocity += 0.5;
                double fallDistance = bricksMan.checkBrickTop(
                        (int) xWorld + (getWidth() / 2),
                        (int) yWorld + (getHeight() + (int) yVelocity), (int) yVelocity);
                yWorld += fallDistance;
                locY += fallDistance;
            } else {
                /* ground has been hit */
                yVelocity = 0;
            }
        } else if (isRising)// is rising
        {
            if (yVelocity > -10) // is moving towards air
            {
                if (!bricksMan.insideBrick(xWorld + (getWidth() / 2), yWorld)) {
                    /* There is air above Karl */
                    yVelocity -= -0.5;
                    double riseDistance = bricksMan.checkBrickBase(
                            (int) xWorld + (getWidth() / 2),
                            (int) yWorld + (int) yVelocity, (int) yVelocity);
                    System.out.println("riseDistance and yVelocity: " +riseDistance+" "+yVelocity);
                    yWorld += riseDistance;
                    locY += riseDistance;
                } else {
                    yVelocity = 0; // stop getting any higher!!
                    isRising = false;
                }
            }
            if (yVelocity >= 0.0) // nothing was hit, but we ran out of velocity
            {
                isRising = false;
                yVelocity = 0;
            }

        }
    }

NOTES:

  1. locY and the yWorld-fields are the same right now, since the world is not scrolling!
  2. Karl is the player.
  3. yVelocity is a double.
  4. collisions on the x-axis is dealt with before calling update().
  5. when jumping yVelocity is set to -9.5, and isRising it set to true.

I tried to implement the falling, and jumping part of the collision detection in the same way,
but it appears I haven’t as only the falling works right.
Can you spot the problem with the code?

I’ll happily supply any more info about the logic if anything it unclear to you :slight_smile:

TL;DR VERSION:
Please read the text :slight_smile:

Why are you checking the yVelocity when moving up and not down? I don’t think you need to do that. Just check if Karl hit his head.
The line yVelocity -= -0.5; looks confusing because you have a double negative. yVelocity += 0.5; // slow down upward speed.

That’s because his next position on the y-axis is currentY + yVelocity. yVelocity is the number of pixels he will move on the y-axis.

So, when he jumps does he “go through” blocks above him? Are you checking his current location or his new location (currentY + yVelocity) for the collision? You should be checking the new location.

The location after the move.


} else if (isRising)// is rising
        {
            if (yVelocity > -10) // is moving towards air
            {
                if (!bricksMan.insideBrick(xWorld + (getWidth() / 2), yWorld)) {
                    /* There is air above Karl */
                    yVelocity -= -0.5;
                    double riseDistance = bricksMan.checkBrickBase(
                            (int) xWorld + (getWidth() / 2),
                            (int) yWorld + (int) yVelocity, (int) yVelocity);
                    System.out.println("riseDistance and yVelocity: " +riseDistance+" "+yVelocity);
                    yWorld += riseDistance;
                    locY += riseDistance;
                } else {
                    yVelocity = 0; // stop getting any higher!!
                    isRising = false;
                }
            }
            if (yVelocity >= 0.0) // nothing was hit, but we ran out of velocity
            {
                isRising = false;
                yVelocity = 0;
            }

See the double riseDistance :slight_smile:

Let me see if I follow your code right:
In checkBrickBase(). If you pass in someX, 95, -9. Ignoring x coordinates.
The location is in brick at Y 90 to 100. 10 Brick height, you want to return -4 or smallStep = yWorld + step - topOffset.
Then your Y in the update method would start out as 104 and would end up at 100.

It’s supposed to take the new position as well as the amount risen as parameters. Then it’ll return the amount Karl SHOULD rise so he doesn’t hit a brick with his head. So, if the next move will be inside a brick it returns small-step so KArl only moves up to the brick and no further.

It looks to me like the step you’re passing into checkBrickBase() is negative, but the function is expecting the value to be positive (and is trying to return a positive value in response).

Try this instead:

double riseDistance = -bricksMan.checkBrickBase(
                            (int) xWorld + (getWidth() / 2),
                            (int) yWorld + (int) yVelocity, (int) -yVelocity);

(Note the two extra minus signs.)

Other comments:

  • It’s possible you could run into problems mixing integers and floating-point numbers in this sort of game. I’d suggest keeping everything as integers as far as possible (and fixing the frame rate of the game loop). Otherwise you have to be very careful when casting doubles to ints. For example, iyWorld+(int)yVelocity[/i] will not always be the same as i(yWorld+yVelocity)[/i]. In the worst case, these issues could lead to you character sometimes getting stuck on the edges of blocks.
  • Don’t forget to add a maximum speed when falling. You don’t want your character to fall so fast that he/she skips a block.
  • Personally I’d get rid of the isRising variable, and use the sign of yVelocity to determine whether the character is going up or down. But I would have an onGround variable to indicate when the character is standing on a block. (You can’t tell from yVelocity alone whether the character is airborne – a zero value means either standing on the ground or at the top of a jump.) But that’s just personal taste.
  • Yes, you’re original post was far too long. :stuck_out_tongue: I wouldn’t have bothered reading it if there was something better on the telly (it’s all some stupid wedding thing).

Simon

:point: :point: :point: :point:

[quote=“dishmoth,post:8,topic:36599”]
It looks to me like the step you’re passing into checkBrickBase() is negative, but the function is expecting the value to be positive (and is trying to return a positive value in response).

Try this instead:

double riseDistance = -bricksMan.checkBrickBase(
                            (int) xWorld + (getWidth() / 2),
                            (int) yWorld + (int) yVelocity, (int) -yVelocity);

(Note the two extra minus signs.)

Other comments:

  • It’s possible you could run into problems mixing integers and floating-point numbers in this sort of game. I’d suggest keeping everything as integers as far as possible (and fixing the frame rate of the game loop). Otherwise you have to be very careful when casting doubles to ints. For example, iyWorld+(int)yVelocity[/i] will not always be the same as i(yWorld+yVelocity)[/i]. In the worst case, these issues could lead to you character sometimes getting stuck on the edges of blocks.
  • Don’t forget to add a maximum speed when falling. You don’t want your character to fall so fast that he/she skips a block.
  • Personally I’d get rid of the isRising variable, and use the sign of yVelocity to determine whether the character is going up or down. But I would have an onGround variable to indicate when the character is standing on a block. (You can’t tell from yVelocity alone whether the character is airborne – a zero value means either standing on the ground or at the top of a jump.) But that’s just personal taste.
  • Yes, you’re original post was far too long. :stuck_out_tongue: I wouldn’t have bothered reading it if there was something better on the telly (it’s all some stupid wedding thing).

That worked like a charm! :open_mouth: One sign…

I wanted to give you the feeling of knowing whats going on here, because the problem could’ve been a hundred places :stuck_out_tongue:
If I only used integers, how do I add velocity? Right now I just increase or decrease by 0.5 because that value is a double… How do I do that with integers? I don’t wan’t my dear Karl to increase the speed double as fast as now :smiley:

Thank you a lot for reading it all and coming up with nice post

It’s a tricky question. I don’t know how other people would handle it, but I’d suggest one of two approaches.

  1. Don’t worry about it too much. ;D Carry on using doubles, but keep in mind that you’ve got to be careful when casting to integers. For example, the following code

if ( !insideBrick((int)x, (int)y+(int)yVelocity) )  y += yVelocity;
assert( !insideBrick((int)x, (int)y) );

could fail the assertion (Karl inadvertently jumps inside a brick) because of the mixture of ints and doubles. And remember that you can run into similar problems because of numerical rounding issues with floating-point numbers.

  1. Instead of working in units of pixels, work in tenths of a pixel. (Or hundredths, or (1/256)-ths, or whatever.) Then you would be changing the velocity by 5 each step, rather than 0.5. No more doubles! This is either a very practical solution, or an ugly hack, depending on your tastes. :stuck_out_tongue:

Simon

So you would be passing in like 105 and would expect to get -4 back so he is at 101. You want to check if 105 - 9 is inside a box. Change if (insideBrick(xWorld, yWorld)) -> if (insideBrick(xWorld, YWorld + step). Then use the same smallStep = yWorld + step - topOffset.