Help me with C++ for my game


#63

Hey I fixed it!, I’ll post the updated code later

Edit: okay I think I figured out how to update the files, but I’m not sure you’ll be able to compare the before/after of the code on Github

I’m back to the good old enum class bugs I don’t understand.

‘Player’ is not a member of ‘EntityType’

All I did was uncomment this bit in the main ino file:

void setup() {
    arduboy.begin();
    arduboy.clear();
    arduboy.setFrameRate(30);
    arduboy.initRandomSeed();
    // ------------------------------ commented section --------------
    player.type = EntityType::Player; // <------- BUG HERE
    player.x = 64 - 4;
    player.y = 16 - 4;
    // ----------------------------------------------------------------

Edit: I put the whole enum class in Entity.h and removed anything ‘enum class’ related from the .cpp file. I get a different error now

‘EntityType’ does not name a type

^ this is in Entity.cpp. Why is Entity.cpp unaware of the existence of my enumerator? Am I misunderstanding the problem?


#64

Okay… if I define my enum classes twice then it works. I thought we couldn’t define something twice?

If I put this:

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

enum class Direction : uint8_t {
    Down,
    Right,
    Left,
    Up,
};

in the Entity.h file but also in the Entity.cpp file, then it compiles. Is that what I’m supposed to do? (apparently not, but as I’ve already written the post I’ll post it, and re-reply as soon as I have the solution understood)


#65

The cpplang slack channel is great

It now compiles, I’m eating I’ll edit this and explain what I did to fix all this in a minute

EDIT: well in short, I put this part in Entity.h:

struct Entity {
    public:
        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;
    public:
        void move(int8_t xmove, int8_t ymove);
        void update();
        void draw();
};

I also moved all the enum classes in Entity.h, and I added #include “Entity.h” in Entity.cpp, because apparently it’s necessary

And there’s no ‘struct Entity’ in Entity.cpp. I only left the method definitions in the .cpp file.
I updated the code on GitHub now, the game works and compiles just fine.

I also changed the framerate to 60, following the advice of Pharap.


#66

This will prob clear up the difference between header files and .cpp files.

Think of the .h as a table of contents from a book.
You need to include the .h in it’s associated .cpp file. Then when you need something you include the .h instead of the .cpp

That’s the correct way to format. You can put const definition or more likely In this setting const PROGMEM variables in .h files too. If you look at peoples code you’ll notice most people will have something like images.h with a bunch of const PROGMEM uint8_t[] arrays of all the sprites/bmps/ graphics and no .cpp file.


(Pharap) #67

I still use getters because it makes it easier to change the implementation, and they’re almost always inlined anyway so there’s typically no extra cost.

I still avoid globals for two reasons.
Firstly because globals are more expensive to access on AVR.
Secondly so I can keep a better eye on where they’re used,
I don’t have to worry about them being used from absolutely anywhere.


@SamSibbens, unfortunately I wasn’t around yesterday or I could have answered all of these issues.

You’re better off having one repository per game anyway.

Yes, that semicolon is necessary.
It’s one of the many unfortunate ‘hangovers’ from C.

Basically in C you could write struct StructName { /*struct stuff*/ } objectName; to declare a variable named objectName of type struct StructName.
If you just wanted to define the type and not a variable of that type, you’d write struct StructName { /*struct stuff*/ };.
So when C++ introduced class it kept that rule because that’s what people were familiar with.
These days most people realise that was a bad idea, so usually you don’t get many people trying to declare a variable at the same time as a struct type.

When you see .text+ in an error message, this is a special kind of error called a ‘linker error’.
It means that something has been declared once but defined twice for some reason.

Causes for this can vary.
Often it’s because something is defined twice somehow.

Entity.cpp must manually include Entity.h, it isn’t included automatically.
I think you’ve figured that out.

For code to be aware of the existance of a type, that type must be declared beforehand.
Often that means including the header where the type is defined.

Never been there.

Pretty much all the people I’ve worked with on games do this.
It’s the tried and tested solution, and it hardly ever breaks.


(Matt) #68

Expensive as far as cpu cycles? I haven’t been writing any cpu intensive games, so fortunately don’t have to worry about that. But I do want as much flash space as possible. Switching to globals in Ardynia saved around 1kb of flash.


(Pharap) #69

Nope, code size. According to the AVR optimisation guide (section 3.2 Tip #2 - Global variables and local values):

Atmel AVR4027 C Optimisation.pdf (236.7 KB)

(I uploaded because I don’t have the web link anymore, I got this before they did the big reshuffle and moved all the resources to www.microchip.com.)

If you were passing things around as function arguments before then it’s probable that the size increase came from the function preample where arguments are pushed onto the stack.

There’s no hard and fast rule about what works better when it comes to optimisation,
there’s lots of factors that have to be taken into account.

For example sometimes the compiler can’t figure out how/when it can turn a switch into an array lookup or it legally can’t because of the rules of the language, so you can help it along by writing your own lookup array.
Other times I’ve attempted to save memory by writing my own and discovered that the compiler was already doing that.
(I’m still not sure why it can sometimes and not others, it’s probably related to the complexity of the code surrounding the switch or something.)


(Matt) #70

Yup, that’s what I was doing. Started out writing the game with all the protections I’ve built up over the years. Stripped pretty much all of them out by the end. Felt weird, but it worked.

Oh for sure. Always gotta measure. Sometimes looking at the disassembly can help too. Sometimes I’d try something I was sure would save space and would actually cost 12 more bytes, and other times I’d do something and cringe thinking how many bytes I just lost, but would instead gain 100! Compiler, you work in mysterious ways…


(Pharap) #71

Yeah, unfortunately AVR wasn’t really designed with these sorts of things in mind.

They designed AVR to make the chip cheap and simple and suitable for things like toasters.
They didn’t even add a barrel shifter.
That alone would have cut code size down significantly.

Yeah, the problem is that the order it performs operations in makes a difference, and sometimes optimisations can have a knock-on effect.

Theoretically, the compiler could go through every permutation until it found the one that produced the smallest code,
but then people would complain about compilation times. :P


I’ve found that one of the best ways to ensure small code is actually to just keep your functions as short as possible and make sure you break large functions up into smaller functions.
The compiler starts to struggle after hitting a certain amount of code/complexity, so using lots of functions gives it smaller chunks to work with, and thus a smaller context to worry about, so it picks better optimisations because it doesn’t have to consider as many possibilities.
If a function is only used once then it still tries to inline it, but because it omptimised that function in isolation, it does a better job of the optimisation and ends up with smaller code.


(Matt) #72

haha, I wouldn’t :slight_smile:


(Pharap) #73

Considering how many optimisation techniques there are,
it would probably take half a day at the minimum, possibly even 2-3 days.

I’d be up for it if I had a spare computer and I only did it for release builds I guess. :P


(Matt) #74

Thankfully ProjectABE will run a sketch that is too big. So it wouldn’t impact the dev feedback loop too much. I’d say worth it if it mean we could squeeze a few more frames of animation or what not into a game. But I realize that’s pretty extreme.


(Pharap) #75

@SamSibbens, I think the biggest problem you’re having is that you don’t understand how compilation works yet.

So here’s a breakdown.
(Maybe I should post this in another thread later? It might be useful to other people too.)

Preprocessor
Firstly the source code gets put through a thing called the ‘preprocessor’.
This is basically a glorified copy & paste machine.
This is what translates all the #defined values and handles all the #includes.
In fact any of the words with a # in front are preprocessor commands,
including #pragma, #if, #else, #elif, #endif and so on.

#defines are handled by replacing the #defined symbols with their definitions.
This is one of the reasons why using #defines is often a bad choice.
The compiler itself never sees the name, it only ever sees what the macro has been replaced with,
so any error messages will only feature the contents of the macro and not the macro’s name.

#includes are handled by copying the contents of the source file and effectively just pasting it where the #include was.
This is why include guards and/or #pragma once are needed, either to prevent the same thing being pasted multiple times (i.e. #pragma once) or to ensure that being pasted multiple times isn’t an issue (i.e. include guards).

Compiler
The ‘compiler’ translates all the .h and .cpp files into assembly code.
This is actually the only phase that deals with C++ code, none of the other stages understand C++.
This is where the majority of errors are detected, because most errors are errors in the context of the C++ language.

(The compiler itself actually has several phases involved, but I won’t go into that because compilers are complex beasts. If you want to know more about how compilers work, I have some resources I can link to.)

Assembler
The assembler takes any generated assembly code and turns it into .o files, which are called ‘object’ files.
(These have nothing to do with classes and objects, they were called object files in C too.)
This stage should never generate any errors because the compiler should generate correct assembly code, otherwise the compiler is not doing its job properly.

Linker
Then the object files are put through a thing called the ‘linker’ which joins up all the .o files into a single executable or library file, which could be an .exe, .dll or .lib if it’s targetting Windows, an .elf, .so or .a if it’s targetting Linux, or .hex when targetting Arduino boards like the Arduboy.
(That’s not an exhaustive list, those are just the more common/relevant examples.)
This is where linker errors come from.
When there are multiple .o files with different definitions for the same symbol, the linker doesn’t know which definition is correct (even if the definition is exactly the same) so it complains.


Edit
Also I stumbled upon a video series someone once recommended.
I’ve only seen a few bits of it, and there were things I weren’t happy about but overall it wasn’t too bad, so I’ll link to that.
It was called The Cherno Project. The playlist for the C++ stuff is here.


#76

cpplang slack channel is the C++ channel (I’m clarifying in case it wasn’t obvious). They couldn’t help with Arduboy specific stuff but they were able to spot my C++ mistakes

I mostly understood all this but I didn’t know .cpp files needed to include their header files counterpart.

I also didn’t (and probably still don’t) understand all the nuances between a declaration and a definition. I understand the theory, but in practice? It sounds like some things cannot be declared without being defined, like enum classes and classes. (but class methods can be defined later). I suppose it’s the job of the constructor to define/redefine the class variables later, when it gets instantiated?

I also know there’s a unique way to define constant members of a class via the constructor

I understood most of the theory, but in practice how to declare stuff without defining them that seems very quirky. If you could add examples of that in your post, it would make it 1000 times easier to switch from another language to C++.

More specifically, examples for:

  1. enum class declared in the header and defined wherever it needs to be defined, + the explaination as to why it can’t be defined elsewhere after being declared
  2. a class declared (defined I guess?) in the header file, and defined later (if it can be) and its methods being defined later as well
  3. same as number 2, but with the methods requiring parameters
  4. I think it’s already in your post but that it’s necessary for .cpp files to include their header counterparts

That + how to make a ‘free function’ g’global function’ and make them accessible from any .cpp file (I assume this is with an #include as well?

^ all that is basically what I mostly learned yesterday, I say mostly because the nuances between defining and declaring (in practice, not in theory) still confuse me. If you put your great post and add this to it, it’d be extremely helpful to many I think. Definitely worthy of it’s own post

Still I’m pretty satisfied with yesterday, I think it’s been productive.

By the way @city41 and @Pharap I’ve been reading your conversation, keep at it it’s informative. I’d tend to go the “everything public except a few things” direction. Exceptions being if a variable changed needs to update other variables because they’re closely related, then I’d use setters.

In GameMaker there are no setters so what I’m used to is having a variable that gathers information (e.g damage_taken) and let the instance take care of what happens from that information


#77

To quote Clean Code “a function should do one thing and one thing only”. It’s interesting to know that it also helps optimize things. (I read only 5% of the book by the way, but it improved my code by 1000%.)


(Pharap) #78

I’ve never even been on a slack channel, let alone the C++ one. :P

About what I would have expected.
To be honest I think it makes sense to learn full C++ alongside Arduboy.
I started using the Arduboy after I’d learnt C++ and I probably found it a lot easier because of that.

Hopefully my explanation of the compilation process will make it clear why things are this way.

For the record, file names are completely arbitrary.
The compiler doesn’t consider fish.h and fish.cpp to be related, it’s only what you write that makes them related.

Anything can be defined without being declared because a definition is also a declaration, but you can’t declare something unless it’s also defined somewhere.

I’m not sure what you mean by this.

Do you mean how to declare something and have its definition elsewhere?

In most cases you don’t need to do this, you can just define everything in the same header that the thing is declared in.
You don’t even necessarily need to have any .cpp files.

You probably don’t want to do this because the values of the enum class are just as important as the type itself.
Declaring the type doesn’t give you access to its values, so you want to be defining it in a header and then including that header when and where the type is needed.

That said, you can predeclare with enum class name;,
or if you’re specifying the size of the enum as well then enum class name : type; (e.g. enum class EntityType : uint8_t;).
But like I say, you shouldn’t need that, because having access to the type doesn’t give you access to the values.

Again, this is something you probably don’t want to be doing because only having knowledge of the type doesn’t give you knowledge of the class’s members.

In fact, if you only know the type exists, you can only use pointers and references to the class, you can’t even declare a function argument of the class type (because without the definition the compiler doesn’t know how big the type is).

This can be done with class name;.

This one’s a bit more common, where you declare the methods in the class definition but don’t implement them in the header file, you just implement in the .cpp file.

That would be like:

// SomeClass.h
class SomeClass
{
private:
	int value;
public:
	int getValue() const;
	void setValue(int value);
};

// SomeClass.cpp
#include "SomeClass.h"

int SomeClass::getValue() const
{
	return this->value;
}

void SomeClass::getValue(int value)
{
	this->value = value;
}

Every file exists in isolation.
If a file needs to know the existance of a type that’s declared or defined in a different header, it must include that file.
.cpp files are a slight exception to this rule.
If a function is defined in a .cpp file then only the declaration needs to be known.

For types, you almost always want to see the full definition because it’s important for knowing the size and members of the type (this is true for struct, class, enum and enum class (and another thing I won’t tell you about because it will complicate matters)).

If you want to define it in a header:

// File.h
inline int get2Squared()
{
	return 2 * 2;
}

If you want a separate declaration and definition:

// File.h
int get2Squared();

// File.cpp
int get2Squared()
{
	return 2 * 2;
}

Yes. As I say, any file that wants to ‘see’ something must include the declaration/definition.
Includes aren’t something to be afraid of, they’re the natural way of being aware of something’s existance.

I’ll get round to doing that at some point.

The only thing that puts me off is that I’d be worried about all the experienced programmers getting into arguments over biases, nuances and pedantry.

Probably the best bit of advice I could give you:
Forget (almost) absolutely everything you learned in gamemaker.

GML is a terrible language created by a novice who never studied language theory and it has very little in common with ‘real world’ languages.

I’ve never got round to reading it, but it comes up a lot.

Yep. Sometimes these rules have more than one purpose/reason.


#79

The way I’d describe my experience with GameMaker would be “I learned everything about programming except a programming language” (this statement isn’t 100% correct but it’s a pretty good summary)

The good news is, I don’t really have anything to forget. The bad news is, I have to learn everything language related. (mostly. I have a bit of experience with JavaScript and Lua with Love2D)

Meh, a simple disclaimer at the top of the post should be okay but I understand the feeling. Getting a flame war for trying to be helpful isn’t fun

Sprites time! I’ll be getting around to using the sprites class now. I’ll keep your fixed point library in mind for later


(Pharap) #80

I’d say that’s probably getting close to the truth, but really Gamemaker is just one game engine, so it’s only really taught you a subset of how game engines behave.

I detest JavaScript but I like Lua.
Lua is what scripting languages should be like in my opinion.

It would be a very polite disagreement rather than a flamewar, but it would still be a big distraction.

A word of advice: you don’t have to create an instance to use it.
All the functions are static, so you can just do Sprites::drawOverwrite instead of sprites.drawOverwrite.

(In my experience, Sprites is more commonly used for drawing sprites than the Arduboy2 drawing functions.)

There’s no rush to use it, only a handful of games actually do.
(The ones that do are pretty cool though: 1943 and a 3D(ish) pod racing game.)


#81

I learned some more C++ today already thanks to you

How to use arrays with for loops isn’t something I need to learn (I had a very hard time understanding it at first), I know how to make levels out of tiles, collision detection. I implemented bresenhem’s line algoritm for a line of sight function. That’s actually why I got pretty tired of GameMaker, if I’m making all my own functions for everything why use an engine at all?

By the way I’ll probably redo my tentacle enemies on the Arduboy :smiley: There’s a triangle drawing function and it’s all I need. Actually I think I’m gonna do this right now

I’ll be right back

EDIT: I didn’t expect a yellow emoji to appear because of my : D but I’ll leave it there


(Daniel Pelletier) #82

Ouch ! I have a lot of things to learn ! Thanks for the explanation ! It will be usefull ! My c++ class are 20 years this year and I didn’t practice it so I mostly lost it all !

I will start again with my son as soon as our arduboys are in the mailbox !