It is an ‘undefined’ behaviour when using self-masked. You could argue its a bug
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?
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?
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.
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.
What’s 0.5 * 0.5
?
With the physics finished up (or almost if you count all the hitboxes and stuff), I’ve moved onto animations, and I created some frames. I have an animation set up whenever the player presses B (in the HandleInput() and the attack1Animation() as well as the drawPlayer()). Now it works absolutely fine as it is, but I wanted to make it automatic, and play on it’s own after only one B press, so I added an everyXFrames() function, although when I play the code, and press B, it just doesn’t work. I have 3 frames, so I set the everyXFrames to everyXFrames(20). Am I doing something wrong, or is there a technicality?
Also unrelatedly, I love using VSCode, it’s much more efficient and fun-to-use than the Arduino IDE!
Previously you called it an ‘if loop’ and I am wondering if you still think the code below will execute multiple times.
if (arduboy.justPressed(B_BUTTON))
{
if (arduboy.everyXFrames(20))
attack1Animation();
}
This will execute once - when the player presses the B button - and never again until they press the button a second time.
Also the code:
void attack1Animation()
{
if (animationFrame < lastAttack1Frame)
{
++animationFrame;
}
else
{
attack1AnimationEnable = false;
animationFrame = 0;
}
attack1AnimationEnable = true;
}
Does some frame counting (great!) but when the last frame has been shown sets the attack1AnimationEnable
to false then immediately sets it back to true.
I am guessing what you want to do is call attack1Animation()
from the main loop()
rather than when B is pressed. Change the code to:
if (arduboy.justPressed(B_BUTTON)) {
attack1AnimationEnable = true;
}
And
void attack1Animation() {
if (attack1AnimationEnable) {
if (animationFrame < lastAttack1Frame) {
++animationFrame;
}
else {
attack1AnimationEnable = false;
animationFrame = 0;
}
}
}
Then call attack1Animation()
in the main loop.
Actually, when the player presses the B button and the number of frames elapsed since the game start happens to be a multiple of 20.
That second part is really important, because unless you time it absolutely perfectly then attack1Animation()
won’t get called.
@filmote’s code is closer to what’s needed - the code certainly needs to separate the animating from the input that activates the animation (it’s becoming my mantra: separation of concerns!), but @filmote’s code still has the aforementioned bug:
if (arduboy.justPressed(B_BUTTON)) {
if (arduboy.everyXFrames(20)) {
attack1AnimationEnable = true;
}
}
It’ll only fire when B is first pressed and the number of elapsed game frames is divisible by 20. (Note: I’m saying ‘game frames’ to distinguish from ‘animation frames’.)
So, @Ard_Flamingo, what you probably need is something more like this:
// When the B button is first pressed
if(arduboy.justPressed(B_BUTTON))
{
// Enable the first attack animation
attack1AnimationEnabled = true;
}
void updateAttack1Animation()
{
// If the animation isn't enabled
if (!attack1AnimationEnabled)
// Exit the function
return;
// Only update the animation every 20 game frames
if(!arduboy.everyXFrames(20))
return;
// If the animation has not finished
if (animationFrame < lastAttack1Frame)
{
// Move to the next animation frame
++animationFrame;
}
// Else, when the animation has finished
else
{
// Disable the animation
attack1AnimationEnabled = false;
// Reset the animation frame
animationFrame = 0;
}
}
Note that the everyXFrames
belongs inside the animation code. It’s the animation that needs to update every 20 frames, not the code that enables it!
Though I expect you’ll still run into problems when you start trying to add a second attack animation because there’s an important piece of machinery you’re still missing that’s needed to glue things together.
You might find later that you need the delta timing or acceleration, but if you’re happy with what it’s doing so far then fair enough.
The version on GitHub does seem to be missing friction though.
Did you decide not to add that, or could you not get it to work?
I would count it, because you’ll need to modify the existing collision code to handle more platforms. Not massively, but significantly enough that it’s worth counting the collision code as ‘unfinished’.
(Unless you were planning to just copy and paste the collision code for every platform?)
Undoubtedly. Both @filmote and I use it for Arduboy games.
(Well, I use VSCodium rather than VSCode, but it’s 99% the same tool.)
I was attempting to add it, but the multiplication happens whenever the xVelocity is bigger than 0, so the movement will always be jerky. I tried making it only happen if you don’t press A & B, then the friction gets added. But I might continue playing with it.
Pretty much, but I was going to remove the bottom and side collision for both top platforms, only only allow for top collisions.
Thanks! It’s working great now.
0
multiplied by anything is always 0
, so there’s no reason to avoid multiplication when the x velocity is 0
as far as I’m aware.
That makes me wonder if there was some other reason for the ‘jerkiness’, though without knowing what you wrote I can’t test it.
It would work, but duplicating the code is wasteful (each copy significantly increases the progmem size) and doesn’t scale well (if you got up to e.g. 20 platforms, would you really want to be making 20 copies of what would be effectively the same code block?).
Can you think of any better options?
I think you misunderstand how the collision code works.
It doesn’t check for side collisions at all, it only checks to see if the player has travelled down through the top of a platform.
You might want to read through the code to make sure you understand what each line is actually doing. The lines that reference the sides of the platform only check to see if the player is horizontally in line with the platform. Without those checks, the platform would effectively be infinitely wide (as if it were the ground), and then the player would be unable to fall over the edge.
For now at least. You can’t make an omelette without breaking eggs.
Yep, you’re right. I updated the repo with my new code, it has a new animation for when the player presses A, I did it exactly how I did the other one, but it just doesn’t work. The function is upAttackAnimation(). I think it should work but it doesn’t.
Right, but that’s not the issue. When you move, the x velocity increases, and if the x velocity is bigger than 0 (i.e. you’re moving), then the multiplication fires. So it constantly multiplies and makes the x velocity 0, while you’re moving (and adding to the x velocity). But anyway, I’m pretty happy with the movement so far.
Something to do with arrays? Like an array of platform widths, and heights etc.
It took me a few minutes to work out the problem.
I’m going to explain the whole process in detail just for the sake of demonstrating how one goes about solving these sorts of problems…
The first thing I did was the old standby: add some text printing code to check that variable states and conditions are as they should be.
// Technically I didn't have to use these,
// but memory saving habits die hard.
const auto trueString = F("true");
const auto falseString = F("false");
// I didn't add this the first time. What a mistake that was!
arduboy.setTextSize(1);
// Print the status of the two important variables,
// just to verify that they do indeed have the intended values...
arduboy.print(F("Attack 1: "));
arduboy.println(attack1AnimationEnable ? trueString :falseString);
arduboy.print(F("Attack Up: "));
arduboy.println(upAttackAnimationEnable ? trueString :falseString);
if (upAttackAnimationEnable && !attack1AnimationEnable)
{
// If the above condition is true, this will print some text
arduboy.println(F("Animation running"));
Sprites::drawOverwrite((playerEntity.x + 2), (playerEntity.y + 1), upAttack, animationFrame);
}
Sure enough the text in the condition printed when it was supposed to and the variable values were what they should have been, which ruled out a problem with the logic, so I commented out all the lines above (because you’re actually drawing the animations on top of the player’s default sprite):
// if (isPlayerRight)
// {
// Sprites::drawOverwrite(playerEntity.x, playerEntity.y, playerSideways, 0);
// }
// else
// {
// Sprites::drawOverwrite(playerEntity.x, playerEntity.y, playerSideways, 1);
// }
// if (attack1AnimationEnable && isPlayerRight)
// {
// Sprites::drawOverwrite(playerEntity.x, playerEntity.y, playerAttack1Right, animationFrame);
// }
// if (attack1AnimationEnable && !isPlayerRight)
// {
// Sprites::drawOverwrite((playerEntity.x - 3), playerEntity.y, playerAttack1Left, animationFrame);
// }
Then when I ran it again, nothing drew, which firmly established that the problem was with the actual drawing of the animation.
I could see no problem with the line that calls the function, so I looked at Images.h
…
Clearly the sprite is not blank, so that’s not the problem.
Then I noticed the first line.
If you haven't noticed the problem yet...
You’re missing the PROGMEM
macro.
So I undid all my previous edits, fixed that problem and then it worked.
Well, sort of… The fact you’re drawing the animation on top of the default sprite becomes really obvious because the animation is offset from the default sprite. I’m not sure if it’s supposed to be offset or if it’s supposed to be properly aligned.
That can be fixed either by correcting the offset or preventing the player from being drawn when an animation is running.
(Note that I wouldn’t have been able to identify the problem had I not had up to date code, because this time the problem was not deducable from the game’s behaviour, it required examining the code to notice the discrepency.)
In case you’re wondering about the ?
and :
, that’s C++'s conditional operator.
(Also colloquially referred to as “the ternary operator” because it’s C++'s only ternary (3-argument) operator.)
It behaves a bit like an if
-else
statement, but it’s an expression rather than a statement so it conditionally evaluates to one of two values rather than conditionally running one of two statements.
Or to put it another way, this:
int variable = condition ? 5 : 10;
Is roughly equivalent to:
int variable;
if(condition)
variable = 5;
else
variable = 10;
That’s kind of what the point of friction is - it’s supposed to gradually whittle the velocity down the zero.
But I think perhaps getting it to balance properly against how you’re doing movement would be difficult. The friction has to be strong enough to stop the player sliding off the platform but weak enough that the player’s movement force is enough to overcome it to start the player moving.
One slightly cheating option that might be better than coming to an immediate standstill is:
if (arduboy.notPressed(RIGHT_BUTTON) && arduboy.notPressed(LEFT_BUTTON))
{
playerEntity.xVelocity *= inverseFriction;
}
(Using multiplication instead of carefully coordinated subtraction is already ‘cheating’, so a bit more ‘cheating’ shouldn’t hurt.)
I did a few quick tests with that and found that 0.95
is slightly slippery, whilst 0.75
is not quite instant.
Precisely.
If you had a way to represent any platform with a single type (e.g. Rect
, or Platform
) then you could keep all your platforms in an array and simply loop through them all.
After all, why write the same code 20 times when you can write it once and then run it over as many objects as you want?
You could even do something similar for drawing your platforms too…
Oh, of course! I always use Team A.R.G’s image converter, and when I was renaming the image, I must have accidentally deleted PROGMEM!
Normally I just use the normal x and y for the player, then subtract and or add values to get the image properly alligned (ex: (playerEntity.x - 4))
Thanks! I’ll try this out. I love this! I’m using a value of about 80-85 right now. It feels much much more fluid than just coming to a stop
If you’re on Windows and willing to either compile some C# from source or trust a downloadable executable then there is another converter you could try. <\self-promotion>
I can see that much, but what I meant is that I wasn’t sure if the offsets are wrong or if the image was already ‘properly aligned’ and the duplicate drawing was the bug rather than the incorrect offsets.
Yep, that’s the whole idea of having friction in the first place: more convincing movement.
Thinking about it, I’m not sure Smash Bros has that effect, but I’m sure I’ve seen it in one fighting game or another.
I would use yours, since it has a built-in mask function as well, but I’m actually on MacOS.
Yep, they were wrong, fixed it though.
Yeah, it doesn’t, probably so the player has more control. But Smash Bros has a lot of other things that makes the game feel more fluid, whereas mine probably definitely won’t have them.