Help with my game (currently for animations)

I actually tried putting the jump loop in a function, and inside of the loop, I put the function, and it worked…just like the while-loop, so basically my player teleported.

I was going to break this down more, but I ended up writing the lot so I may as well post it all in one go.

You don’t have to take it in all at once though. Feel free to take your time to make sense of it.

In other words, changing this:

To this:

if (isOnSolid)
{
  if(playerEntity.yVelocity > 0)
    playerEntity.yVelocity = 0;
}
else
{
  playerEntity.yVelocity += gravitySpeed;
}

If the player is on solid ground, and if the y velocity is greater than 0 (i.e. it’s heading downwards), the velocity is set to 0.

The inner test is to avoid zeroing the velocity when the player is travelling upwards, because that implies that the player either jumped or was knocked upwards by some force.

The outer test is to differentiate between the logic that runs when the player is standing on a solid surface (i.e. when there should be no gravity) and the logic that runs when the player is not on a solid surface (i.e. gravity takes effect).

I kind of feel like this is a bit of a cheat in a way because theoretically gravity should always be applying, but not applying gravity when the player is on a solid surface is probably simpler than whatever the alternative would have to be.

That’s easy. A simple if condition shoved into the input handling:

// This probably should have been 'justPressed'
// but, I copied and pasted from the above code.
if (arduboy.pressed(A_BUTTON))
{
  playerEntity.yVelocity += -5;
}

If acceleration were taken into account then this would be slightly different. Probably the jump would be a force that affects acceleration rather than velocity, and the player would probably start slow and accelerate upwards before decelerating and then falling, which would be more realistic.

For now I’m cutting corners so the code starts simple and provides more immediate results. You may even find that this sort of unrealistic physics suits your needs - I don’t know either way, I’m only presuming you’re going to need to incorporate acceleration later based on instinct.

I believe you had these and then removed them.

playerEntity.x = playerX;
playerEntity.y = playerY;

Believe me, they are important…

They go in updatePlayer, above the drawing code. (Which again, really belongs in a separate function. handleInput, updatePlayer and drawPeople should all be separate functions because they do very different things.)

This is also a simple fix. From this:

To this:

if (isPlayerRight)
{
  Sprites::drawOverwrite(playerEntity.x, playerEntity.y, playerSideways, 0);
}
else
{
  Sprites::drawOverwrite(playerEntity.x, playerEntity.y, playerSideways, 1);
}

playerEntity.x and playerEntity.y represent the player entity’s actual position.
playerX and playerY are merely temporary values used for the duration of the update function - they represent the values that playerEntity.x and playerEntity.y might end up becoming - their future values, before those values have been properly determined.

When I get to the rectified collision code, you’ll see why that distinction is important.

I double checked and actually I didn’t have this wrong. It was right the first time. I only thought it was wrong because of some poor thinking and a misplaced semicolon, so disregard this point.

Now we reach the complicated part.

From this code:

To this:

void updatePlayer()
{
  if (isOnSolid)
  {
    if(playerEntity.yVelocity > 0)
      playerEntity.yVelocity = 0;
  }
  else
  {
    playerEntity.yVelocity += gravitySpeed;
  }

  auto playerX = (playerEntity.x + playerEntity.xVelocity);
  auto playerY = (playerEntity.y + playerEntity.yVelocity);

  isOnSolid = false;

  if (playerEntity.yVelocity > 0)
  {
    auto platformTop = platform1Y;
    auto platformLeft = platform1X;
    auto platformRight = (platform1X + platform1Width);

    auto playerLeft = playerX;
    auto playerRight = (playerX + playerWidth);

    auto oldPlayerBase = (playerEntity.y + playerHeight);
    auto newPlayerBase = (playerY + playerHeight);

    bool leftOverlaps = ((playerLeft >= platformLeft) && (playerLeft <= platformRight));
    bool rightOverlaps = ((playerRight >= platformLeft) && (playerRight <= platformRight));
    bool playerPassesThroughTop = ((oldPlayerBase <= platformTop) && (newPlayerBase >= platformTop));

    if ((leftOverlaps || rightOverlaps) && playerPassesThroughTop)
    {
      playerY = (platformTop - playerHeight);

      isOnSolid = true;
    }
  }

  playerEntity.x = playerX;
  playerEntity.y = playerY;
}

Firstly, I’ve moved the drawing code into a separate function, drawPlayer, moved updatePlayer out of handleInput, and then had them both called from playScreen:

void playScreen()
{
  handleInput();
  updatePlayer();
  drawPlayScreen();
  drawPlayer();
}

Like I say, separate functions doing separate jobs - separation of concerns, and the update logic should happen before drawing because you want the world to be fully updated before you start drawing things - the rendered frame is supposed to be a snapshot of a particular point in time, not a mixture of things from different points in time. (Your updating logic is how time progresses within the game world.)

I also moved the gravity code out of handleInput and into updatePlayer since it’s technically updating logic, not a reaction to user input.

That aside, most of this is the same. The big difference is here:

auto oldPlayerBase = (playerEntity.y + playerHeight);
auto newPlayerBase = (playerY + playerHeight);

bool leftOverlaps = ((playerLeft >= platformLeft) && (playerLeft <= platformRight));
bool rightOverlaps = ((playerRight >= platformLeft) && (playerRight <= platformRight));
bool playerPassesThroughTop = ((oldPlayerBase <= platformTop) && (newPlayerBase >= platformTop));

if ((leftOverlaps || rightOverlaps) && playerPassesThroughTop)
{
  playerY = (platformTop - playerHeight);

  isOnSolid = true;
}

What the code was doing before is that it was testing to see if the player’s base was between the top and the bottom of the platform, and if it was, then the player was moved onto the top of the platform and declared to be on a solid surface.

However, the problem with that is that if the player moves too fast they actually overshoot the platform and the game never detects them travelling inside the platform (because they don’t - they teleport past it). This is what I (and a few others) refer to as the “bullet through paper problem” - where a fast moving projectile effectively jumps over a small obstacle because the collision checking isn’t precise enough.

If this were happening with polygons, the code to rectify that would be mind-numbingly complicated and probably involve things like ‘raytracing’ or maybe ‘separating axis theorem’ collision checking.

Fortunately, this game is done entirely with rectangles, so we can cheat!

This is where the distinction between playerEntity.y and playerY becomes important. playerEntity.y is the ‘old’/‘current’ position, and playerY is the ‘new’ position that the game logic is trying to move the player to. From these, you can work out the base of the player’s sprite from both the old position and the new position:

auto oldPlayerBase = (playerEntity.y + playerHeight);
auto newPlayerBase = (playerY + playerHeight);

Then, you compare the two to the top of the platform. If the old position was less than or equal to the top of the platform and the new position is greater than or equal to the top of the platform, then the player is attempting to pass through the top of the platform. This holds true whether the player is attempting to travel 1 pixel past the platform or 100,000 pixels past the platform.

bool playerPassesThroughTop = ((oldPlayerBase <= platformTop) && (newPlayerBase >= platformTop));

Thus, if the player’s right or left edges horizontally overlap the platform, and if the player is attempting to pass through the platform, the player has theoretically collided with the platform, regardless of how fast the player was travelling.

if ((leftOverlaps || rightOverlaps) && playerPassesThroughTop)

And that is how you know to stop the player from travelling through the platform by setting the player’s y coordinate and registering that they are now on solid ground. (Theoretically this might also be a good place to zero out the player’s vertical velocity, but I’m not sure what the implications of that would be, e.g. whether there might be some potential negative side effects in the future, so for now I left that as the concern of the earlier code.)

Now, does that all make sense?


One quick note: technically the if (playerEntity.yVelocity > 0) check in updatePlayer isn’t necessary, because playerPassesThroughTop should only be true if the player is travelling downwards, so the code will still work fine, but the main point of that check is to reduce the amount of time spent checking collisions because collision checking can get expensive when you start doing it for more and more objects.

E.g. if you get to a point where you have several entities and several platforms, and you have to check every entity to see if they’re colliding with any platform, that’s going to chew up quite a bit of CPU time, so if you can cut that down by deciding not to collision check an entity because it’s not travelling downwards then that could save you a bit of CPU time, which should help the game run more smoothly.


To be more blunt than @filmote: there is no such thing as an ‘if-loop’…

An if statement runs its block of code (i.e. its body) once (and only once) if the condition is true, or doesn’t if the condition is false. For the code in the body of the if statement to run again, the function that the if statement resides in would have to be called again.

In contrast, a while statement continuously runs its block of code as long as the condition is true, and only ceases to run the code when the condition becomes false (or when the code inside the block uses a break; statement to explicitly break out of the loop).

Theoretically, a while statement is equivalent to an if statement that uses a goto to jump back to the code before the if.

Which is to say that this:

int i = 0;
while(i < 10)
{
	++i;
}

Is equivalent to this:

int i = 0;
// This label marks a position that can be
// jumped to with the 'goto' statement.
loopStart:
if(i < 10)
{
	++i;
	// This 'goto' statement forces the code to jump
	// back to the 'loopStart' label.
	goto loopStart;
}

And internally, that’s actually more or less what the compiler does - it builds the code for the while by inserting a jump at the end. But using gotos and labels is bad practice for a number of reasons - in particular, they’re tedious and error prone.

In most cases the only thing you’re going to need loops for is to iterate through arrays, in which case a for loop is better anyway.

int i = 0;
while(i < 10)
{
	doSomething(array[i]);
	++i;
}

// Effectively the same as the 'while' loop above
for(int i = 0; i < 10; ++i)
{
	doSomething(array[i]);
}

That’s certainly not the case with the version on GitHub.

Pressing the right or left button adds horizontal velocity (i.e. adds to the x velocity), and that velocity stays until some other force cancels it out. You don’t have any mechanism in place to diminish the horizontal velocity (e.g. friction), so if the player presses the right button once (and then lets go and never touches the Arduboy again), their entity/character will just keep travelling right forever because there’s no other force in place to diminish that velocity.

If the player were controlling a spaceship, that would actually be accurate because there’s negligable friction in space, but on a planet where gravity and friction are in effect, if you push a toy car along a surface it will eventually come to a halt because the friction of the surface diminishes the horizontal velocity.

You don’t actually have to use proper friction, you can ‘cheat’ so the player has full control of their character/entity’s horizontal movement by doing something like:

if (arduboy.pressed(RIGHT_BUTTON))
{
	playerEntity.xVelocity = movementSpeed;
}
else
{
	playerEntity.xVelocity = 0;
}

But in a game like Smash Bros. you’re probably going to have player characters/entities being knocked around the arena, in which case you don’t want the player to come to a halt just by letting go of the directional buttons.

You could have different logic depending on whether the player is on the ground or in the air, but really it depends how you want the physics to behave.

1 Like

Yep, currently the player just keeps on moving, and right now, to get the player moving, you have to sort of hold the button down for maybe about 500 millisecs before the player actually starts to move. As for the friction, I certainly would like to maintain the velocity, and slowly add friction to reduce the velocity until it hits zero. Not sure how to do that though. Also, after adding your updated collision code, the player has a black square under it, is it a bug or something?

Also, on a completely unrelated note, I purchased my first official Arduboy FX! Super excited for it.

As far as I’m aware it was doing that before.
You may not have noticed depending on where the sprite was being rendered.

The cheating option is to multiply the velocity by a fractional value between 0 and 1, and then do a check to reduce a sufficiently small velocity to 0. But to do that you’ll need fixed points or floating points first.

The proper way to do it is long, boring and complicated.

The square is only visible when the sprite is on the platform, since it’s black, and the platform is white. The code I had and most likely the one I uploaded to Github did not have the black square.

I’m kind of confused how to do that, maybe share some example code? Also, all my physics code is in floating points (ex: movementSpeed, playerEntity.x etc).

You are using drawing a sprite that is 9px high but all sprites are really multiples of 8 high. The lower section will be a square. You need to read up on sprites and masks, here:

1 Like

I’m not sure I understand what you mean, do I need to change my Sprite to 8x8?

No. You need to create and use a mask for the existing sprites as per the referenced document.

1 Like

But the thing is, my sprite is only 9x9, so the black square underneath it shouldn’t be there?

Read the documentation. You need a mask.

1 Like

Yeah, you were right, I used SelfMasked. Not sure why that black square was there though.

It is an ‘undefined’ behaviour when using self-masked. You could argue its a bug :slight_smile:

I’ve thought about it and realised why.

I changed the order of drawing so that the world is drawn before the player whereas before you were drawing the player, then the world.

Which is to say that the black square was technically present in the original GitHub code, but it wasn’t obvious because the platform was being drawn on top of it.

If playScreen were instead:

void playScreen()
{
  handleInput();
  updatePlayer();
  drawPlayer();
  drawPlayScreen();
}

Then you wouldn’t see the square at the moment, but it would still technically be there because of how sprites work.

The height of a sprite is rounded up to its nearest multiple of 8, so a height of 9 gets rounded up to 16, thus you end up with a 9x7 black box beneath your player sprite.

An image mask fixes that.

(Note that Arduboy’s image masks are actually the inverse of how video games normally use masks because the operations involved are different.)

Self-masked is the quickest solution, but the downside to using self-masked is that your character’s black body will also be transparent, so only its eyes and outline will be visible when it overlaps another sprite.

With a proper mask you could make the body opaque and the rest of it transparent.

The version on the GitHub is not, and that’s the only version of the code I can see. If your local copy of the code is different, I have no way of knowing what it’s like.

Worry about understanding and incorporating all the other changes first.

If you leave it too long without updating the GitHub, communication is going to fall apart because nobody else will know what your code looks like.


What’s undefined/a bug about self-masked?
Do you mean the ‘round up to the nearest multiple of 8’ part of drawOverwrite is a bug?

1 Like

No.

I mean that drawSelfMasked() should not affect anything outside of the size of the image. If you image is only 9px wide, the remaining 7px are all zeroes (black). Even if the boundary is ignored and all images are multiples of 8px, the zeros should not render a black square as black (zero bits) are not supposed to affect the buffer.

I still don’t know what you mean by ‘bug’/‘undefined behaviour’.

drawSelfMasked() doesn’t produce a black square below (it’s technically still there, but treated as ‘transparent’), whereas drawOverwrite() does produce a black square because of how it’s defined.

Here’s the updated code, I’m going on a trip today so this is likely how it will stay for the majority of today.

And one question about masks, how do I create them? I saw @filmote tutorial but I’m really confused.

EDIT: I just changed the drawing player code back to drawOverwrite, and switched the drawPlayer and drawPlayScreen functions in playScreen.

That’s much more useful, now it’ll be easier to keep track of what’s what.


Three quick things…

Firstly, this is redundant:

If playerEntity.xVelocity == 0 then xVelocity is already 0, so playerEntity.xVelocity = 0; merely sets it to the value it already has.
If it were almost zero (but not quite zero) then playerEntity.xVelocity == 0 would not be true.

Secondly, hasJumped isn’t actually being used anywhere, so unless you’ve got something planned for it, it’s probably redundant. (And if you have got something planned, there may be a better option.)

Thirdly, playerEntity.x and playerEntity.y are still int16_t, so even though playerX and playerY in the collision code will be inferred as float, you’re still going to end up with everything after the decimal place being discarded the moment those values are assigned to playerEntity.x and playerEntity.y.


If you’re on Windows, the easiest way is this:

I can’t remember offhand if any other tools can generate the masks for you, but I expect there’s at least one that can:

That’s not an ideal solution either, because it means your player is going to appear to be jumping behind the platforms instead of in front of them. Unless you’re happy with that being the case?

1 Like

Yep, forgot about those, I changed them to floats and the game performs quite differently, for the better.

Yeah, that was when I was still testing, and when I added your updated code I forgot to remove it.

I will add that right now, it was supposed to be yes or no if the player jumped, so that it can prevent the player from jumping again (though I may change it for double jumping).

I think I’m ready to add friction now, but I don’t know how, and I would like to add a lot of friction, as I only want the player to slide very little, just so the movement feels more natural.

And another thing, since you can accelerate infinitely while pressing left or right, how do I add a speed cap?
Nvm I figured it out, I added another if-statement inside the check for the button presses and only runs the code if xVelocity is smaller or equal to 0.5.

I don’t have time to provide checked and edited code, but the basic idea is something like this:

playerEntity.xVelocity *= inverseFriction;

(Note: it’s ‘inverse friction’ because a value of 0 would not mean ‘frictionless’ - a value of 1 would be ‘frictionless’; it’s the inverse relationship to friction, less inverse friction means more friction.)

However, you probably only want friction to occur on the ground, so you probably want something like:

if(isOnGround)
	playerEntity.xVelocity *= inverseFriction;

However, there’s yet one more problem…

Multiplication alone isn’t quite enough because xVelocity will never quite reach 0 this way. Even an inverseFriction of something like 0.00000001 (theoretically) wouldn’t reach zero. To solve that, you need to pick a cut off point at which you declare “if the velocity has become smaller than this amount, it is declared to be 0”.

if(isOnGround)
{
	playerEntity.xVelocity *= inverseFriction;

	// Chosen arbitrarily
	constexpr float cutoffPoint = 0.000001;
	
	if(playerEntity.xVelocity < cutoffPoint)
		playerEntity.xVelocity = 0;
}

(This should go in updatePlayer, between the application of gravity and the collision checking.)

That’s one way to do it.

I suspect there’s a more ‘proper’ way to do it involving actual physics, or a more controlled way, but if it gets the behaviour you want and doesn’t interfere with anything else then it’ll do.

1 Like

After adding your code, the player moves really jerkily and slow. Also, if you’re multiplying, won’t the number get bigger? For some reason, going left is always slower and more jerkier than going right. Figured out it was due to my speed cap line for the left movement.