Procedural or OOP for games design paradigms on the Arduboy?

(Boardkeystown) #1

Hello,

I’m spinning my wheels here, I have made several small sketches to test different things tiles system, timers, sprites, and the such.

Now, I am trying to combined everything such that it work as a game. But I can’t quite tell what the best direction to program on the Arduboy is.

I have programmed games before and the de facto paradigm I use for my games is:

// Main game class     
Game myGame()
    main () {
      myGame.run()
    } 
    //Basically what run does   
    while(true){
         //some game tick control 
         processInput()
         update() 
         render()
        }

I OOP my games in that there is a game class which has one public function run which I call in main. And I design everything in terms of objects and abstract types which use some from of polymorphism. I have done games in which I basically program it procedurally you know like have tons of methods that call each other but this tends to get messy and redundant quick. I’ve notice that most people that program for the Arduboy make their games procedurally with game loops based on some kind of system of function pointers which regulate the game state.

Is there a reason for this? I guess my main question is how well does the arduboy or I should say arduino leonardo chip support C++ OOP? Is there a performance hit from passing a pointer of Arduboy2 object like a stick at a relay race? Is there a “good” programming paradigm for programming on the Arduboy?

Thanks for reading,
I hope some more experienced programmers can pass on their knowledge.

0 Likes

2D Top Down Tile/Map System Programming Question?
(Gavin Atkin) #2

I’m probably not the best person to answer this, but I’d say the best thing to do is test different ways of structuring simple programs and compare compile sizes. When I was testing some different OOP concepts at one point they seemed to compile a little larger, but I don’t remember the specifics since it was a while ago. When you’re trying to finish a game with 100bytes left you do whatever you have to to save space lol.

0 Likes

(Pharap) #3

I appologise in advance if I discuss something you already know or if I’ve gone into too much detail.
When I write comments like this I tend to avoid assuming prior knowledge and hope that what I write will also benefit other readers.


Just to check, is this code supposed to compile? (I’m assuming not.)

I’d like to point out that usually Arduino programs use setup and loop functions.

You can write your own main but you should be aware of what the default main provided by Arduino does.

On the Arduboy, I think serialEventRun is always false and I think there is no USBDevice to attach(), but I’m not sure about init().


If by polymorphism you mean ‘virtual functions’ then be aware that virtual functions eat up quite a bit of progmem, so they can be quite expensive on the Arduboy.

(I’d like to point out that OOP’s definition of polymorphism via inheritance is in fact only one kind of polymorphism - specifically ‘subtyping’. There’s also ‘ad hoc’ polymorphism, a.k.a. function overloading, and ‘parametric’ polymorphism. C++ supports all three kinds in one shape or form.)

It depends on whose code you’re looking at.

Personally speaking I haven’t written many fully fledged games,
but when I write Arduboy code I tend to use just as many classes as I do in desktop code.

I think most people write procedural code either:

  • Because they’re beginners who don’t know much about OOP
    • (I find a surprising number of people think classes are scary and/or confusing.)
  • Because they think it somehow results in less code
    • (Which may or may not be true depending on the structure of the code - I think in general it makes litte difference, classes and objects are cheap if you avoid virtual functions (and possibly inheritance - but the verdict is still out on that).)
  • Because they find it quicker/easier for some reason?

Personally I think a lot of people avoid classes because they think “classes make the code look bigger, therefore the generated code must be bigger too”.
As someone who knows (roughly) how classes work under-the-hood,
I’d say 8 times out of 10 that’s untrue.
Merely using classes and/or OOP doesn’t automatically make your code bigger,
what matters is how you’re using them and what specific techniques/features you’re using.

The only place I know for definite that classes are more costly is when you start using virtual functions.
virtual functions force the creation of a vtable and that chomps up progmem, on top of which there has to be a pointer to the vtable (an extra sizeof(void*) bytes of RAM per object) and some extra set up code to copy the vtable pointer into the object.

I suspect that inheritance might also have a cost in some situations because I remember doing a test once that implied that both the parent and child classes get their own copy of an inherited function,
but it’s been a long time since I’ve tested that and it might only be an issue for inherited functions.

Performance isn’t much of an issue, but usually passing a pointer or reference to Arduboy2 object to a function has a memory cost.

The Arduboy2 object is one case where using a global variable (or a member variable in a Game class) is probably a good idea, purely because not having the extra function parameter saves progmem.

(This is part of the reason I tend to prefer the Sprites class for drawing - all the functions are static so there’s no object to pass around.)

Firstly, technically speaking a programming paradigm is a way of classifying programming languages, not a way of programming.

Assuming you mean “is there a preferred style/method of programming on the Arduboy”, I’m not sure if there is really.
Everybody uses different mixtures of techniques and features.
Generally people stick to what they’re used to using or what they prefer.

A lot of the rules that apply to desktop programming don’t apply to programming for an embedded device for various reasons.

For example there’s not enough memory to make dynamic allocation worthwhile, so all the pracises built up around dynamic allocation (e.g. using std::move and handling rvalue references) are effectively worthless.

However, a lot of rules that apply to readability and correctness are still important (like making sure your code is const-correct, prefering templates over macros etc).


I’d also like to point out that the reality of OOP in programming languages is quite different from what purist OOP theory believes.

Programming paradigms aren’t religions that you sign up to and stick with, they’re different tools in the same toolbox and you should strive to recognise and appreciate the benefits and drawbacks of each approach.

C++ has many different features that could be classified as belonging to different paradigms, but using the features in conjunction with each other is strongly encouraged and generally results in better quality code.

1 Like

(Scott) #4

However, creating an instance for one of the Sprites classes doesn’t cost anything over using the class functions directly (at least for a simple global instantiation).

The following sketch will produce an identical .hex file if you uncomment the two commented out lines, and comment out
Sprites::drawOverwrite(60, 30, sprite1, frameNum);

#include <Arduboy2.h>

Arduboy2 arduboy;
//Sprites sprites;

const unsigned char sprite1[] PROGMEM = {
8, 8,
0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 
0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff
};

byte frameNum = 0;

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

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

  arduboy.clear();
  arduboy.pollButtons();

  if (arduboy.justPressed(A_BUTTON)) {
    frameNum = frameNum == 0 ? 1 : 0;
  }

//  sprites.drawOverwrite(60, 30, sprite1, frameNum);
  Sprites::drawOverwrite(60, 30, sprite1, frameNum);

  arduboy.display();
}

Because of this, I prefer instantiating a Sprites class. It makes it easy to compare code sizes between using Sprites or SpritesB just by changing the class that you instantiate.

Sprites sprites;  // to use the functions in the Sprites class
SpritesB sprites; // to use the (identical) functions in the SpritesB class
1 Like

(Pharap) #5

Using it as a global means it has all the disadvantages of a global:

  • It forces you to use extern Sprites sprites; (in certain situations)
  • Global variables can be hard to keep track of because they can be used anywhere in the program

Alternatively you can just use a simple type alias:

using SpritesClass = Sprites;
//using SpritesClass = SpritesB;

And then do:

SpritesClass::drawOverwrite(60, 30, sprite1, frameNum);

Which is just as easy to understand.

0 Likes

(Scott) #6

It requires an extra character (:: instead of .) for every function call you have to type, though :wink:

And, don’t you have to use using everywhere where you would have to use extern?

1 Like

(Pharap) #7

With a type alias you only need to ddeclare it once in a header,
with a global variable you need to declare the variable extern in a header and then place the actual definition in a separate .cpp file.

But most people just pick either Sprites or SpritesB and stick with it,
you only need a type alias if you plan to be regularly switching between the two for some reason.

Also in terms of semantics using :: (the scope resolution operator) makes more sense than using . (the member of object operator) because static member functions are associated with the class scope, not with the object (i.e. they have no this pointer and they behave more like (scoped) free functions than member functions).

0 Likes

(Scott) #8

Except during development, where you may want to try both before each release to see which produces the smaller code size (assuming the extra speed gained by Sprites isn’t a requirement).

0 Likes

(Simon) #9

I always pick Sprites until I run out of space then I … wait for it … do a find and replace to change it from Sprites:: to SpritesB::.

No instantiation, no extern.
No using.

2 Likes