Help me with C++ for my game


#43

I’m getting pretty close to having a working level with collision, I’m getting one bug now:

exit status 1
‘is_tile_solid’ was not declared in this scope

So apparently my function isn’t global? Here’s the snippet of that function declaration/definition

inline bool is_tile_solid_at(int16_t x, int16_t y) {
    return level[x][y];
}

main ino:

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

#include <Arduboy2.h>
Arduboy2 arduboy;


#include "my_world.h"
#include "Entity.h"

constexpr uint8_t MAX_ENTITY_COUNT = 10;
Entity entities[MAX_ENTITY_COUNT];

Entity& player = entities[0];
Entity& knight = entities[1];


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


void setup() {
    arduboy.begin();
    arduboy.clear();
    arduboy.setFrameRate(30);
    arduboy.initRandomSeed();
    player.type = entPLAYER;

    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;
            }
        }
    }
}


void loop() {

    //Prevent the Arduboy from running too fast
    if(!arduboy.nextFrame()) {return;}
  
    arduboy.clear();

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


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

Entity.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, 
};


constexpr uint8_t EntityWidth = 8;

enum EntityType {
    entNONE,
    entPLAYER,
    entKNIGHT,
    entSWORD,
};

enum DirType {
    dirDOWN,
    dirRIGHT,
    dirLEFT,
    dirUP,
};

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


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;
    }
}

inline bool detect_wall(int8_t x, int8_t y) {
    extern bool level[WORLD_WIDTH][WORLD_HEIGHT];
    int8_t x1 = x_on_grid(x);
    int8_t x2 = x_on_grid(x+TILE_WIDTH-1);
    int8_t y1 = y_on_grid(y);
    int8_t y2 = y_on_grid(y+TILE_WIDTH-1);
    
    return (is_tile_solid(x1, y1) || is_tile_solid(x2, y1) || is_tile_solid(x2, y2) || is_tile_solid(x1, y2));
}

inline void move_entity(Entity& ent, int8_t xmove, int8_t ymove) {
    int8_t xnew = ent.x + xmove;
    int8_t ynew = ent.y + ymove;
    if(!detect_wall(xnew, ent.y)) {
        ent.x = xnew;
    }
    if(!detect_wall(ent.x, ynew)) {
        ent.y = ynew;
    }
}

inline void update_entity(Entity& ent) {
    switch(ent.type){
        case entPLAYER:
            int8_t xinput = 0;
            int8_t yinput = 0;
            if(arduboy.pressed(LEFT_BUTTON)) {xinput--;}
            if(arduboy.pressed(RIGHT_BUTTON)) {xinput++;}
            if(arduboy.pressed(UP_BUTTON)) {yinput--;}
            if(arduboy.pressed(DOWN_BUTTON)) {yinput++;}
            move_entity(ent, xinput, yinput);
            break;
        default:
            break;
    }
}

my_world.h

#pragma once

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

inline int8_t x_on_grid(int16_t x) {
    x = (x / TILE_WIDTH);
    x = max(x, 0);
    x = min(x, WORLD_WIDTH-1);
    
    return x;
}

inline int8_t y_on_grid(int16_t y) {
    y = (y / TILE_WIDTH);
    y = max(y, 0);
    y = min(y, WORLD_HEIGHT-1);
    
    return y;
}

inline bool is_tile_solid_at(int16_t x, int16_t y) {
    return level[x][y];
}

I tried renaming World.h to my_world.h in case there was a conflict, I tried removing inline in front of is_tile_solid, I tried adding #include “my_world.h” at the top of Entity.h


(Matt) #44

you invoke a function named is_tile_solid but named the function is_tile_solid_at


(Pharap) #45

I’m late to the party again because this stuff always happens around the time I’m eating dinner. :P

Sorry if I end up repeating stuff that’s already been said, I tend to write my response as-and-when I’m reading the comments.


It looks good. (Shame it’s made in Gamemaker though.)

It’s not that difficult to get to the point where you can write things that work, but it’s difficult to master.

The biggest thing that makes it hard is its low level aspects (and some of its more complex rules).
People get used to the modern hand-holding languages like Python, JavaScript, C#, Java etc, which don’t require understanding how the underlying hardware works.

The relative difficulty is justified though.
C++ is far more powerful than other high-level languages and produces much smaller and faster code.

(And frankly it’s still easier than assembly, and safer than C.)

That’s fine. I’ve been doing C++ for about 5-6 years, so it’s not like I learnt all this stuff overnight.

“The journey of a thousand miles begins with a single step.”

That’s because you haven’t changed type to EntityType in your Entity class.

Look again at what I wrote:

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;
};

I’m using EntityType for type, not uint8_t.

The point of an enum class (“scoped enumeration”) is that it’s a new type, it’s not implicitly convertible to int like a plain enum is.

This is a good thing, it improves ‘type safety’.

Thankfully you don’t need to fully understand git to use GitHub.
(If you did, I wouldn’t be using it. Command line git confuses the hell out of me.)

The easiest fix is to put EntityType in the same header as Entity.

Like this:

#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, 
};

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;
    }
}
#pragma once

// For uint8_t
#include <stdint.h>

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

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

Like I say, the important thing to remember about an enum class is that it creates a new type with a special name.

Types are very important in statically typed languages (e.g. C++, C#, Java).

More like cry internally.
C style casts are dangerous because they can cast away const by accident.

80% of the time you only need static_cast, and 19% of the time you need reinterpret_cast.
@SamSibbens isn’t using pointers yet so he won’t need reinterpret_cast, only static_cast.

It shouldn’t work, there is no type called array.

Off the top of my head, there are three ways you can pass an array to a function.
I won’t introduce you to the best way because it involves a thing called ‘templates’, which I don’t think you’re ready to learn yet.
I won’t introduce you to the way that uses pointers because it’s not very safe and you haven’t learnt pointers yet.

So here’s the third option:

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


inline void CreateLevel(bool (&level)[WORLD_WIDTH][WORLD_HEIGHT]) {

    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;
            }
        }
    }

}

Hopefully if you understand references this should make sense.
This passes a reference to an array of the specified size.
If you try to pass an array with a different size, this won’t work anymore, which is why I’m using the constexpr variables for the sizes.

(By the way, when you’ve got everything working, I know a good way to make level use less memory. But best leave that until your game is nearer completion.)

I wish I could recommend one, but I didn’t learn through videos, I learnt through reading a large number of articles, StackOverflow answers and just experimenting - trial and error.

The most important thing is to not give up.
Programming rewards determination and hard work.

https://www.learncpp.com/ is still my favourite tutorial because it includes the more modern language features that make life easier (e.g. constexpr, enum class).

I don’t like them because they teach what I would class as ‘bad habits’.
The techniques might be easier to use than ‘good habits’, but they’re harder to unlearn and they can cause problems later down the line.

Rule 1 of programming: computers are stupid.
They only do what you tell them to.

is_tile_solid’ was not declared in this scope

inline bool is_tile_solid_at(int16_t x, int16_t y)


#46

I didn’t even think to check the spelling. You were right, that’s the whole reason it wasn’t compiling

I supposed you noticed my spelling mistake as well

I’m using the Arduino IDE and it doesn’t have autocomplete/intellisense, should I be using something else?

Edit: I’m talking with someone on Slack about different IDEs


(Pharap) #47

You could use VSCode with the Arduino extension, which lets you compile and upload from VSCode.
It’s not as good as Visual Studio’s intellisense but it does the job.

That’s what I use for Arduboy now, but prior to that I was writing code in Notepad++,
which also has a form of autocomplete, but I had to use the Arduino IDE for compiling.


(That said, personally I think it pays to avoid autocomplete if possible.
I find that if you practise enough without it then eventually you don’t need it.)


#48

That sounds like an interesting solution, I’ll check it out
Ninja edi: I didn’t know you were talking about Microsoft Visual Studio, I installed it 5 minutes ago. Good to know there’s an arduino extension

I like to give very descriptive names so that I don’t need to leave comments, without autocomplete I tend to give shorter and harder to understand variable and function names. (I got that tip from Clean Code, I desperately needed ways to organize my code better in GameMaker Studio)


(Pharap) #49

I’m not, I’m talking about VSCode:

I think Visual Studio also has an Arduino extension but I’ve never tried it.
VSCode is a more lightweight editor that relies on extensions for functionality.

If you’re used to a heavy editor like Visual Studio/Eclipse/Netbeans then moving to something like VSCode can be a bit of a culture shock, but you soon get used to it.
Once you’ve assigned a hotkey for ‘build’ and a hotkey for ‘upload’, you’ll see it’s actually an upgrade from the Arduino ‘IDE’ - it can do everything that ‘IDE’ can do and more.

Even with descriptive names, you should still aim to leave comments to explain roughly how things work.
That said, I know a lot of people who tend not to bother, myself included.

Either way, descriptive names are a good thing.
I aim to always use index instead of idx, always use direction instead of dir, always use position instead of pos etc.

In fact I wrote a pair of articles about code style for the Arduboy magazine:
The first was in issue 10, pages 34-37 (inclusive) and the second was in issue 11 pages 20-24 (inclusive).

I greatly dislike GameMaker, I had to use it for a few months back in college.
I’ve heard some horror stories about the language’s ‘quirks’ that make me cringe in horror.
At least C++'s more obscure rules tend to have a justification behind them, but GML’s quirks tend to just be a result of poor design.


If you want to make desktop games whilst learning C++, I recommend SDL2.
It’s a C API rather than a C++ API, so you’ll have to learn pointers and do your own memory management, but it’s relatively easy to use and there’s a half-decent tutorial for it (called ‘lazyfoo’).

The only downside is that it can be a pain to set up, but luckily after the nth time of one of my friends complaining about how hard it is to set up, I decided to write a Visual Studio project template for it:


(Note that the .dlls were compiled on an x64 system, so if you’ve got a 32-bit OS or a different CPU then you might need to replace them.)


#50

I don’t know when that was but an important thing to note is that GameMaker improved a lot (If you were using GameMaker 8 for example, there’s been a lot that changed)

Still no real functions. At the very least it’d be great to define scripts like you would a function; all in the same file as text. You can use groups and subgroups which are basically folders and sub-folders, but it’s not the same (making a lights and shadow system using this is annoying, implementing a verlet integration physics system with this is annoying, anything really complex is annoying to make)

I found you on github comments about making Visual Studio Code work with Arduino… you’re everywhere!

I spent an hour on this, I was gonna ask for help but I figured it out. I added the paths manually.

It said that unsigned char wasn’t compatible with uint8_t for sprites (from the Arduboy2 library), but with the arduino IDE it never was an issue. Should I change unsigned char to uint8_t for sprites ?

I’m also getting a lot of errors related to the PROGMEM (saying a semicolon was missing) and also a bunch of errors related to attribute in stdint.h which I recognised as the file defining various types including unsigned char as a uint8_t. Visual Code suggestion was “disable error squiggles” which I did

Now I’m getting no error (but maybe I disabled them completely?)

I’ll be taking a break, I’ve been setting up my PC for programming since I woke up and I haven’t even programmed yet


(Pharap) #51

This is why I prefer things like Love2D. Instead of trying to invent their own language, they used a language designed for embedding into other programs - Lua - and just exposed a bunch of useful functions.

It’s a very easy language to learn as well.

Yeah, occaisionally I get annoyed enough about something to get involved in resolving it, either by discussing the issue in an issue section or sometimes even making a PR.

(For example, I’ve been trying to get placement new (an advanced C++ feature) added to the Arduino library. I’d like some other features too, like a fix to the annoying EEPROM warning, but they’re already snowed under, so I can wait.)

I think you might have done.

Just to check, did you grab this Arduino extension?:
https://marketplace.visualstudio.com/items?itemName=vsciot-vscode.vscode-arduino

If not, that’s the one I’m using. It needs the Arduino IDE already installed.
I can’t remember if you have to tell it where your Arduino folder is or whether it can automagically detect it.

Also I recommend this extension for better C++ code formatting:
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools

Fair enough, rome wasn’t built in a day.


#52

I love Love2D. I actually started programming with this, but as a beginner and with the very few video tutorials on Love2D, it was too difficult for me. I actually stopped for 6 months (but the passion for creating games didn’t go away… so here I am a few years later with more experience and now learning C++)

I probably will try Love2D again soon, for anything more complex than average (I still like GameMaker, especially Studio 2 but for complex things… nope)

Anyway, no bugs but I have tole collisions working right so I thought I’d throw the code out there, might as well throw the bugless code too once in a while when I have that. For now, you can slowly move around and not bug through walls (but you can spawn in one, that’s less cool)

By the way I am curious, how many 8x8 sprites should I expect to be able to have in a game?

Main ino file

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

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


#include "my_world.h"
#include "Entity.h"

constexpr uint8_t MAX_ENTITY_COUNT = 10;
Entity entities[MAX_ENTITY_COUNT];

Entity& player = entities[0];
Entity& knight = entities[1];

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


void setup() {
    arduboy.begin();
    arduboy.clear();
    arduboy.setFrameRate(30);
    arduboy.initRandomSeed();
    player.type = entPLAYER;
    player.x = 64 - 4;
    player.y = 16 - 4;

    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) <= 0) {level[x][y] = true;}
        }
    }
}


void loop() {

    //Prevent the Arduboy from running too fast
    if(!arduboy.nextFrame()) {return;}
  
    arduboy.clear();

    arduboy.fillScreen(WHITE);
    draw_walls();
    update_entities();
    arduboy.display();
}


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

Entity.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, 
};


/*** WORLD SPRITES ***/
const unsigned char spr_wall[] PROGMEM  = {
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
};


constexpr uint8_t EntityWidth = 8;

enum EntityType {
    entNONE,
    entPLAYER,
    entKNIGHT,
    entSWORD,
};

enum DirType {
    dirDOWN,
    dirRIGHT,
    dirLEFT,
    dirUP,
};

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


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;
    }
}

inline bool detect_wall(int8_t x, int8_t y) {
    extern bool level[WORLD_WIDTH][WORLD_HEIGHT];
    int8_t x1 = x_on_grid(x);
    int8_t x2 = x_on_grid(x+TILE_WIDTH-1);
    int8_t y1 = y_on_grid(y);
    int8_t y2 = y_on_grid(y+TILE_WIDTH-1);
    
    return ((is_tile_solid(x1, y1) || is_tile_solid(x2, y1) || is_tile_solid(x2, y2) || is_tile_solid(x1, y2)));
}

inline void draw_walls() {
    for(uint8_t x = 0; x < WORLD_WIDTH; x++) {
        for(uint8_t y = 0; y < WORLD_HEIGHT; y++) {
            if (is_tile_solid(x, y)) {
                //arduboy.drawRect(x*TILE_WIDTH, y*TILE_WIDTH, 8, 8, WHITE);
                arduboy.drawBitmap(x*TILE_WIDTH, y*TILE_WIDTH, spr_wall, 8, 8, BLACK);
            }
        }
    }
}

inline void move_entity(Entity& ent, int8_t xmove, int8_t ymove) {
    int8_t xnew = ent.x + xmove;
    int8_t ynew = ent.y + ymove;
    if(!detect_wall(xnew, ent.y)) {
        ent.x = xnew;
    }
    if(!detect_wall(ent.x, ynew)) {
        ent.y = ynew;
    }
}

inline void update_entity(Entity& ent) {
    switch(ent.type){
        case entPLAYER:
            int8_t xinput = 0;
            int8_t yinput = 0;
            if(arduboy.pressed(LEFT_BUTTON)) {xinput--;}
            if(arduboy.pressed(RIGHT_BUTTON)) {xinput++;}
            if(arduboy.pressed(UP_BUTTON)) {yinput--;}
            if(arduboy.pressed(DOWN_BUTTON)) {yinput++;}
            move_entity(ent, xinput, yinput);
            break;
        default:
            break;
    }
}

my_world.h

#pragma once

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

inline int8_t x_on_grid(int16_t x) {
x = (x / TILE_WIDTH);
x = max(x, 0);
x = min(x, WORLD_WIDTH-1);

return x;
}

inline int8_t y_on_grid(int16_t y) {
y = (y / TILE_WIDTH);
y = max(y, 0);
y = min(y, WORLD_HEIGHT-1);

return y;
}

inline bool is_tile_solid(int16_t x, int16_t y) {
return level[x][y];
}

(Pharap) #53

Does that mean video tutorials those are your primary method of learning and you don’t like written tutorials?

If you do, I suggest starting with just learning Lua first and then doing Love2D.
If you install Lua command line and configure it to be on your %PATH% (assuming you’re using Windows, I don’t know what the Mac equivalent is) then you can use it to run Lua scripts.

You could start with basic things like ‘guess the number’ and reinventing the file copy command.

Aassuming you have experience with making command line programs that is. If you don’t, that’s worth learning about.
You don’t have to be a Linux die-hard to benefit from the odd command line tool.
(For example, I wrote a .bat file that uses ffmpeg to convert any compatible files dropped onto it into .mp3 files.)

I originally learnt Lua through a Minecraft mod called Computercraft.
I don’t know if it’s still kept up to date, but it was a fun mod.

Anyway…

‘tole’?

It depends on whether you have transparency and which image format you use.

One 8x8 sprite in Sprites format with no mask uses 10 bytes - 8 bytes for the image, 1 byte each for the width and height.
You can use the ‘frames’ feature to pack all your images into one image block, in which case it would be (imageCount * 8) + 2 bytes, but most people don’t do that.
Most people keep the images separate and use the frames feature for animation frames, as intended.

You have 28672 bytes of PROGMEM available, most of which will be eaten by your code.
Ultimately it varies, but it’s best to not worry about it until you start getting near the memory limit - then you can start learning about memory saving techniques.

Glad to see some comments, that’s something I don’t do enough. :P

A small tip: arduboy.begin() already clears the screen at sets the framerate to 60 fps.
If you changed to work towards 60 fps you could cut out the calls to clear and setFrameRate.

You’re probably better off jamming the sprites into a seperate file.
Calling it Sprites.h won’t clash with the Sprites.h that Arduboy2 uses as long as you remember to include it with quotes instead of angle brackets, e.g. #include "Sprites.h".
That said, I usually call mine Images.h.

(Fun fact: technically what a lot of people call ‘sprites’ aren’t true hardware sprites like you used to get on old consoles. The meaning of ‘sprite’ has shifted from its original meaning.)

I’d say aim to understand enum classes next, so you can replace your outdated unscoped enums with modern, typesafe scoped enums.
(I know I keep saying it, but they really are underrated. There’s a few aricles in the magazine about them, but I can’t remember which pages/volumes.)

Entity.h should #include "my_world.h" instead of trying to extern bool level.

Overall it’s a good start. I’ll run it tomorrow if I get chance.
I won’t try now because it’s very late here.


#54

Tile collision*

More or less. I used the SoloLearn app and went halfway through it (I stopped to put it to practice, theorie alone with no practice is hard to remember) but yes I do enjoy video tutorials very much. Sometimes it’s much easier to see something (and to hear it) than to just read about it. Also, it allows to see things in practice. Unfortunately a lot of tutorial series don’t show clean code or assume full knowledge of the programming language from the viewer, it’s hard to get a good mix between the two.

I did download the Arduboy2 .pdf documentation, I really love reference documents like that https://mlxxxp.github.io/documents/Arduino/libraries/Arduboy2/Doxygen/pdf/Arduboy2.pdf

That’s why I was/am thinking of following a video tutorial series of C++ in another engine/framework/whatever if I find it too difficult to learn C++ the way I’m doing it now (SoloLearn + articles + practicing)

I’m sad that there aren’t great tutorial series for the Arduboy, but at the same time I am happy because that means I will get to do a tutorial series when I’ve learned enough.

Thank you, this works! I used extern as a last resort

I didn’t clean it up yet because I was really tired of messing around with perfection and not getting any results, but I really like this idea and I will definitely do this

For this game 30fps is perfect, characters won’t be moving at more than 2 pixels per frame at 60fps and I’ll have a bigger budget for collisions, attacks, spikes traps etc. I’d go for 60fps if the resolution was bigger. I’d definiely go for 60fps for a platformer so this is good information to know

I could see myself doing that for environment tiles, they won’t animate, only entities will. I made an auto-tiling thing and it’d be a shame if I couldn’t use it

Thanks! I try to use “as few as possible” but sometimes there’s nothing better than a comment (I try to use very understandable variable names and make each function do one thing and only one thing, making it all document itself). For sections, I got that from HTML and CSS

I believe you! I keep trying it but I keep messing it up, so I keep reverting it, but it’s on my to-do list. For now I tried giving it names that nothing else will ever have so that there’s no conflict

I might have missed something else you said but I hope not, I’ll go sleep now too
Have a good night


(Pharap) #55

Ah. I’ve never used videos, I just read articles and tutorials.
Some are better than others, but overall the best material tends to be written.

Once or twice I’ve watched some presentations, but those are generally better for when you understand the basics and start getting into the more advanced stuff.
(My favourite is probably Bjarne Stroustrup’s 2012 ‘Keynote’ presentation about the benefits of C++11.)

There are bits and pieces, but no single tutorial.
Writing good tutorials is hard and time consuming.

I’ve attempted to write my own once or twice but I usually give up because I have so much to say and I can never decide what order to teach things in.

(It’s worth nothing that the iso C++ faq suggests that finding a mentor is more useful than simply following tutorials.)

I’d recommend flicking through the magazine though:

When you eventually know how to use classes you probably won’t need extern anymore.
It’s only really needed for global variables.

It’s not so much about perfection as it is about reducing clutter.
Images are something you don’t need to edit by hand, so after you’ve created them you generally don’t have to look at again (unless you forget their names), so you want them kept away from your actual code.

Even if you have all your code shoved in a single file, it pays to keep the images separate,
because a large blob of numbers in the middle of your code is more likely to be an annoying distraction than other bits of code.

Fair enough, but note that on the Arduboy memory is usually a bigger constraint than speed/time.

Tiling is also a common use for the frame argument.
The tile type can be arranged to match up with the frame number.
E.g. Sprites::drawOverwrite(tileX, tileY, tiles, static_cast<uint8_t>(tileType));

Even if everything is seemingly self documenting it still pays to use comments.
I know a lot of people don’t bother, myself included, but there have been times where I’ve opened up one of my old projects after I’d completely forgotten what it was and then struggled to understand what I was doing.

Messing things up is part of the learning process.
If you don’t make mistakes then you don’t get anywhere.

In fact the whole reason enum class exists is because the committee realised that enum was a flawed construct and they wanted to fix it/improve upon it.

I’ve made an edited version of your code which I’ve put it in this gist.
I changed EntityType and DirType to an enum class (and slightly renamed them).
I also moved the sprites into a separate Images.h file and fixed a small warning in update_entity.

I didn’t check, but the resulting machine code should be pretty much the same.

I only had to change four things to use enum class:

The enum class definition, from:

enum EntityType {
    entNONE,
    entPLAYER,
    entKNIGHT,
    entSWORD,
};

To

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

(I didn’t have to change the names, but typically only macros use allcaps.)

Secondly, your Entity defintion, from:

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

To:

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

Thirdly, your case statements, from:

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;
}

To:

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;
}

And finally the type assignment in setup, from:

player.type = entPLAYER;

To:

player.type = EntityType::Player;

(Excuse some of the spacing differences, my editor automatically changes spaces to tabs.)

After seeing the changes I made, can you see what you were doing wrong before?


#56

I’m not sure but I did the change on-by-one following what you said and it worked. I probably had forgotten something (I initially accidentally removed "case " before a case, for example. It probably was a similar mistake)

I’m gonna put all the world.h functions and variables into a class, I think it’s time I properly learn about classes.


(Pharap) #57

classes are easier on Arduboy because you can’t really afford to use virtual functions or inheritance unless you’re willing to sacrifice a large chunk of memory.

So basically they’re just like structs, but with member functions (often called ‘methods’ in other languages), constructors and destructors.
(You’ll want to know about constructors, but probably not destructors. You’re very unlikely to need those on the Arduboy.)

The most important thing to understand is copy semantics vs reference semantics.
E.g. with a class definition like:

class Point2D
{
public:
	// Member variables
	// (often called 'fields' in other languages)
	int x;
	int y;
	
	// Constructor
	Point2D(int x, int y) : x(x), y(y) {}
};

Value semantics get you this:

Point2D a = Point2D(3, 4);
Point2D b = a;
a.x = 7;

// a.x = 7, a.y = 4
// b.x = 3, b.y = 4

Reference semantics get you this:

Point2D a = Point2D(3, 4);
Point2D & b = a;
a.x = 7;

// a.x = 7, a.y = 4
// b.x = 7, b.y = 4

Oh, and you’ll need to know about the three accessors, public, protected and private.
(The only difference between a class and a struct is that struct members are public by default and class members are private by default.)
(You probably won’t need protected much either, so don’t worry about it too much unless you plan to do non-Arduboy C++.)


Hopefully some of that made sense and I’m not just rambling on.


#58

(I’ll briefly mention that I’m aware of the difference between struct and class and public and private, but I am not aware of what private does - I mention it not as a “I already knew!” but as a “I acknowledge what you just said, I’m not ignoring anything you write”)

I’m starting to wonder if OOP is the best for something like this. I’m trying to put entity functions into the entity class, for some functions it makes sense but for others it seems to make a lot more sense to have a function outside of the entity class. For example my move function, I could theoretically want to move some other instance/object/thing that isn’t an entity (something that simply adds to the x and y)

Also, isn’t there overhead from having each instance of the class have a function, or do they reference the class and there’s only one copy of the function? Also, it’d be great to have a class function affect all it’s instances, instead of having to call the method on each instance. For example having Entity.updateAll() that updates all entities

I know some people prefer an entity component system instead of an OOP approach but I’m assuming I’m just not used to OOP. Do you have guidelines and recommendations on how to best use classes?


(Pharap) #59

That can be explained quite simply:

class SomeClass
{
private:
	// (The secret is that it's actually a long.)
	long secret;
	
public:
	SomeClass(int secret) : secretVariable(secret) {}

	int getSecret() const
	{
		return this->secret;
	}
};

SomeClass someClass = SomeClass(5);

// Ok
int secret = someClass.getSecret();
// Error: attempt to access private member
int secret2 = someClass.secret;

Anything that’s private can only be accessed by the class itself, it’s off-limits to anything else.

(Unless you meant to write protected there instead?)

That’s fine. Not everything makes sense as a member function.

But you don’t have to worry about name clashes, object.move(2, 3) and entity.move(4, 5) are two different functions operating on two different objects.
And if you had a ‘free function’ (thats the proper name for an ‘outside the class’ function) called move, you’d have to write different versions for Object and Entity, even if they both had x and y values.
I.e.

void move(Entity & entity, int x, int y)
{
	entity.x += x;
	entity.y += y;
}

void move(Object & object, int x, int y)
{
	object.x += x;
	object.y += y;
}

If you wanted to check obstacles at the same time then it’s probably better as a free function, but if you just want it to move without question then it’s probably better as a member function.

And you can always mix them to do something like this:

bool tryMove(Entity & entity, int x, int y)
{
	// Check for collisions
	if(/* figure out collisions */)
		return false;
	
	entity.move(x, y);
	return true;
}

Good question.
There’s only one copy of the function per class.
What actually happens is that when you have something like this:

class Entity
{
private:
	int x;
	int y;
public:
	void move(int x, int y)
	{
		this->x += x;
		this->y += y;
	}
};

move gets turned into something like this:

void move(Entity * entity, int x, int y)
{
	this->x += x;
	this->y += y;
}

Member functions are no more expensive than free functions, but they’re easier to read in most cases.
I.e. entity->move(4, 5); implies that entity is going to be altered somehow, whereas move(entity, 4, 5) makes it less obvious.
(In fact passing non-const references to free functions is often a bad idea.)

It doesn’t work like that. The class doesn’t know how many instances have been created.

That’s why you need your entities array - you decide which entities you care about and how they’re manipulated.

Ah, that’s a tool for modern computers. The Arduboy certainly doesn’t have enough RAM for that approach.
Plus it usually involves using reflection/RTTI (runtime type information), which comes with a performance cost.

There are lots of different ways to use classes, but in an embedded environment you have to throw out half the rulebook about good design because you just can’t afford the overhead.

Typically only the more fundamental rules apply.
For example, you should keep data private if it doesn’t need to be accessed outside the class, and if it needs to be readonly you write a ‘getter’ function, but no ‘setter’ function.

There are many different schools of thought when it comes to OOP.
Ultimately you don’t have to worry too much on the Arduboy because your program can only grow so big.
Figuring out which designs work best is something you gain with experience and after reading many articles with differing viewpoints and arguments.


(Matt) #60

I just make everything public and avoid getters/setters on the arduboy. Heck I make most objects global too. Things I’d never do in a more well rounded environment. But every single byte matters when you’ve only got 28672 of them. And like you said, the program can only get so big and do so much so protecting yourself from yourself is less of a concern.


#61

Edit: here’s the source https://github.com/TheProgrammer163/arduboy/tree/master/game_prototype
I forgot how to use GitHub so apparently I’ll have only one project in my Arduboy repository instead of multiples, I think.

Ok so with the information you gave I decided to have my draw_entity - update_entity and move_entity functions placed in the Entity class and leave other functions outside

(I had written a bunch of stuff that are no longer true before I modifed my files a lot I just edited this part out.)

I’m getting a weird bug

expected initializer before ‘MAX_ENTITY_COUNT’

I didn’t change any code here except adding some includes

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

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

#include "Entity.h"
#include "Images.h"
#include "World.h"

constexpr uint8_t MAX_ENTITY_COUNT = 10;
Entity entities[MAX_ENTITY_COUNT];

EDIT: if I add a semicolon after the struct Entity in Entity.h it seems to fix it but…

struct Entity {
    public:
        int8_t x;
        int8_t y;
        EntityType type;
        uint8_t damage_taken;
        uint8_t state;
        Direction facing;
    public:
        void move(int8_t xmove, int8_t ymove);
        void update();
        void draw();
}// <----- if I add a semicolon here

But then I get a different error:

exit status 1
Error compiling for board Arduboy.


#62

Edit: I figured out how to get the whole error message, and here it is:

sketch\World.cpp.o (symbol from plugin): In function `level’:

(.text+0x0): multiple definition of `level’

sketch\Entity.cpp.o (symbol from plugin):(.text+0x0): first defined here

sketch\game_prototype.ino.cpp.o (symbol from plugin): In function `setup’:

(.text+0x0): multiple definition of `level’

sketch\Entity.cpp.o (symbol from plugin):(.text+0x0): first defined here

C:\Users\Alex\AppData\Local\Temp\cc5LuSuf.ltrans0.ltrans.o: In function `loop’:

C:\Users\Alex\Documents\Arduino\tutorial projects\game_prototype/game_prototype.ino:59: undefined reference to `draw_walls()’

C:\Users\Alex\AppData\Local\Temp\cc5LuSuf.ltrans0.ltrans.o: In function `move’:

sketch/Entity.cpp:38: undefined reference to `detect_wall(signed char, signed char)’

sketch/Entity.cpp:41: undefined reference to `detect_wall(signed char, signed char)’

collect2.exe: error: ld returned 1 exit status

exit status 1
Error compiling for board Arduboy.