Help me with C++ for my game


#203

Hey good news!

I was gonna ask about 8 or 9 questions about bugs, but I was able to fix every single one of them. I’ve improved a bit

This is actually great, it’s an easy fix. What would be harder was if it wasn’t constant and it actually just worked, that’d be a bit harder to find

French Canadian layout, but I’m using a slightly different one to be able to write { and } efficiently. { is CTRL+ALT+` (yes that tiny thing). } is CTRL+ALT+< (not a typo either). but the symbol for < is actual | on top and \ below.

[ and ] are the keys { on top and [ below, and } on top and ] below.

The wave symbol which I need for Lua, assuming I didn’t forget it… is got it! It’s the key with: and; with CTRL and ALT. Anyway, I had a very fun time learning programming with a keyboard like that. It was the same with my old laptop too. Actually my old laptop was worse, the Q and W keys don’t work, so I had to remap them to two other buttons. I programmed with those broken keys for 2 years

I tried the one you suggested, but I accidentally entirely removed the debugging aspect of it (if you remember, it’s not 100% compatible with the Arduboy, things like ‘PROGMEM’ are seen as errors). SO I went back to the Arduino IDE

I use it for my sprites. sprLevel, sprPlayer. Not to say that it’s an array, but to say that it’s an image. Otherwise I’d have to say levelSprite or levelTileset etc. I’m open to suggestion, but I think this is one exceptional case where it is actually beneficial instead of detrimental.

I would love to make it part of my player and my level object (I’m doing this for the free functions) but I think I’d have to learn a bunch of other stuff to learn again, with pointers and type conversion etc

But I am open to it

Edit: wait, can I make a public static class called Images, and have all my sprites in that? like Images::playerRunning ?

I think the answer is yes, I’ll go try it now


#204

What’s really, really really cool is that I’ll be able to reuse this for other platformers I make in the future, and sidescrollers. The only thing I might need to change a bit is the view, because for my platformer there’s no vertical movement of the camera.


(Pharap) #205

Good job. Often it’s just a matter of confidence and determination.

I’ve seen many different students learning programming, the ones who did badly were always the ones who panicked when they saw an error and the ones that did quite well were the ones who kept their cool and gave it a go.

That’s really weird.

I think I’ll stick to the British layout:

Maybe you should get one? :P

Grave”.

I’m using them to write my code blocks when I reply.

Tilde”.

Yikes.
The worst I’ve had is a missing ctrl key.
(A cat ripped it off when they were being removed from the keyboard.)

Do you mean having a red line under them or the compiler actually refusing to compile them?

If it’s just a red line then that’s still an improvement over the Arduino IDE.

You’re probably better off making those suffixes instead, i.e. playerSprite, levelSprite.
In this case ‘player’ and ‘level’ become an adjective and ‘Sprite’ is a noun.
Also, it follows that theme of making the code look more like natural language.
(Specifically English. I’m aware that French uses ‘noun adjective’ instead of ‘adjective noun’.)

Here’s an alternative technique for you: use a namespace.

// Avoid name conflict with Arduboy2's 'Sprites' class
namespace Images
{
	const unsigned char player[] PROGMEM = { /*...*/ };
	const unsigned char monster[] PROGMEM = { /*...*/ };
}

Then you can do:

Sprites::drawOverwrite(x, y, Images::player, 0);

(I think I used that technique in Minesweeper.)

If you were writing for desktop or you were using the same class for both the player and the monsters then I’d completely agree.
However, on the Arduboy I think it often actually makes more sense to just keep sprites global because it uses less RAM and progmem.

For what it’s worth, that wouldn’t be that difficult.

class Player
{
public:
	const unsigned char * sprite;
	
	// ...
};

void someFunction(Player & player)
{
	Sprites::drawOverwrite(player.x, player.y, player.sprite, player.frame);
}

const unsigned char playerSprite[] PROGMEM = { /*...*/ };

void someOtherFunction(Player & player)
{
	player.sprite = playerSprite;
}

That would work too, but a namespace is easier.


#206

AMAAAAZING!!! I don’t know if I should be that excited about this, but I am. I’m gonna use namespaces as much as you use templates, watch out

Well no bugs to report yet, still porting everything. I’ll probably get faster if I start doing this a lot, if I wasn’t learning c++ while using it I’d probably be faster

Edit: I love how in C++ if you access an element out of bounds of an array, you get whatever values are at those indexes. It looks beautiful as a background for the game (I’m being sarcastic about it looking good, but it does look interesting)

Edit2: I also love that it is “strongly typed”. I confused two variables and I immediately knew what was wrong, the compiler told me.


(Pharap) #207

I don’t actually use templates as much as people think,
it just looks like I do because most people (writing for Arduboy) don’t use any templates in their code.

Namespaces are incredibly useful though:
https://en.cppreference.com/w/cpp/language/namespace

Actually, that’s partly the Arduino environment’s fault.

If you were writing for desktop you could use std::array instead, and that has a member function at which would throw a std::out_of_range exception if you tried to go out-of-bounds.

Modern C++ is actually really safe, but when you’re writing for an embedded system you often can’t afford things like that, you have to go low level and be extra careful.

Type safety is a really useful thing.

Unfortunately a lot of the implicit conversions on basic types make the strong typing less useful, but it’s too late to change the rules about that.


#208

Here’s how my images and masks are handled now:

#pragma once

#include <Arduino.h>
#include <Arduboy2.h>


namespace Images {
    /**** LEVELS ****/
    const uint8_t level[] PROGMEM  = {
        // Dimensions
        8, 8,
        // Tileset, 4 tiles
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfe, 0x56, 0x2a, 0x56, 0x2a, 0x56, 0x2, 0x6, 0xe2, 0x66, 0xa2, 0x60, 0x2e, 0x66, 0x2a, 0xff, 0xc3, 0xbd, 0xa5, 0xb5, 0xbd, 0xc3, 0xff, 0xff, 0xff, 0xc3, 0xbd, 0xbd, 0xc3, 0xff, 0xff, 0xff, 0x3f, 0xcf, 0xb3, 0x4f, 0x3f, 0xcf, 0x3f,     
    
    };
    /*** PLAYER ***/
    const uint8_t playerRun[] PROGMEM  = {
        // Dimensions
        8, 8,
        0x00, 0x7c, 0x3e, 0x26, 0x3e, 0x26, 0x7c, 0x00,
        0x00, 0x3c, 0x7e, 0x26, 0x3e, 0x66, 0x3c, 0x00, 
    };
    const uint8_t playerAir[] PROGMEM  = {
        // Dimensions
        8, 8,
        0x00, 0x7c, 0x3e, 0x26, 0x3e, 0x26, 0x3c, 0x00, 
    };

}
namespace Masks {
    /*** PLAYER ***/
    const uint8_t playerRun[] PROGMEM  = {
        // Dimensions
        0xfe, 0xff, 0xff, 0x7f, 0x7f, 0xff, 0xff, 0xfe,
        0x7e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 
    };
    const uint8_t playerAir[] PROGMEM  = {
        // Dimensions
        0xfe, 0xff, 0xff, 0x7f, 0x7f, 0x7f, 0x7f, 0x7e, 
    };
}

Quick question, if I want functions that can be accessed anywhere, is it better to use a static public class or a namespace?

For example, for this:

#include <Arduino.h>
#include <Arduboy2.h>
#include "Level.h"

static int16_t Level::xOnGrid(int16_t xpos) {
    int16_t result = (xpos / LEVEL_TILE_WIDTH);
    result = max(result, 0);
    result = min(result, LEVEL_WIDTH_IN_TILES);
    return result;
}
static int16_t Level::yOnGrid(int16_t ypos) {
    int16_t result = (ypos / LEVEL_TILE_HEIGHT);
    result = max(result, 0);
    result = min(result, LEVEL_WIDTH_IN_TILES);
    return result;
}


static uint8_t Level::getTileAtTile(int16_t xIndex, int16_t yIndex) {
    return pgm_read_byte(&(levelData[LEVEL_WIDTH_IN_TILES * yIndex + xIndex]));
}
static uint8_t Level::getTileAtPixel(int16_t xpos, int16_t ypos) {
    int16_t xIndex = xpos/LEVEL_TILE_WIDTH;
    int16_t yIndex = ypos/LEVEL_TILE_WIDTH;
    return Level::getTileAtTile(xIndex, yIndex);
}

(Pharap) #209

Technically in C++ there’s no such thing as a ‘static’ class.
You can have a regular class with static members, but the class itself isn’t actually ‘static’.

A namespace is better anyway.

namespace Something
{
    inline void someFunction()
    {
    }
}

Also, you don’t need static in front of your functions.
You’re probably expecting static to do something different to what it actually does.

You do however need inline on a function if you’re declaring it in a header file.

(See this SO answer for info about both static and inline.)


#210

Ok, I’ll use namspace then

I found a really weird bug, if I go far enough to the right (outside the level), the game crashes.
It’s not a bug I need to fix since it’s outside the level, but it is interesting

It compiles fine without the inline keyword, why don’t I need it?

Level.h

#pragma once

#include <Arduino.h>
#include <Arduboy2.h>

constexpr uint8_t LEVEL_WIDTH_IN_TILES = 160;
constexpr uint8_t LEVEL_HEIGHT_IN_TILES = 8;

constexpr uint8_t LEVEL_TILE_WIDTH = 8;
constexpr uint8_t LEVEL_TILE_HEIGHT = LEVEL_TILE_WIDTH;
constexpr uint16_t LEVEL_WIDTH_IN_PIXELS = LEVEL_TILE_WIDTH * LEVEL_WIDTH_IN_TILES;
constexpr uint8_t LEVEL_HEIGHT_IN_PIXELS = LEVEL_TILE_HEIGHT * LEVEL_HEIGHT_IN_TILES;

const uint8_t levelData[LEVEL_WIDTH_IN_TILES*LEVEL_HEIGHT_IN_TILES] PROGMEM = {
a ton of zeros and ones that were taking too much space for this....
};

enum class TileType : uint8_t {
    background,
    solid,
    coin,
    spike
};

namespace Level {
    uint8_t getTileAtPixel(int16_t xpos, int16_t ypos);
    uint8_t getTileAtTile(int16_t xIndex, int16_t yIndex);
    bool isIndexOutOfBoundsAtTileX(int16_t xIndex);
    bool isIndexOutOfBoundsAtTileY(int16_t yIndex);
    bool isTileOutOfBoundsAtTile(int16_t xIndex, int16_t yIndex);
    int16_t xOnGrid(int16_t xpos);
    int16_t yOnGrid(int16_t ypos);
};

Level.cpp

#include <Arduino.h>
#include <Arduboy2.h>
#include "Level.h"


int16_t Level::xOnGrid(int16_t xpos) {
    int16_t result = (xpos / LEVEL_TILE_WIDTH);
    result = max(result, 0);
    result = min(result, LEVEL_WIDTH_IN_TILES);
    return result;
}
int16_t Level::yOnGrid(int16_t ypos) {
    int16_t result = (ypos / LEVEL_TILE_HEIGHT);
    result = max(result, 0);
    result = min(result, LEVEL_WIDTH_IN_TILES);
    return result;
}

bool Level::isIndexOutOfBoundsAtTileX(int16_t xIndex) {
    return ((xIndex < 0) || (LEVEL_WIDTH_IN_TILES <= xIndex));
}
bool Level::isIndexOutOfBoundsAtTileY(int16_t yIndex) {
    return ((yIndex < 0) || (LEVEL_HEIGHT_IN_TILES <= yIndex));
}
bool Level::isTileOutOfBoundsAtTile(int16_t xIndex, int16_t yIndex) {
    return (isIndexOutOfBoundsAtTileX(xIndex) || isIndexOutOfBoundsAtTileY(yIndex));
}

uint8_t Level::getTileAtTile(int16_t xIndex, int16_t yIndex) {
    if (Level::isTileOutOfBoundsAtTile(xIndex, yIndex)) {
        return static_cast<uint8_t>(TileType::solid);
    }
    return pgm_read_byte(&(levelData[LEVEL_WIDTH_IN_TILES * yIndex + xIndex]));
}
uint8_t Level::getTileAtPixel(int16_t xpos, int16_t ypos) {
    int16_t xIndex = xpos/LEVEL_TILE_WIDTH;
    int16_t yIndex = ypos/LEVEL_TILE_WIDTH;
    return Level::getTileAtTile(xIndex, yIndex);
}

By the way is there a built-in clamp function like there is for min() and max() ?


(Pharap) #211

Crashes how exactly? Freezes up or resets?

Because you’re only declaring it in the header and you’re defining it in a .cpp file.

inline is only needed if you’re defining it in the header.
(For such sort functions, it might be easier to just define it in the header.)

Probably not.

min and max aren’t actually functions in this case, Arduino (rather stupidly) defines them as macros.

(The standard library added one in C++17.)

You can write your own though.

There’s several different ways to write it.
One way is like this:

template< typename ValueType, typename MinType, typename MaxType >
ValueType clamp(ValueType value, MinType minimum, MaxType maximum)
{
	return (value < minimum) ? minimum : (value > maximum) ? maximum : value;
}

Which will allow you to write code with mixed types like this:

int a = 1;
char b = 2;
long c = 3;
long long d = clamp(a, b, c);

It’s easier to get away with this on the Arduboy, but in a desktop program this would be bad because the objects are passed by value, which would mean larger types would end up being copied more than neccessary.

A better version would be:

template< typename Type >
const Type & clamp(const Type & value, const Type & minimum, const Type & maximum)
{
	return (value < minimum) ? minimum : (value > maximum) ? maximum : value;
}

This is better because it’s just the references that get copied.
The object isn’t copied until the final assignment,
and even then you can avoid the copy by using a reference:

int minimum = 0;
int maximum = 5;
int value = 10;

const int & result = clamp(value, minimum, maximum);
// result now refers to maximum
maximum = 15;
// result is now 15 as well, because it's a reference to maximum

But because of the single-type restriction, it forces you to specify the template argument in some cases:

int a = 1;
char b = 2;
long c = 3;
long long d = clamp<long>(a, b, c);

#212

Freezes

And if I go to the left of my level my view object stops drawing the tiles, but the game continue fine and the player gets drawn. It isn’t a big deal because the view is clamped to the level. Also, I’m fixing 10 thousand other bugs right one, maybe the crash was a “compound bug” and when I fix the other ones, it’ll be gone. (it crashes at an exact distance away from the level)

By the way an interesting note, when I go outside the level to the right, the level loops back on itself, with an offset of 1 tile on the Y axis.

Insert SpongeBob meme picture: two thousand years later…

I finally managed to not get stuck on the walls. I don’t usually struggle with collision detection, but today, apparently yes.

Insert the other meme thing: I don’t usually struggle with collisions, but when I do, it takes two thousand years to fix.

I’ve been using the this-> pretty much everywhere and I absolutely cannot stand it, but I got an idea. My “global” object idea I got earlier, all I got to do is use a namespace. could be ‘game’, could be “global”, but if I stick to that system, I shouldn’t ever get problems from not using this->. The exception might be when passing arguments of the same name as member variables, but that’s easily avoided; using a different parameter name


#213

I’ll need to work on my collisions a bit more, it’s not a 1:1 perfect copy of what I made in GameMaker (although it’s very close)

But the good news is, by using a state machine and more-or-less organized code, it won’t be too hard to debug anything


#214

I’ll be going to bed now but here’s the first semi-playable version. By semi-playable I mean you can float to the ceiling and get stuck in it.

I suspect the while loop with the detectWall method of my player to be the cause, which is this:

void Player::move() {
    if (this->detectWall(x+xvel, y)) {
        int8_t sign = velSign(xvel);
        int8_t dist = ceilFixed(absFixed(xvel)).getInteger();
        int8_t count = 0;
        xvel = 0;
        while(count < dist && !this->detectWall(x+sign, y)) {
            x+=sign;
            count++;
        }
    } else {
        x+=xvel;
    }
    if (this->detectWall(x, y+yvel)) {
        int8_t sign = velSign(yvel);
        int8_t dist = ceilFixed(absFixed(yvel)).getInteger();
        int8_t count = 0;
        yvel = 0;
        while(count < dist && !this->detectWall(x, y+sign)) {
            y+=sign;
            count++;
        }
    } else {
        y+=yvel;
    }
}

The reason I think it’s the cause is because I had to change it to make it work. I had to add the coun < dist bit, otherwise it would either freeze or throw you far away. In GameMaker it isn’t there because it simply works without, so I either made a mistake somewhere, or the fixed points get interpreted differently (I don’t think that would be it)

Anyway the rest of the code is below if you want to take a look at it or just play it a bit. Tomorrow I’ll continue

Edit: now that I think of it unless you see something blatantly obvious, don’t worry about my collision code. I want to rewrite it anyway for different reasons, so it’ll be different tomorrow


(Pharap) #215

Freezing usually implies getting stuck in a loop somewhere.

That might change someday.

I used to insist on making all my functions pascal case because that’s what I was used to in C#, and I used to hate underscores,
but now I’m used to having camel-cased function names and I’m not adverse to using underscores.

(I still hate non-Allman brace styles though.)

State machines are glorious things.

In all honesty it’s possible.
I think I remember getting problems with the non-common fixed point types before, but I can’t remember exactly.

I’ll skim it and try to resist the urge to bombard you with suggestions.

(I’m already struggling…)


#216

Do your fixed points use a recursive function or any loop?

Even without my loops it’s still happens, so I think we found the exception to the rule

It’s also possible that it isn’t crashing but that movement stops working.

Hum. Well I’ll be testing it now. I’ll add a Testing state to my player where I move either 1 pixels or 1/16 pixels depending on whether I’m pressing B. I’ll make sure the collision detection works well. If I don’t get stuck anywhere, we’ll now it’s probably not the fixed points. (it could still be a bug from implicit casting or biased rounding, so the next step will be to play with how I get the X and Y for the collisions)

Just to make sure, getInteger is floored first, right?

I don’t follow all your recommendations but I always keep them in mind. If you want to rewrite the whole thing when I’m done so that I can see different approaches, that’d actually be great. The game (without the levels) should be ready within the next 7 days

I made a lot of platformer prototypes in the past, it is now paying off because I knew what I was doing

Edit: no matching function for call to ‘floorFixed(SFixed<11u, 4u>::IntegerType)’
Do I need to overload floorFixed with a custom function myself for my SQ11x4 type?
EDitOfMyEdit: I just had to change the paranteses brackets?)
return (Level::detectWall(floorFixed(xpos).getInteger()-4, floorFixed(ypos).getInteger()-4));

EDIT SOLVED!! getInteger doesn’t seem to floor the value. I absolutely need it to be floored otherwise it won’t work properly

Therefore this works:
bool Player::detectWall(SQ11x4 xpos, SQ11x4 ypos) {
return (Level::detectWall(floorFixed(xpos).getInteger()-4, floorFixed(ypos).getInteger()-4));
}
This doesn’t:
bool Player::detectWall(SQ11x4 xpos, SQ11x4 ypos) {
return (Level::detectWall(xpos.getInteger()-4, ypos.getInteger()-4));
}
I don’t know if that’s your intended behavior. Personally I’d make getInteger return a floored value, but that’s just me.

Now I just need to figure out why my player randomly starts flying. Probably a problem with isOnGround()

Edit Okay, this isn’t making any sense. I put back the buggy detectWall code, but the bug didn’t come back. (or maybe I had a different version uploaded on my Arduboy yesterday night, and then fixed it right after? o.o…)

Other edit: I made 5 8x8 sprites, R, W, F, B and D which is the first letter of each state. That’ll make debugging easier


(Pharap) #217

As far as I’m aware I don’t.

If GitHub’s search function is telling the truth then I’m not using any while or for loops, and I think it would be unlikely that I’m using recursion because as a general rule I avoid recursion in C++ because it’s inefficient.

Which loops?

That’s also plausible.

It’s truncated - the fractional part is completely discarded, leaving only the integer part, which should be equivalent to flooring.

It returns the bits that represent the integer part,
the fractional part is shifted out completely.

return (static_cast<IntegerType>(this->value >> IntegerShift) & IntegerMask) | ((this->value < 0) ? ~IntegerMask : 0);

I probably wouldn’t rewrite the whole thing, but there are quite a few changes I’d like to make.

For example, velClamp and velSign would make more sense as standalone utility functions instead of being private member functions of Player.
(Also they’d make more sense as just clamp and sign/signOf.)

What are you trying to do when you get this error?

The error itself literally means you’re trying to pass an integer type to floorFixed, which I’m guessing means you wrote floorFixed(xpos.getInteger()) or similar.

I’m going to test this.

Edit
I tested:

#include <Arduboy2.h>
#include <FixedPoints.h>

Arduboy2 arduboy;

void setup(void)
{
	arduboy.begin();
}

using SQ11x4 = SFixed<11, 4>;

void loop(void)
{
	if(!arduboy.nextFrame())
		return;

	arduboy.pollButtons();

	arduboy.clear();
	
	{
		SQ11x4 a = 45.45;
		
		arduboy.println(a.getInteger());
		arduboy.println(floorFixed(a).getInteger());
		arduboy.println(static_cast<float>(a));
		arduboy.println(static_cast<float>(floorFixed(a)));
	}
	
	{
		SQ11x4 a = -45.45;
		
		arduboy.println(a.getInteger());
		arduboy.println(floorFixed(a).getInteger());
		arduboy.println(static_cast<float>(a));
		arduboy.println(static_cast<float>(floorFixed(a)));
	}
	
	arduboy.display();
}

Output:

45
45
45.44
45.00
-46
-46
-45.44
-46.00

Which is exactly what it should be.

I have had problems in the past where new games weren’t uploading properly, it looked like it was uploading but the old game didn’t go.

Why not use arduboy.print('R')?


#218

Yeah I have this planned, I just couldn’t bother when I made them. It’s important but not urgent
Clamp would have to have a version of itself for different types, with velCLamp I only had to worry about one

I have a bug in my state machine, still trying to fix it. Collisions seem to work fine however, except in the middle of the level where everything is apparently considered solid, but I’ll fix that later

Because I looked for that function and couldn’t find it, and gave up


#219

I am so confused. isOnGround() works perfectly. I decided to run it seperately to check, and even in my walking state, in which the player refuses to fall, isOnGround() works. I might just copy my running code and paste it in my walking state, since they’re almost the same

Seems easier than to find the bug

Edit: I think I found the guilty code… my changeState function.


(Pharap) #220

Not necessarily.

As long as it’s a function (not a macro), you can start with just one set of parameters and add other oveloads as needed.

And even if you added a templated version later on, there would be no ambiguity because the more specific version would take precedence.

I just had a look, it’s missing from the doxygen documentation because the documentation doesn’t cover Arduino itself and print is actually a virtual function that Arduboy2 inherits from the Print class, which is part of the Arduino library.


One of my suggestions would be to fuse your running and walking states into a single state and just make decisions based on the player’s velocity at the time (i.e. by checking isFastEnoughToRun), because at the moment there’s a lot of code duplication between the two states.

When you break it down, it seems that there’s actually not much difference between them:

void Player::executeRunningState()
{	
	if (!isOnGround())
		changeState(PlayerState::Falling);

	if (input.isJumpPressed() && canJump())
		jump(jumpStrengthRunning);
		
	if (isSlowEnoughToWalk() || !input.isRunHeld())
		changeState(PlayerState::Walking);		

	if (input.xInput == 0)
	{
		xvel -= deceleration * velSign(xvel);
	}
	else
	{
		xvel += input.xInput * accel;

		xvel = velClamp(xvel, -velRunMax, velRunMax);
		
		if (input.xInput == -velSign(xvel))
			changeState(PlayerState::Breaking);
	}
	move();
}
void Player::executeWalkingState()
{
	if (!isOnGround())
		changeState(PlayerState::Falling);

	if (canJump() && input.isJumpPressed())
		jump(jumpStrengthWalking);
			
	if (isFastEnoughToRun() && input.isRunHeld())
		changeState(PlayerState::Running);
	
	if (input.xInput == 0)
	{
		xvel -= deceleration * velSign(xvel);
	}
	else
	{
		xvel += input.xInput * accel;

		if (input.isRunHeld())
			xvel = velClamp(xvel, -velRunMax, velRunMax);
		else
			xvel = velClamp(xvel, -velWalkMax, velWalkMax);
	}
	move();
}

Edit
I think this would have the same effect, but I haven’t tested it:

void Player::executeRunningState()
{
	bool isRunning = isFastEnoughToRun() && input.isRunHeld();

	if (!isOnGround())
		changeState(PlayerState::Falling);

	if (input.isJumpPressed() && canJump())
		jump(isRunning ? jumpStrengthRunning : jumpStrengthWalking);	

	if (input.xInput == 0)
	{
		xvel -= deceleration * velSign(xvel);
	}
	else
	{
		xvel += input.xInput * accel;
		
		if (isRunning)
		{
			xvel = velClamp(xvel, -velRunMax, velRunMax);
		}
		else
		{
			xvel = velClamp(xvel, -velWalkMax, velWalkMax);
		
			if (input.xInput == -velSign(xvel))
				changeState(PlayerState::Breaking);
		}
	}

	move();
}

#221

It’s true that there’s a lot of duplicate code with Walking and Running but I have combined similar states in the past and most of the time I ended up paying for it later. More code for less headaches. (although it’s true that isFastEnoughToRun() could take care of things)

GOOD NEWS! The game works pretty much exactly like my PC version, I’ll have to modify a few tiny things, like adding ArdBitmap to allow for sprite mirroring, and a ‘dead’ state. Then there’s gonna be all the polishing stuff, but the game itself is pretty much ready.

I still have to fix the “invisible wall at the end of the level” bug, but otherwise the game is playable

The level is almost the same as the one from my prototype and you can actually play it correctly now :D

You can’t win or die yet, so you might want to wait until later to play, but it’s already getting good. There isn’t a lot of space on the Arduboy so I’ll have to pick between a ton of levels and only walls and background tiles, or go for slightly less levels but having coins and spikes added. If I go for only walls and background, that would be 2 tiles and I could have a ton of levels. 16 bytes per section of 16x8 is very few

If I implement a good score/time limit system, it could be fun enough that coins and spikes aren’t necessary. Sometimes having to race is fun enough.


(Pharap) #222

It depends on what the states are.
It pays to have things like jumping and basic movement split into different states, but you have to ask just how much of a difference is there.
As far as I can see, the only difference between running and walking is the ability to ‘break’ and the speed the player travels at.

Best do some tests to decide whether that’s going to be worth it.
The library itself is quite chunky, so if there’s only a few sprites that need mirroring it can often be easier to just use Sprites's frame parameter.