Help me with C++ for my game


(Scott) #21

Just something to consider before you get too far along:
If you intend your game to have a white background with black sprites/objects, it may make it easier to put

arduboy.invert(true);

in setup() to reverse black/white on the entire display.

Note, though, that the more lit (white) pixels are on the display, the more power it will draw, thus affecting battery life for the game. Being that each pixel is an individual LED, a lit pixel draws power but a black pixel doesn’t.


(Pharap) #24

I decided to have a look, but I can’t find anything definitive on when it was introduced.

Wikipedia claimed GCC has had it since v3.4, which came out in 2004, but looking at the patch notes they already had it before then but it wasn’t working properly with symlinks and hardlinks until v3.4, so users were given a warning.

I can’t be bothered to check all the compilers to see who had it first,
but I’d consider anything prior to C++11 to be ‘quite old’ anyway.
Post C++11 is the era of ‘modern’ C++ in my opinion.
Anything older than C++98 is pre-standardisation and thus ‘ancient’ in C++ terms :P


#25

I changed the title as suggested by MLXXXp

Okay, I have no bugs but I need help organizing my code.

Main ino file:

// Sam Sibbens
// 2018 NOVEMBER 01
// Unnamed project

#include <Arduboy2.h>
Arduboy2 arduboy;

#include "Entity.h"
#include "my_sprites.h"

#define MAX_ENTITY_COUNT 10
Entity entities[MAX_ENTITY_COUNT];
Entity& player = entities[0];


/* FUNCTIONS DECLARED AND DEFINED IN THIS FILE */
void draw_entities();


void setup() {
    arduboy.begin();
    arduboy.clear();
    arduboy.setFrameRate(30);
    player.type = 1;
}
void loop() {

    //Prevent the Arduboy from running too fast
    if(!arduboy.nextFrame()) {return;}
  
    arduboy.clear();
    uint8_t playerspeed = 1;
    if(arduboy.pressed(LEFT_BUTTON)) {
        player.x = player.x - playerspeed;
    }
    if(arduboy.pressed(RIGHT_BUTTON)) {
        player.x = player.x + playerspeed;
    }
    if(arduboy.pressed(UP_BUTTON)) {
        player.y = player.y - playerspeed;
    }
    if(arduboy.pressed(DOWN_BUTTON)) {
        player.y = player.y + playerspeed;
    }

    arduboy.fillScreen(WHITE);
    draw_entities();
    arduboy.display();
}


/**** FUNCTIONS ****/
void draw_entities() {
    for(uint8_t i = 0; i < MAX_ENTITY_COUNT; i++) {
        draw_entity(entities[i]);
    }
}

Entity.h

#pragma once

#include <Arduboy2.h>

struct Entity
{
    uint8_t x = 0;
    uint8_t y = 0;
    uint8_t type = 0;
    uint8_t damage_taken = 0;
    uint8_t state = 0;
    uint8_t facing = 0;
};

my_sprites.h

#pragma once

#include <Arduboy2.h>

/*** HUMANOID SPRITES ***/
// Humanoid (general sprites) //
const unsigned char spr_human_outline[] PROGMEM  = {
    0x3f, 0xe1, 0xad, 0xa1, 0xa1, 0xad, 0xe1, 0x3f,
};
const unsigned char spr_human[] PROGMEM  = {
    0x00, 0x1e, 0x5e, 0x5e, 0x5e, 0x5e, 0x1e, 0x00,
};

// Player Faces //
const unsigned char spr_player_down[] PROGMEM  = {
    0x3f, 0xe1, 0xad, 0xa1, 0xa1, 0xad, 0xe1, 0x3f,
};
const unsigned char spr_player_right[] PROGMEM  = {
    0x00, 0x1e, 0x5e, 0x52, 0x5e, 0x52, 0x1e, 0x00, 
};
const unsigned char spr_player_left[] PROGMEM  = {
    0x00, 0x1e, 0x52, 0x5e, 0x52, 0x5e, 0x1e, 0x00, 
};

enum Entity_id {
    entNOONE,
    entPLAYER,
    entSWORD,
};

extern Arduboy2 arduboy;

inline void draw_entity(Entity& ent) {
    uint8_t x = ent.x;
    uint8_t y = ent.y;
    switch(ent.type){
        case entPLAYER:
            arduboy.drawBitmap(x, y, spr_human_outline, 8, 8, BLACK);
            arduboy.drawBitmap(x, y, spr_human, 8, 8, WHITE);
            break;
        default:
            break;
    }
}

I was going for a non-OOP approach by using a simple struct and using global functions + switches to update different entity types and drawing them as well. The code is very rapidly getting messy

For example it’d be much easier to pass in a number, pointer or something that references the correct “Face” sprite instead of having 4 different switch cases depending on the direction the player or the enemy is facing. This reason I went non-OOP is because a) I don’t fully understand C++ classes yet and b) because I think I can’t have an array of different entity types (different methods, ifferent classes etc)

How can I improve all this?


(Matt) #26

Honestly, I recommend just keep going. Your first game will probably end up with things you’re not happy with, but you’ll also naturally learn a lot. If you get too caught up in analysis paralysis, you’ll probably not finish the game and that’s far worse.

My first arduboy game had things that worked out well, and other things that were hacked in and bad. I’ve started game #2 and I’m applying what I learned.


(Matt) #27

And just to be clear, I’m not trying to say “don’t ask for help”, everyone here is happy to help. But I feel like architecture and code layout is more something that you just need to get a feel for.

This is true for the most part. There are ways around it:

  • use dynamic memory. Have an array of entity pointers and new up different entity types as needed, and use virtual dispatch on them. Not recommended normally, as dynamic memory is generally not recommended. Although this is probably the most common approach if you’re writing a C++ game on say a PC.
  • have one entity type and use switch statements, which is basically what you are doing. Not the most elegant but hey if it works, there’s nothing wrong with that.
  • build your own “poor man’s virtual dispatch”, which is what I did in Ardynia. I had an array of Entity, and then I had different functions for different entity types. So I had Blob::render() and Skeleton::render(), and then I’d give an Entity object a function pointer to these functions.
  • have different arrays for each entity type. So one array of wolves, another array of snakes, whatever it may be. This is the approach I’m taking in my second game because I really only have two entities.

#28

I’m entirely new to C++ so code layout and even syntax and keywords are my weakness right now, so I assume I’m gonna miss out on obvious things

That seems like the best advice, thank you! And like you said, although switch statements aren’t necessarely the most elegant thing, they do work anyway


(Matt) #29

Switch statements are often a fine solution.

You might find you’re starting to run out of space (that 28k goes fast), and if that’s the case there are some clever ways to avoid switch statements that take up less space using PROGMEM arrays, but I recommend not worrying about this kind of stuff until you’re running out of room.


(Pharap) #30

I partly agree that for now you probably shouldn’t worry too much about organisation and just focus on writing code and learning things, however…

My first piece of advice is to read about scoped enumerations (enum class).
Then you can do something like:

enum class Direction : uint8_t
{
	North = 0, East = 1, South = 2, West = 3,
};

My second piece of advice, now you’ve updated to Arduboy2 is to learn how to use the Sprites class.
Its drawing functions are far more useful than the standard drawing functions.
It supports ‘frames’ and masking (a way of doing transparency).

I’ve already written a few good explanations about how frames work:


And for masking:




Combining enum class and Sprites will result in some much cleaner code:

enum class Direction : uint8_t
{
	North,
	East,
	South,
	West,
};

enum class EntityType : uint8_t
{
	None,
	Player,
	Sword,
};

struct Entity
{
	uint8_t x = 0;
	uint8_t y = 0;
	EntityType type = EntityType::None;
	Direction direction = Direction::North;
};

const uint8_t * getImageFor(EntityType type)
{
	switch(type)
	{
		case EntityType::Player:
			return playerImage;
		case EntityType::Sword:
			return swordImage;
		default:
			return nullptr;
	}
}

inline void drawEntity(const Entity & entity)
{
	uint8_t x = entity.x;
	uint8_t y = entity.y;
	EntityType type = entity.type;
	Direction direction = entity.direction;
	
	const uint8_t * image = getImageFor(type);
	uint8_t frame = static_cast<uint8_t>(direction);

	Sprites::drawPlusMask(x, y, image, frame);
}

(There’s also a way of doing getImageFor without a switch that could result in smaller code, but it’s more advanced and it involves more complex pointer use, so it’s probably better to ask me about that either when you’re comfortable with pointers or you’re actually running out of space.)


Some further advice:

Don’t use defines for constants, use constexpr variables:

constexpr uint8_t MaxEntityCount = 10;

(There’s a related SO answer here, but it predates constexpr. As of C++11 constexpr is better than const for constant values.)

Consider using compound assignment operators, e.g. player.x += playerspeed;

You can, but you have to use pointers and virtual functions chew up a lot of memory because of how they work internally (which is quite a complicated and advanced topic).

(Spoiler: structs are classes anyway.)

https://en.cppreference.com/w/cpp/keyword


I forgot to mention this resource earlier: https://isocpp.org/faq

It’s very good for answering some of the big questions, like “What is a class?”, “Why should I care about const correctness?” and “How do virtual functions really work?”.
(Also it’s oddly humourous at times considering it’s published on the C++ standard committee’s website.)


#31

All this really makes me feel like a complete beginner. For context here’s what I made in 2 days as a prototype for what I want to make on the Arduboy: https://theprogrammer163.itch.io/arduboy-hack-and-slash-adventure?password=arduboy

Here’s something else that I made to learn a bit of pixel art: https://www.youtube.com/watch?v=nFaTDJjMcoo
Playing with inverse kinematics: https://www.youtube.com/watch?v=3kICkNOSlP4

I underestimated the difficulty of learning C++

Thanks for all the explanations and resources by the way. I do pay attention to what you say it just takes me a bit of time to understand it and apply it, so I read it multiple times and change one thing at a time


(Matt) #32

That looks great! What did you build the prototype with? (EDIT: ah I see, gamemaker)

C++ is a pretty tough language. But just keep at it, you’ll get it.


#33

Thank you! I actually learned how to do auto-tiling making this (the black walls). It’s also my first decent sword animation

Thank you. Maybe I just need to do some art and work on the HTML5 version while I learn C++, it might keep me more motivated


#34

I get a bugs with enum class

exit status 1
could not convert ‘Player’ from ‘EntityType’ to ‘int’

I tried enum classes before and had the same problem (didn’t bring it up before because I changed it back to just enum)

Edit: my code

Main iono

// Sam Sibbens
// 2018 NOVEMBER 01
// Unnamed project

#include <Arduboy2.h>
Arduboy2 arduboy;

#include "Entity.h"
#include "my_sprites.h"

constexpr uint8_t MAX_ENTITY_COUNT = 10;
Entity entities[MAX_ENTITY_COUNT];
Entity& player = entities[0];


/* FUNCTIONS DECLARED AND DEFINED IN THIS FILE */
void draw_entities();


void setup() {
    arduboy.begin();
    arduboy.clear();
    arduboy.setFrameRate(30);
    player.type = EntityType::None;
}
void loop() {

    //Prevent the Arduboy from running too fast
    if(!arduboy.nextFrame()) {return;}
  
    arduboy.clear();
    uint8_t playerspeed = 1;
    if(arduboy.pressed(LEFT_BUTTON)) {
        player.x = player.x - playerspeed;
    }
    if(arduboy.pressed(RIGHT_BUTTON)) {
        player.x = player.x + playerspeed;
    }
    if(arduboy.pressed(UP_BUTTON)) {
        player.y = player.y - playerspeed;
    }
    if(arduboy.pressed(DOWN_BUTTON)) {
        player.y = player.y + playerspeed;
    }

    arduboy.fillScreen(WHITE);
    draw_entities();
    arduboy.display();
}


/**** FUNCTIONS ****/
void draw_entities() {
    for(uint8_t i = 0; i < MAX_ENTITY_COUNT; i++) {
        draw_entity(entities[i]);
    }
}

Entity.h

#pragma once

#include <Arduboy2.h>

struct Entity
{
    uint8_t x = 0;
    uint8_t y = 0;
    uint8_t type = 0;
    uint8_t damage_taken = 0;
    uint8_t state = 0;
    uint8_t facing = 0;
};

my_sprites.h is where there’s the bug

#pragma once

#include <Arduboy2.h>

/*** HUMANOID SPRITES ***/
// Humanoid (general sprites) //
const unsigned char spr_human_outline[] PROGMEM  = {
    0x3f, 0xe1, 0xad, 0xa1, 0xa1, 0xad, 0xe1, 0x3f,
};
const unsigned char spr_human[] PROGMEM  = {
    0x00, 0x1e, 0x5e, 0x5e, 0x5e, 0x5e, 0x1e, 0x00,
};

// Player Faces //
const unsigned char spr_player_down[] PROGMEM  = {
    0x3f, 0xe1, 0xad, 0xa1, 0xa1, 0xad, 0xe1, 0x3f,
};
const unsigned char spr_player_right[] PROGMEM  = {
    0x00, 0x1e, 0x5e, 0x52, 0x5e, 0x52, 0x1e, 0x00, 
};
const unsigned char spr_player_left[] PROGMEM  = {
    0x00, 0x1e, 0x52, 0x5e, 0x52, 0x5e, 0x1e, 0x00, 
};

enum class EntityType : uint8_t {
    None,
    Player,
    Sword,
};
enum class Dir : uint8_t {
    Down,
    Right,
    Left,
    Up,
};

extern Arduboy2 arduboy;

inline void draw_entity(Entity& ent) {
    uint8_t x = ent.x;
    uint8_t y = ent.y;
    switch(ent.type){
        case EntityType::Player: // <--- BUG HERE
            arduboy.drawBitmap(x, y, spr_human_outline, 8, 8, BLACK);
            arduboy.drawBitmap(x, y, spr_human, 8, 8, WHITE);
            break;
        default:
            break;
    }
}

(Matt) #35

try this

struct Entity
{
    uint8_t x = 0;
    uint8_t y = 0;
    EntityType type = EntityType::None;
    uint8_t damage_taken = 0;
    uint8_t state = 0;
    uint8_t facing = 0;
};

You’ll need to include my_sprites.h in entity.h

Also, are you comfortable with git? If you can put your code on github, then other people can download and compile it/play with it. For example, I could try what I just suggested to you just to confirm I didn’t miss anything.


#36

I tried learning git a while back and I could “more or less” use it (as in I wouldn’t have been able to roll back to older versions or reverse changes)

I agree that it’d be very useful but it’s hard for me to learn things that have a lot of different information (it doesn’t have to do with the complexity, but the quantity)

I did what you said, I had to include my_sprites.h in entity.h and entity.h in my_sprites.h (I’m confused as to why I can’t just include everything in the main ino file)

I get the bug I used to have:

exit status 1
variable or field ‘draw_entity’ declared void

Main ino

// Sam Sibbens
// 2018 NOVEMBER 01
// Unnamed project

#include <Arduboy2.h>
Arduboy2 arduboy;

#include "Entity.h"
#include "my_sprites.h"

constexpr uint8_t MAX_ENTITY_COUNT = 10;
Entity entities[MAX_ENTITY_COUNT];
Entity& player = entities[0];


/* FUNCTIONS DECLARED AND DEFINED IN THIS FILE */
void draw_entities();


void setup() {
    arduboy.begin();
    arduboy.clear();
    arduboy.setFrameRate(30);
    player.type = EntityType::None;
}
void loop() {

    //Prevent the Arduboy from running too fast
    if(!arduboy.nextFrame()) {return;}
  
    arduboy.clear();
    uint8_t playerspeed = 1;
    if(arduboy.pressed(LEFT_BUTTON)) {
        player.x = player.x - playerspeed;
    }
    if(arduboy.pressed(RIGHT_BUTTON)) {
        player.x = player.x + playerspeed;
    }
    if(arduboy.pressed(UP_BUTTON)) {
        player.y = player.y - playerspeed;
    }
    if(arduboy.pressed(DOWN_BUTTON)) {
        player.y = player.y + playerspeed;
    }

    arduboy.fillScreen(WHITE);
    draw_entities();
    arduboy.display();
}


/**** FUNCTIONS ****/
void draw_entities() {
    for(uint8_t i = 0; i < MAX_ENTITY_COUNT; i++) {
        draw_entity(entities[i]);
    }
}

my_sprites.h

#pragma once

#include <Arduboy2.h>
#include "Entity.h"

/*** HUMANOID SPRITES ***/
// Humanoid (general sprites) //
const unsigned char spr_human_outline[] PROGMEM  = {
    0x3f, 0xe1, 0xad, 0xa1, 0xa1, 0xad, 0xe1, 0x3f,
};
const unsigned char spr_human[] PROGMEM  = {
    0x00, 0x1e, 0x5e, 0x5e, 0x5e, 0x5e, 0x1e, 0x00,
};

// Player Faces //
const unsigned char spr_player_down[] PROGMEM  = {
    0x3f, 0xe1, 0xad, 0xa1, 0xa1, 0xad, 0xe1, 0x3f,
};
const unsigned char spr_player_right[] PROGMEM  = {
    0x00, 0x1e, 0x5e, 0x52, 0x5e, 0x52, 0x1e, 0x00, 
};
const unsigned char spr_player_left[] PROGMEM  = {
    0x00, 0x1e, 0x52, 0x5e, 0x52, 0x5e, 0x1e, 0x00, 
};

enum EntityType : uint8_t {
    None,
    Player,
    Sword,
};
enum class Dir : uint8_t {
    Down,
    Right,
    Left,
    Up,
};

extern Arduboy2 arduboy;

inline void draw_entity(Entity& ent) {
    uint8_t x = ent.x;
    uint8_t y = ent.y;
    switch(ent.type){
        case EntityType::Player:
            arduboy.drawBitmap(x, y, spr_human_outline, 8, 8, BLACK);
            arduboy.drawBitmap(x, y, spr_human, 8, 8, WHITE);
            break;
        default:
            break;
    }
}

Entity.h

#pragma once

#include <Arduboy2.h>
#include "my_sprites.h"

struct Entity
{
    uint8_t x = 0;
    uint8_t y = 0;
    EntityType type = EntityType::None;
    uint8_t damage_taken = 0;
    uint8_t state = 0;
    uint8_t facing = 0;
};

Yeah I suppose it would save us a lot of copy-pasting if I used git


(Matt) #37

ah yeah, now you have a circular dependency. Like I said, C++ is tough.

Fixing circular dependencies is doable, but hard to explain, here’s a good starting point: https://stackoverflow.com/questions/625799/resolve-build-errors-due-to-circular-dependency-amongst-classes

However, as a short term fix, undo my first suggestion and instead try:

inline void draw_entity(Entity& ent) {
    uint8_t x = ent.x;
    uint8_t y = ent.y;
    switch((EntityType)ent.type){
        case EntityType::Player:
            arduboy.drawBitmap(x, y, spr_human_outline, 8, 8, BLACK);
            arduboy.drawBitmap(x, y, spr_human, 8, 8, WHITE);
            break;
        default:
            break;
    }
}

and @Pharap will cringe at the C style cast, but one thing at a time :slight_smile:


#38

I’m trying to generate a level and I get “invalid type” error

exit status 1
invalid types ‘bool[uint8_t {aka unsigned char}]’ for array subscript

EDIT: I replaced “bool&” with “array&” and it worked. But now I get:

exit status 1
variable or field 'CreateLevel' declared void

World.h

constexpr uint8_t WORLD_WIDTH = 16;
constexpr uint8_t WORLD_HEIGHT = 8;
bool level[WORLD_WIDTH][WORLD_HEIGHT];


inline void CreateLevel(array& level) {

    for(uint8_t x = 0; x < WORLD_WIDTH; x++) {
        for(uint8_t y = 0; y < WORLD_HEIGHT; y++) {
            
            // clear the level
            level[x][y] = false;
            
            // make borders solid
            if (x == 0 || x == WORLD_WIDTH-1 || y == 0 || y == WORLD_HEIGHT-1) {
                level[x][y] = true;
                continue;
            }
            // randomly make some tiles solid
            if ((rand() % 5) <= 1) {
                level[x][y] = true;
            }
        }
    }

}

(Matt) #39

This is a reference to a single bool. You need to pass in a reference to a 2d array of bools (or a pointer).

Are you working through any C++ tutorials or books? I recommend finding one that speaks to you and working through it. Sorry I can’t recommend any, as I learned C++ decades ago.


#40

Yes I figured it out (I remembered that I’m actually passing an array, not a bool)

Unfortunately I edited my comment as you were writing yours, I have the same old bug that keeps coming back:

exit status 1
variable or field ‘CreateLevel’ declared void

Honestly I am thinking about following a C++ video tutorial in a popular c++ engine and coming back to the Arduboy later


(Matt) #41

Crait’s tutorials here in the forum are quite popular

I’ve never read them myself, but they are probably worth a look.


#42

Yes I did his tutorial first, it’s more of an introduction to the Arduboy libraries and the Arduino IDE than it is a C++ introduction (which makes sense)

EDIT: I moved my level creation code to setup() and it worked