Questions about optimizing code

(Andrew Crawford) #1

Hey everyone, so unfortunately ive put a hold on what I had originally intended this game to be (until Im better experienced to give it a proper go), but was looking to salvage what i currently have and had a few questions…

As of right now, the game isnt much- just randomly spawns the player and their crashed ship somewhere in the specified map space and randomly populates/spawns a number of rocks, as well as player movement/animation. Most recently ive added a laser using the drawPixel function and attempted to use collision detection between the laser and rocks to delete the rock sprite (which didnt work and i figure probably isnt right). So I had a few questions.

  • First, how can i better organize my code? I went back and localized as many variables as i could and commented each section for a bit more clarity, i just know there are probably better ways to do this using structs and classes that im not so sure on and dont want to make a mess of things

  • Second, how can I set collision detection- firstly just between the player and rocks (because i figure this would be simpler) and secondly to erase the rock via laser collision (which i figure may require more because im not sure if it stores each individual rock location in a buffer somewhere but am guessing probably not).

  • And lastly, is there any way i can loop the map? Rather than restrict the player to a boundary box i would rather just repeat everything, if possible (just because it can make things feel larger or i can do more with less).

Sorry, I know this is a lot to throw out there/ask, I just want to try to use what I have rather than try to start over, and once I get these other parts down I think i can figure out the rest from there.

Here is my current code:

// See: https://mlxxxp.github.io/documents/Arduino/libraries/Arduboy2/Doxygen/html/
#include <Arduboy2.h>
Arduboy2 arduboy;
Sprites sprite;

int playerx;
int playery;
int mapx;
int mapy;
int crashedshipx;
int crashedshipy;
int WORLD_WIDTH = 512;
int WORLD_HEIGHT = 256;
uint16_t rockx[50];
uint16_t rocky[50];
uint8_t const *player = nullptr;
uint8_t const *playermask = nullptr;

PROGMEM const unsigned char background[] = {
// Bitmap Image. No transparency
// Width: 10 Height: 16
8, 8, 
0x00, 0x08, 0x80, 0x00, 0x01, 0x20, 0x04, 0x02, 0x04, 0x00, 
0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 
};

PROGMEM const unsigned char crashedship[] = {
// Bitmap Image. No transparency
// Width: 30 Height: 24
30, 24, 
0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x04, 0x08, 0x70, 0x90, 0x10, 0x30, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x0B, 0x17, 0x2E, 0x5C, 0xB8, 0x70, 0xE1, 0xC2, 0x82, 0x03, 0x04, 0x08, 0x90, 0x20, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0C, 0x0E, 0x0F, 0x0F, 0x0E, 0x0F, 0x0F, 0x0F, 0x0E, 0x0F, 0x0F, 0x0E, 0x0C, 0x0D, 0x0F, 0x0E, 0x0C, 0x08, 0x08, 0x00, 
};

PROGMEM const unsigned char rock [] = {
  // Sprite: Image + Mask
  // Width: 8 Height: 64
  8, 8,
  0x3C, 0xFF, 0x5A, 0xFF, 0xF3, 0xFF, 0xCB, 0xFF,
  0xBE, 0xFF, 0xE7, 0xFF, 0x7A, 0xFF, 0x3C, 0xFF,
};

PROGMEM const unsigned char playerdown[] = {
  // Sprite: Image + Mask
  // Width: 11 Height: 24
  11, 17,
  //frame 0
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  //frame 1
  0x00, 0xE0, 0x10, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x03, 0x05, 0xFF, 0xC2, 0xFF, 0xB2, 0xBF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  //frame 2
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  //frame 3
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x10, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xBF, 0xB2, 0xFF, 0xC2, 0xFF, 0x0A, 0x07, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

PROGMEM const unsigned char playerdown_mask [] = {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
};

PROGMEM const unsigned char playerup[] = {
// Bitmap Image. No transparency
// Width: 11 Height: 24
11, 17,
//frame 0
0x00, 0xE0, 0x90, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFE, 0xC2, 0xFF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
//frame 1
0x00, 0xE0, 0x90, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x10, 0xE0, 0x00, 
0x00, 0x01, 0x02, 0xBF, 0xB2, 0xFE, 0xC2, 0xFF, 0x0A, 0x07, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//frame 2
0x00, 0xE0, 0x90, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFE, 0xC2, 0xFF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
//frame 3
0x00, 0xE0, 0x10, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x03, 0x05, 0xFF, 0xC2, 0xFE, 0xB2, 0xBF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};

PROGMEM const unsigned char playerleft[] = {
// Sprite: Image + Mask
// Width: 11 Height: 192
11, 17,
//frame 0
0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x03, 0xFF, 0xC2, 0xFF, 0x03, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  
//frame 1
0x00, 0x00, 0x00, 0xFC, 0x5E, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x03, 0x02, 0xFF, 0xC2, 0xFF, 0x7F, 0x70, 0x20, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
//frame 2
0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x03, 0xFF, 0xC2, 0xFF, 0x03, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
//frame 3
0x00, 0x00, 0x00, 0xFC, 0x5E, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00, 
0x00, 0x08, 0x14, 0x27, 0xFB, 0xC7, 0xFF, 0x03, 0x03, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   
};


PROGMEM const unsigned char playerright[] = {
11, 17,
//frame 0
0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x03, 0xFF, 0xC2, 0xFF, 0x03, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
//frame 1
0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0x5E, 0xFC, 0x00, 0x00, 0x00, 
0x00, 0x20, 0x70, 0x7F, 0xFF, 0xC2, 0xFF, 0x02, 0x03, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
//frame 2
0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x03, 0xFF, 0xC2, 0xFF, 0x03, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
//frame 3
0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0x5E, 0xFC, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x03, 0x03, 0xFF, 0xC7, 0xFB, 0x27, 0x14, 0x08, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};

PROGMEM const unsigned char playerleft_mask[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
};

uint8_t x = 0;

void setup() {
  arduboy.boot();
  //ship, player, and rock locations
  arduboy.initRandomSeed();
  crashedshipx = random(10, 80);
  crashedshipy = random(10, 30);
  playerx = crashedshipx + 20;
  playery = crashedshipy + 5;
 for (x = 0; x < 50; x++){
   rockx[x] = random(0,512);
   rocky[x] = random(0,255);
  } 
  arduboy.setFrameRate(50);
  arduboy.clear();
}

uint8_t frame = 0;

void loop() {
  //laser x and y origin
  int laserx = 5 + playerx;
  int lasery = 5 + playery;
  
  if (!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();
  //drawing background tiles
  for (int x = 0; x < WORLD_WIDTH; x = x + 8 ) {
    for (int y = 0; y < WORLD_HEIGHT; y = y + 8 ) {
      sprite.drawOverwrite(x + mapx, y + mapy, background, 0 );
    }
  }
  //player movement and animations
  if (arduboy.pressed(LEFT_BUTTON)) {
    mapx += 1;
    player = playerleft;
    playermask = playerleft_mask;
    frame++;
    if (frame > 3) frame = 0;
  }
  if (arduboy.pressed(RIGHT_BUTTON))  {
    mapx -= 1;
    player = playerright;
    playermask = playerleft_mask;
    frame++;
    if (frame > 3) frame = 0;
  }
  if (arduboy.pressed(UP_BUTTON)) {
    mapy += 1;
    player = playerup;
    playermask = playerdown_mask;
    frame++;
    if (frame > 3) frame = 0;
  }
  if (arduboy.pressed(DOWN_BUTTON)) {
    mapy -= 1;
    player = playerdown;
    playermask = playerdown_mask;
    frame++;
    if (frame > 3) frame = 0;
  }
  if (!arduboy.pressed(UP_BUTTON) && !arduboy.pressed(DOWN_BUTTON) && !arduboy.pressed(LEFT_BUTTON) && !arduboy.pressed(RIGHT_BUTTON)) {
    frame = 0;
  }
  //draw player, ship, and rocks
  sprite.drawExternalMask(playerx, playery, player, playermask, frame, 0);
  sprite.drawSelfMasked(crashedshipx + mapx, crashedshipy + mapy, crashedship, 0);
  for (x = 0; x < 50; x++) {
    sprite.drawPlusMask(rockx[x]+mapx, rocky[x]+mapy, rock, 0);
    }
  //depending on player orientation and A button press, draw laser and check for collision between laser and rocks
  if (player == playerleft && arduboy.pressed(A_BUTTON)) {
    for (int x = 0; x < 20; x++) {
    arduboy.drawPixel(laserx--, lasery,1);
     if (arduboy.collide((Point){laserx, lasery}, (Rect){rockx,rocky,8,8})){
        sprite.drawErase(rockx,rocky,rock,0);
      }
    }
  }
   if (player == playerright && arduboy.pressed(A_BUTTON)) {
    for (int x = 0; x < 20; x++) {
    arduboy.drawPixel(laserx++, lasery,1);
    if (arduboy.collide((Point){laserx, lasery}, (Rect){rockx,rocky,8,8})){
        sprite.drawErase(rockx,rocky,rock,0);
      }
    }
  }
   if (player == playerup && arduboy.pressed(A_BUTTON)) {
    for (int y = 0; y < 20; y++) {
    arduboy.drawPixel(laserx, lasery--,1);
    if (arduboy.collide((Point){laserx, lasery}, (Rect){rockx,rocky,8,8})){
        sprite.drawErase(rockx,rocky,rock,0);
      }
    }
  }
   if (player == playerdown && arduboy.pressed(A_BUTTON)) {
    for (int y = 0; y < 20; y++) {
    arduboy.drawPixel(laserx, lasery++,1);
    if (arduboy.collide((Point){laserx, lasery}, (Rect){rockx,rocky,8,8})){
        sprite.drawErase(rockx,rocky,rock,0);
      }
    }
  }
  arduboy.display();
}
1 Like
(Pharap) #2

I think it’s going to take several comments to answer your questions because you’ve asked quite a lot of big stuff.

I’ll start by discussing organisation purely because it’s a very general problem and I think you will learn some useful things will help you regardless of what program you’re writing.

(I’ll warn you now, this is a long answer. But I’ve included headings at least.)

I can think of a handful of things.
This is going to be a bit of an infodump,
so I’ll be putting some stuff in ‘details’ blocks.

You don’t have to read the stuff in the ‘details’ blocks if you think it’s too much,
the extra info and links are mainly there to back up my claims in case anyone reading this doubts that I know what I’m talking about.

Capitalisation

There are four (commonly accepted) ‘cases’ in programming:

  • Camel case - e.g. playerX
  • Pascal case - e.g. GameState
  • Snake case (or ‘underscore case’) - e.g. `
  • Macro case - e.g. INT_MAX

In C++, macro case should be used for macros, and only macros.
They should not be used for constant variables.
E.g. your WORLD_WIDTH should be worldHeight et cetera

The other naming styles find various different uses in different styles,
but Arduino code (and by extension Arduboy code) tends to follow a pattern of Pascal case for type names and camel case for functions, arguments and variables.
E.g. your playerx should be playerX et cetera

Naming

Naming is incredibly important in programming.
If you pick good names then your code will be very self-explanatory.
Picking poor names can leave other people screwing their faces up because they don’t understand your code.

In your case most of your names are generally good.
There’s just one or two places where it could be improved.
E.g. playerSprite rather than player, backgroundTile rather than background.

Multiple files

Don’t be afraid to spread your code over multiple files.
You might not be used to using header files yet,
but as your code grows it’s very important to use multiple files.

In your case you could benefit a lot from offloading your images to a separate header.
The majority of Arduboy games keep their images in a separate file.
(Or many separate files, sometimes in a separate directory.)

The idea is to keep related stuff grouped together and reduce the need to do a lot of scrolling.

constexpr

First thing’s first, let me introduce you to my good friend constexpr.
If you mark a variable constexpr it becomes constant.
From then on the value cannot be altered in any way.

constexpr int worldWidth = 512;
constexpr int worldHeight = 256;

Some people try to use macros or const variables for constants,
but both of those are a bad option for different reasons.

Why you shouldn't use macros for constants

Many people try to use #defines (i.e. macros) to represent constants.
This is considered bad form in the C++ world.
Macros are evil and should be avoided like the plague.
(They do have uses, but they should be used very sparingly.)
(See also: 1, 2 and 3.)

Why you shouldn't use const variables for constants

Other people try to use const for constants,
but const doesn’t actually mean ‘constant’.
const actually means ‘read only’ (and in the early days of C++ it was actually called readonly).
A variable marked const can (despite what some people think) be modified.
There’s an (evil) language feature called const_cast that allows someone to get rid of the constness of a variable, which can lead to all sorts of chaos.

However, const variables aren’t evil, they’re still a good thing.
The word const is a promise.
It’s a promise that “I will not modify this object”.
However, it is not a promise that “other code will not modify this object”,
which is why it must be used wherever possible.
The more things that promise not to modify an object,
the more sure you can be of when the object will and won’t change.

One of the big advantages of constexpr is that you can use them to declare arrays:

constexpr size_t arraySize = 10;
uint8_t array[arraySize];

Which you can’t do with const variables.
You can do it with macros to, but macros are evil.

This chapter of the learncpp tutorial discusses const and constexpr:
https://www.learncpp.com/cpp-tutorial/const-constexpr-and-symbolic-constants/

size_t

size_t (std::size_t in the C++ stdlib) is the type that can be used to represent the size of an object (in bytes) and is the type most commonly used for array indexing and loop counting.

I was going to suggest using it for your world size, but I see your code allows negative coordinates and size_t is unsigned so you’d have to be constantly converting it to int.

However, you could still get used to using it in your for loops.
(Or at least use uint8_t.)

More about `size_t`

Warnings

The compiler will actually warn you about things you’re doing that are potentially bugs or could do things you don’t expect.

Unfortunately the Arduino IDE turns these off by default and you have to manually enable all warnings.

If you go to File >Preferences (or press ctrl and ,) then you can turn all warnings on:

Ironically warnings are actually even more important on Arduino because the Arduino IDE turns on a compiler flag called -fpermissive by default.

About fpermissive

-fpermissive relaxes the language rules and allows a lot of things that are normally illegal to be permitted.
It makes programming less scary for beginners because it reduces the frequency of errors, but as a result it can have catestrophic side effects on the code that cause all sorts of weird and unusual bugs.
Ironically you’d have to be very well versed in C++ to actually understand why some of those bugs are happening, so really they’re just making programming harder for people, not easier.

Currently you have a handful of particularly notable warnings.
For example:

Rocks.ino:246:73: warning: invalid conversion from 'uint16_t* {aka unsigned int*}' to 'int16_t {aka int}' [-fpermissive]

      if (arduboy.collide((Point){laserx, lasery}, (Rect){rockx,rocky,8,8})){

Ordinarily this would be an outright error and your code wouldn’t compile,
however thanks to that pesky -fpermissive flag your code is compiling and doing something rather unexpected.

What’s going on is that rockx is an array of int16_t,
which means it decays to int16_t * (a pointer to int16_t) when it’s passed to a function,
and (thanks to -fpermissive) the pointer is being converted into an int16_t.

So basically instead of the x coordinate of a rock, you’re passing the address of the array of your rocks’ x coordinates.

Pretty much all your warnings are basically this same mistake repeated in several different places.

(I’ll show you how to use collide in another comment. You’re almost there.)

Increment and decrement

A lot of people use ++ and -- mixed in with expressions,
like in arduboy.drawPixel(laserx++, lasery,1); or someArray[index++].
But I advise against this.
If you can, always put an increment or decrement on a separate line and try to stick to preincrement/predecrement.
E.g.

arduboy.drawPixel(laserx, lasery, 1);
++laserx;

If you aren’t aware of what the possible issues could be,
have a read of this:

Your brain should explode after a few paragraphs, at which point you’ll realise it’s not worth remembering all the rules and you’ll just stick to using preincrement/predecrement on a separate line because at least you’ll be able to say immediately what’s going on. :P

Use spaces after commas

It’s not a big deal, but using spaces after commas can make your code look a lot neater and generally makes it easier to find things you’re looking for and/or take code in.

For example, instead of:

sprite.drawErase(rockx,rocky,rock,0);

Do:

sprite.drawErase(rockx, rocky, rock, 0);

Grammar is important,both in English and in programming.
Without spaces after commas in written language,things wouldn’t look right,and they would be harder to read.
Itwouldbeevenworseifwedidn’thaveanyspacesatall.

for loop

I’ve mentioned this before.
You’re still using a global variable as a for loop counter.
(x in setup and half way down loop.)
The first part of the for loop exists purposely to allow you to make a variable completely local to the for loop.

It’s not just a matter of readability, it can affect performance too because the value has to be written back into the global variable (in RAM).

If the loop variable is local then often a register will be used instead of RAM.
(A register is a very small, very fast piece of memory built directly into the CPU chip.
If you want to know more about computer architecture, ask me in a PM or ask in another thread)

Sprites

You don’t actually need a Sprites object.
Instead of doing sprites.drawPlusMask (et cetera) you can just do Sprites::drawPlusMask (et cetera).

This is because drawPlusMask is a static member function.
(I linked to learncpp because it’s got a more friendly and more useful explanation than cppreference on this occasion.)

(Strictly speaking this is a matter of preference, but most of the people I’ve worked with do it this way.)

structs and classes

There are indeed, and making the jump to being able to use structs and classes is probably one of the best things you could do to improve your code readability.

(I’m going to just say ‘classes’ from now on, but I mean both structs and classes, because they’re effectively the same thing. There’s actually only one difference between them.)

Classes can really improve your code’s readability.
Without classes you’re effectively working with a cluster of entirely different variables that just happen to have similar names.
E.g. there’s nothing to say that playerX and playerY are two halves of the same coordinate.

When you start using classes you end up working with actual objects.
E.g. instead of having playerX and playerY you have player.x and player.y, where the x and the y are properties of the same coordinate object.

And when your code is using objects, it resembles the idea of the program better.
For example, instead of talking about playerX and playerY,
you can start talking about the player’s position because the player’s position becomes a single entity.

Consider this:

// Define the Player class/struct
struct Player
{
	// Point is a struct itself, containing an x and a y
	Point position;
	uint8_t const * sprite;
	uint8_t const * mask;
};

Player player;

From there on out you can write player.sprite, player.mask, player.position, player.position.x and player.position.y to refer to individual aspects (or more technically ‘member variables’) of the player object.
It allows you to do things like

Point playerOldPosition = player.position;
player.position = Point(4, 5);

Instead of having to do

int16_t playerOldX = playerX;
int16_t playerOldY = playerY;
playerX = 4;
playerY = 5;

However, in this case the resulting code is actually the same.
The C++ philosophy believes in “zero cost abstraction” and “you only pay for what you use”,
and thus using objects is almost always just as cheap as not using objects.
(This is not the case for some languages.)

If you want another explanation of objects and classes I can provide another one, but this comment is already far too long and I’ve spent far too long writing it.


I’ve probably said too much and bombarded you with stuff, so I’ll stop there.

I’ll get to answering your other questions later though.

4 Likes
(Andrew Crawford) #3

Ah, thanks so much for such a detailed response! I spent all of last night working on a second game as well and there are a few spots it acts a little funny (so naturally that seems to reach a point where I can no longer add or change anything without impassible issues due to software bloat) and just from a cursory read through your response a bunch of things are popping right out at me.

And yeah, I did try to localize as many of my variables as I could, just missed the one halfway through the loop, but for some reason the one in setup wouldn’t compile when I tried to localize it (which I would think is just related to general disorganization, convention, or structure). Also my only reason for not putting my bitmaps in separate header files (at the very least) was because I’m programming and testing off the ABE until mine arrives in the mail, but I have been compiling on both because I have noticed some errors on the arduino IDE that didn’t show up in the ABE.

I was also still just a bit confused as to how to access classes because the way I ended up implementing it wasn’t as clear (and evidently wasn’t even correct given the issue with the rock arrays you mentioned). But that clarifies a lot and I’ll definitely sit on that for the next day or so and give reorganizing it a better go, thanks again, really appreciate it!

1 Like
(Pharap) #4

I’ve noticed the thread but haven’t had chance to read it yet.

There’s always a reason, but I wouldn’t be able to tell without knowing exactly what you changed.

I know there were two loops that needed localising, so it’s possible you removed the global after fixing the one in setup and forgot to fix the other one.

Fair enough, though I think ProjectABE does have a way it can accept multiple files.
(I hardly ever use ProjectABE so I don’t know.)

Ironically you’ve already been using two.
Arduboy2 and Sprites are classes.

Every time you do arduboy.something you’re accessing a member of an Arduboy2 object.
E.g. arduboy.display() calls a member function.

Admittedly though, there’s a difference between making use of classes written by other people and writing your own.


To clarify, Point already exists in the Arduboy2 library.
It’s defined here:

Though I think you might have already known that.

1 Like
(Andrew Crawford) #5

Oh no worries, I wouldn’t have expected anyone to have a look at it yet, really. I have so much catching up to do anyway as evident by this first one that it’ll be more worth it once I get things reorganized anyway (and actually just in this time I went back and fixed the naming conventions so far and it fixed one of the bugs).

And I was aware of the point structure, just couldn’t put together how to use it, but with all of that in mind that definitely makes sense now (and helps me to see a bit more how I might implement my own).

And you’re right, the ABE has a way you can have multiple files for headers, im just not sure how to change between different programs so I’ve been copy and pasting each game when I want to work on something different.

Thanks again!

1 Like
(Pharap) #6

For the most part, you can use classes the same way you’d use variables of the ‘fundamental types’ (int, float et cetera).
(Note that when I say ‘classes’ and ‘class types’ I’m talking about structs and classes again. They’re actually the same thing.)

Declare like:

Point point;

Assign like:

point = otherPoint;

What’s different to fundamental types are members and constructors.

As you presumably know now, you access members like so:

// arduboy.pressed is a member function
if(arduboy.pressed(RIGHT_BUTTON))
{
	// point.x is a member variable 
	++point.x;
}

You can only do that with class types, not fundamental types.

Construction is a bit different though.

There are many different ways to construct an object,
all with different rules and caveats,
but instead of going into all the ins and outs of them I’m just going to suggest you use one of two ways.

The first option:

Point point = Point(4, 5);

It looks like a function call, and that’s because it actually is a function call.
A class’s ‘constructor’ is actually a special function that has to be called to create an object.

The second option is to use the newer C++11 ‘uniform initialisation syntax’, like so:

Point point { 4, 5 };

I’ve been used to doing the former way for years,
but I’m trying to get used to using ‘uniform initialisation syntax’ because it has various benefits.
(Some of the benefits are explained here, but it’s pretty boring technical stuff.)

Avoid just declaring an object like this:

Point point;

Because for some types (like Point) this actually leaves some of the members with ‘junk’ values.

I won’t go into why unless you really want to know now because it’s pretty boring and technical and probably won’t make much sense until you’re at the point where you’re writing your own constructors.

And finally, avoid constructing an object like this:

Point point(4, 5);

It’s legal, but it looks like a function.

In fact it actually looks too much like a function.
So much so that it can cause a problem for the compiler where it actually gets mistaken for a function declaration.
This is a well known problem with a fun name: the “most vexing parse” problem.
It’s such an infamous problem that hardly anyone ever uses this form of construction.

2 Likes
(Andrew Crawford) #7

Oh, alright! Now I see why I was getting a bit mixed up with points (and probably rectangles as well)… I had seen the different syntax without any correlation between them and it got a little jumbled, but that makes perfect sense, thanks again!

I’ll go back through and fix both programs regardless, but I’m thinking this will especially help a lot with the parallax scrolling as I noticed the value I used for each layer was adding up the further it moved along an axis (1,2,3 becomes 2,4,6, etc.) and I would think that changing these to classes and making the variables constant then trying to restructure around that (as opposed to everything being more haphazardly related) should make the programming a little more sound, too. Thanks again!

1 Like
(Andrew Crawford) #8

Alright, sorry it took me a little bit to get to it but I got the kinks worked out of the second game (once I added classes some problems popped up, just wanted to iron those out so I had everything straight). But here’s the first game (the one in this thread) with classes added… for some reason I was still having some difficulty with points but i figured it probably wouldnt be too difficult to change to that later. Here it is for now:

// See: https://mlxxxp.github.io/documents/Arduino/libraries/Arduboy2/Doxygen/html/
#include <Arduboy2.h>
Arduboy2 arduboy;

struct Player {
  int playerX;
  int playerY;
};

struct MapXY {
  int mapX;
  int mapY;
};

struct Crashedship {
  int crashedshipX;
  int crashedshipY;
};

Player player;
MapXY mapXY;
Crashedship crashedship;

constexpr int worldWidth = 256;
constexpr int worldHeight = 128;
uint16_t rockX[50];
uint16_t rockY[50];
uint8_t const *playerSprite = nullptr;
uint8_t const *playerMask = nullptr;

PROGMEM const unsigned char backgroundTile[] = {
  // Bitmap Image. No transparency
  // Width: 10 Height: 16
  8, 8,
  0x00, 0x08, 0x80, 0x00, 0x01, 0x20, 0x04, 0x02, 0x04, 0x00,
  0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
};

PROGMEM const unsigned char crashedshipSprite[] = {
  // Bitmap Image. No transparency
  // Width: 30 Height: 24
  30, 24,
  0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x04, 0x08, 0x70, 0x90, 0x10, 0x30, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x0B, 0x17, 0x2E, 0x5C, 0xB8, 0x70, 0xE1, 0xC2, 0x82, 0x03, 0x04, 0x08, 0x90, 0x20, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0C, 0x0E, 0x0F, 0x0F, 0x0E, 0x0F, 0x0F, 0x0F, 0x0E, 0x0F, 0x0F, 0x0E, 0x0C, 0x0D, 0x0F, 0x0E, 0x0C, 0x08, 0x08, 0x00,
};

PROGMEM const unsigned char rock [] = {
  // Sprite: Image + Mask
  // Width: 8 Height: 64
  8, 8,
  0x3C, 0xFF, 0x5A, 0xFF, 0xF3, 0xFF, 0xCB, 0xFF,
  0xBE, 0xFF, 0xE7, 0xFF, 0x7A, 0xFF, 0x3C, 0xFF,
};

PROGMEM const unsigned char playerDown[] = {
  // Sprite: Image + Mask
  // Width: 11 Height: 24
  11, 17,
  //frame 0
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  //frame 1
  0x00, 0xE0, 0x10, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x03, 0x05, 0xFF, 0xC2, 0xFF, 0xB2, 0xBF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  //frame 2
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  //frame 3
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x10, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xBF, 0xB2, 0xFF, 0xC2, 0xFF, 0x0A, 0x07, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

PROGMEM const unsigned char playerdownMask [] = {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
};

PROGMEM const unsigned char playerUp[] = {
  // Bitmap Image. No transparency
  // Width: 11 Height: 24
  11, 17,
  //frame 0
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFE, 0xC2, 0xFF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  //frame 1
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x10, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xBF, 0xB2, 0xFE, 0xC2, 0xFF, 0x0A, 0x07, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  //frame 2
  0x00, 0xE0, 0x90, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFE, 0xC2, 0xFF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  //frame 3
  0x00, 0xE0, 0x10, 0xDC, 0x1E, 0x1E, 0x1E, 0xDC, 0x90, 0xE0, 0x00,
  0x00, 0x03, 0x05, 0xFF, 0xC2, 0xFE, 0xB2, 0xBF, 0x02, 0x01, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

PROGMEM const unsigned char playerLeft[] = {
  // Sprite: Image + Mask
  // Width: 11 Height: 192
  11, 17,
  //frame 0
  0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x03, 0xFF, 0xC2, 0xFF, 0x03, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  //frame 1
  0x00, 0x00, 0x00, 0xFC, 0x5E, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x03, 0x02, 0xFF, 0xC2, 0xFF, 0x7F, 0x70, 0x20, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  //frame 2
  0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x03, 0xFF, 0xC2, 0xFF, 0x03, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  //frame 3
  0x00, 0x00, 0x00, 0xFC, 0x5E, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00,
  0x00, 0x08, 0x14, 0x27, 0xFB, 0xC7, 0xFF, 0x03, 0x03, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};


PROGMEM const unsigned char playerRight[] = {
  11, 17,
  //frame 0
  0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x03, 0xFF, 0xC2, 0xFF, 0x03, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  //frame 1
  0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0x5E, 0xFC, 0x00, 0x00, 0x00,
  0x00, 0x20, 0x70, 0x7F, 0xFF, 0xC2, 0xFF, 0x02, 0x03, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  //frame 2
  0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0xDE, 0xFC, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x03, 0xFF, 0xC2, 0xFF, 0x03, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  //frame 3
  0x00, 0x00, 0x00, 0xFC, 0xDE, 0x9E, 0x5E, 0xFC, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x03, 0x03, 0xFF, 0xC7, 0xFB, 0x27, 0x14, 0x08, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

PROGMEM const unsigned char playerleftMask[] = {
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
};

void setup() {
  arduboy.boot();
  //ship, player, and rock locations
  arduboy.initRandomSeed();
  crashedship.crashedshipX = random(10, 80);
  crashedship.crashedshipY = random(10, 30);
  player.playerX = 20 + crashedship.crashedshipX;
  player.playerY = 5 + crashedship.crashedshipY;
  playerSprite = playerUp;
  for (int x = 0; x < 25; x++) {
    rockX[x] = random(0, 252);
    rockY[x] = random(0, 124);
  }
  arduboy.setFrameRate(25);
  arduboy.clear();
}

uint8_t frame = 0;

void loop() {
  //laser x and y origin
  int laserX = 5 + player.playerX;
  int laserY = 5 + player.playerY;

  if (!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();
  //drawing background tiles
  for (int x = 0; x < worldWidth; x = x + 8 ) {
    for (int y = 0; y < worldHeight; y = y + 8 ) {
      Sprites::drawOverwrite(x + mapXY.mapX, y + mapXY.mapY, backgroundTile, 0 );
    }
  }
  //player movement and animations
  if (arduboy.pressed(LEFT_BUTTON)) {
    ++mapXY.mapX;
    playerSprite = playerLeft;
    playerMask = playerleftMask;
    ++frame;
    if (frame > 3) frame = 0;
  }
  if (arduboy.pressed(RIGHT_BUTTON))  {
    --mapXY.mapX;
    playerSprite = playerRight;
    playerMask = playerleftMask;
    ++frame;
    if (frame > 3) frame = 0;
  }
  if (arduboy.pressed(UP_BUTTON)) {
    ++mapXY.mapY;
    playerSprite = playerUp;
    playerMask = playerdownMask;
    ++frame;
    if (frame > 3) frame = 0;
  }
  if (arduboy.pressed(DOWN_BUTTON)) {
    --mapXY.mapY;
    playerSprite = playerDown;
    playerMask = playerdownMask;
    ++frame;
    if (frame > 3) frame = 0;
  }
  if (!arduboy.pressed(UP_BUTTON) && !arduboy.pressed(DOWN_BUTTON) && !arduboy.pressed(LEFT_BUTTON) && !arduboy.pressed(RIGHT_BUTTON)) {
    frame = 0;
  }
  //draw player, ship, and rocks
  Sprites::drawExternalMask(player.playerX, player.playerY, playerSprite, playerMask, frame, 0);
  Sprites::drawSelfMasked(crashedship.crashedshipX + mapXY.mapX, crashedship.crashedshipY + mapXY.mapY, crashedshipSprite, 0);
  for (int x = 0; x < 50; x++) {
    Sprites::drawPlusMask(rockX[x] + mapXY.mapX, rockY[x] + mapXY.mapY, rock, 0);
  }
  //depending on player orientation and A button press, draw laser and check for collision between laser and rocks
  if (playerSprite == playerLeft && arduboy.pressed(A_BUTTON)) {
    for (int x = 0; x < 20; x++) {
      arduboy.drawPixel(laserX, laserY, 1);
      --laserX;
    }
  }
  if (playerSprite == playerRight && arduboy.pressed(A_BUTTON)) {
    for (int x = 0; x < 20; x++) {
      arduboy.drawPixel(laserX, laserY, 1);
      ++laserX;
    }
  }
  if (playerSprite == playerUp && arduboy.pressed(A_BUTTON)) {
    for (int y = 0; y < 20; y++) {
      arduboy.drawPixel(laserX, laserY, 1);
      --laserY;
    }
  }
  if (playerSprite == playerDown && arduboy.pressed(A_BUTTON)) {
    for (int y = 0; y < 20; y++) {
      arduboy.drawPixel(laserX, laserY, 1);
      ++laserY;
    }
  }
  arduboy.display();
}

EDIT: Sorry, dont mean to spam, but one other thing I just changed is the map coordinate system, was always generating from 0,0 down (so the player spawn was only random within that corner), so I offset the map location to the center of the background and the player spawn within it. Didn’t want to post the whole program again but figured that was worth mentioning.

(Andrew Crawford) #9

Really am sorry for all the spam, don’t mean to overwhelm or bother anyone, just been working around a something for a little while now without even thinking about it since I changed things and hadn’t occurred to me I should probably just ask…

But I noticed that I haven’t been allowed to use constexpr variables in my structs when I try to declare and define them, but I can use const, why is this? (Also been working on changing my data types and trying to use signed/unsigned, const, and 8 or 16 bit integers where fit).

Also have been working on going back and making sure that operators are in the appropriate places… was using compound assignment and single operators (and therefore the operands) interchangeably/somewhat recklessly in some places and now that I’m making some things const I can see where some of it might be a little funny, but to use the syntax the compiler would accept might be something like

laserX += 5 = playerX

doesn’t work but

laserX = 5 += playerX

does work, but it was my understanding that x += 5 was the same as x = x + 5, so I just want to be sure I’m doing the math right where I should and am a little confused about that. (Or is this where preincrement/predecament come into play also?)

(Pharap) #10

I accidentally hit ‘post’ early so I’ll just finish addressing this point before I address any of the others.

(I’ve finished the end of the comment now, so you can read it uninterupted.)


Aha, good question. You can use constexpr in structs, but I’ll explain that in a moment.

About const

Firstly, a fun fact about const.
In the early days of C++, const was actually called readonly.
I don’t know if Bjarne Stroustrup has ever explained why he changed to const though.

When it comes to const, remember this:
const does not mean “this is a constant value”.
const means “I promise not to modify this variable”.
So when you use it, you’re promising that you won’t change the value of the const variable.
(And the compiler will enforce that promise.)

However, const does not mean “I promise that nobody else will modify this variable”.
There are ways that other code can change the value,
but usually that can only happen if there’s threading involved,
which you fortunately don’t have to worry about on Arduboy.

const member variables

When you do:

struct SomeStruct
{
	const int someVariable = 5;
};

What actually happens is that someVariable is made a member of the struct,
which means that when you create an instance of the struct,
that instance will have object.someVariable just like it would without the const,
and when the instance is created that member variable will be given the value of 5,
and from then on you won’t be able to change that value (without cheating).

So basically it’s the same as the struct without the const,
except you aren’t allowed to modify the member variable.

When I’m in a position to do so,
I’ll draw a diagram of how a struct is stored in memory,
it should clear a lot of things up.

constexpr in structs

The reason you can’t use constexpr like you can const on a member variable is because constexpr actually does mean “this is a constant”, and it wouldn’t make sense for a member variable (which is part of any instance/object of the struct) to be a constant value.

Instead what you have to do is create a static member variable.
static member variables are associated with the type of the struct,
not with the instances of the struct.
So basically they’re a bit like globals, but they’re not in the global environment.
(It’s actually called the global ‘namespace’, but I don’t want to get into scoping and namespaces yet because that’s another story.)

So basically, you have to write this:

struct SomeStruct
{
	static constexpr int someVariable = 5;
};

From inside the struct you can just call it someVariable,
but from the outside you’d have to access that like this:

int value = SomeStruct::someVariable;

You’ve seen the :: before. I’ve told you to use that for Sprites.
I’ll tell you now that :: is called the ‘scope resolution operator’,
and that all the functions in Sprites are actually ‘static member functions’,
which is why you should use :: with them and why you don’t need an instance of Sprites to use them - being static means they are tied to the type and not any instance of Sprites.

Again, I won’t go into the ins and outs of scoping and namespaces just yet,
but that should be enough of a stepping stone for now.

1 Like
(Pharap) #11

Sort of.

For all the fundamental types, x += y is the same as x = x + y.

However, C++ allows ‘operator overloading’,
which means people can write their own operators.
(It’s a really cool language feature that comes in very handy when creating mathematical classes like vectors.)

But as a result, people aren’t constrained to the standard rules of the operators.
Someone could write a type where += actually multiplies,
or doesn’t do anything at all et cetera.

Usually nobody in their right mind would do something like that, but it happens.
Some people get caught up in how cool a language feature is and then go out of their way to show off how much they (think) they know about the feature.
(I believe the term for such people is ‘script kiddie’.)

Personally I think it’s fine to do that sort of thing in private as a means of learning, as long as nobody does it in a program they want to be taken seriously.

Good to see that you recognise that the difference is important.

I think you’ve made a mistake somewhere.
By the laws of C++ neither should work.

I just checked on the Arduino to make sure it wasn’t another one of -fpermissive's oddities and I got an error from the compiler for both statements.


A word of caution: keep an eye on the warnings.
Mixing signed and unsigned values for arithmetic or comparisons can cause bugs and headaches.

Not that it’s a bad thing. Using the most appropriate type is a good thing.

(Also unsigned types tend to require fewer machine instructions,
which can save precious progmem on the Arduboy.)

1 Like
(Andrew Crawford) #12

Ah, thank you, that is all super helpful! And as for the compound assignment operator placement I was probably just mistaken, sorry, more specifically it had something to do with the placement in a chain of objects in different structs, I just thought what I wrote was a simplified version I had tried (because I was/am pretty tired), but I’m sure the actual parts in the code would be pretty essential for context.

I’d have plenty more reading/reorganization to do before I really need to post any more, but if that comes up as a potential issue or question when I get to making functions then I might ask about it, fair warning :stuck_out_tongue: thanks again!

1 Like
(Pharap) #13

I’m not sure what you mean by that, but I’ll just assume it means “there was some particularly complex expression being used”.

Yeah, context is quite important.

One of the reasons I use this-> is because it makes it clear that the following identifier is a member variable or member function even when taken out of context.

E.g. if I showed someone:

int Point::getX() const
{
	return this->x;
}

They would be able to infer that x is a member variable of Point and it’s implicitly convertible to int (assuming the code compiled).


I will get back to answering your other questions at some point,
but I think some diagrams would be the best way to answer questions involving geometry.

Besides which I’d need to know a few things first:

  • Are your lasers going to be acting like projectiles or are they instantaneous?
  • Will your lasers be constrained to particular angles or will they be able to fire in any direction?
  • Will you be having more than one kind of background tile?
  • Will you be expecting the rocks to repeat as well, or just the background?
  • Do you want the map to loop inifinitely, or just to specified dimensions?

(Possibly some other stuff I might think of later.)

1 Like
(Andrew Crawford) #14

I overhauled the structs in my other game and using a compound assignment operator didn’t really seem applicable anywhere I didn’t already have it (over just a regular operator) so I’m not so concerned with whatever issues I was having.

And I was just thinking of firing the laser in just four directions (up, down, left, right). As for rocks I wanted them to repeat, but since the map is what moves for player movement (and therefore the rocks are already dependent on map location) I am thinking if I repeated the map probably the rocks would also anyway, by association. Wasn’t thinking of any other map tiles (yet). Was just thinking of an infinite loop where the overall map is maybe 256x128 and once you approached the edge limit in any axis it just scrolled to the other end.

But I still have a lot of work to do yet (on this one and my other) before I’m ready to drag too much more out of ya :stuck_out_tongue:

(Pharap) #15

So it’s a rectangular projectile?

They wouldn’t by default.
You could make them repeat, but there’s a problem.

If you want the rocks to be destructible then if you destroy one, that rock will be destroyed on every repetition of the map.

So if you destroyed all the rocks in one chunk of the map,
you’d have destroyed all the rocks on the map
because those other rocks are just ‘shadows’/‘reflections’ of the original rocks.

That is pretty much how it would work,
but you’d have to render the map multiple times.

The maths is a little tricky.

(Andrew Crawford) #16

Okay, so I have (mostly) scrapped this initial game design in favor of reducing as much as I can, and because i would have had to have rewritten most of it anyway using functions, but I now have something thats still very similar, which I was hoping i might be able to merge with my other game (but, worst case scenario, could still use for something else).

All that being said, the most recent thing I did with my other game Ive also implemented here, which is the infinite background looping, and this is something that takes up the most resources and im hoping to slim down but am not sure how. I was thinking image compression might help, although between the two games i would already be at my RAM limitations, unless there was a way to do this that would also help cut down on RAM (and i dont know enough about compression to know).

If just looking into compression and seeing how i might implement it would be the way to go then I can do the legwork myself, im just not sure if thats the direction i should take. Here is my currrent code (sorry for the length, been having difficulty figuring out how to get header files working, but thats something i have been reading about in between posts):

#include <Arduboy2.h>
Arduboy2 arduboy;
uint8_t playerFrame = 0;
constexpr uint8_t worldWidth = 128;
constexpr uint8_t worldHeight = 64;
constexpr uint8_t tileWidth = 128;
constexpr uint8_t tileHeight = 64;
uint8_t const *playerSprite = nullptr;
uint8_t const *playerMask = nullptr;

struct PointXY {
  int x = 0;
  int y = 0;
};

PROGMEM const unsigned char planetBackgroundTile1[] = {
// Bitmap Image. No transparency
// Width: 64 Height: 64
128, 64, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};

PROGMEM const unsigned char ardunautDown[] = {
// Bitmap Image. No transparency
// Width: 7 Height: 8
7, 8,
0x00, 0x18, 0xFE, 0x3B, 0xFE, 0x18, 0x00, 

0x00, 0x18, 0xFE, 0x3B, 0x7E, 0x38, 0x00, 

0x00, 0x18, 0xFE, 0x3B, 0xFE, 0x18, 0x00, 

0x00, 0x38, 0x7E, 0x3B, 0xFE, 0x18, 0x00, 
};

PROGMEM const unsigned char ardunautUp[] = {
// Bitmap Image. No transparency
// Width: 7 Height: 8
7, 8, 
0x00, 0x18, 0xFE, 0x3F, 0xFE, 0x18, 0x00, 

0x00, 0x18, 0xFE, 0x3F, 0x7E, 0x38, 0x00, 

0x00, 0x18, 0xFE, 0x3F, 0xFE, 0x18, 0x00, 

0x00, 0x38, 0x7E, 0x3F, 0xFE, 0x18, 0x00, 
};

PROGMEM const unsigned char ardunautLeft[] = {
// Bitmap Image. No transparency
// Width: 7 Height: 8
7, 8, 
0x00, 0x00, 0x3E, 0xE3, 0x3E, 0x1C, 0x00, 

0x00, 0x48, 0xB6, 0x73, 0xBE, 0x1C, 0x00, 

0x00, 0xC0, 0x3E, 0x2B, 0x76, 0x9C, 0x00, 

0x00, 0x48, 0xB6, 0x73, 0xBE, 0x1C, 0x00, 
};

PROGMEM const unsigned char ardunautRight[] = {
// Bitmap Image. No transparency
// Width: 7 Height: 8
7, 8, 
0x00, 0x1C, 0x3E, 0xE3, 0x3E, 0x00, 0x00, 

0x00, 0x9C, 0x7E, 0x33, 0xB6, 0x48, 0x00, 

0x00, 0x1C, 0xB6, 0x6B, 0x3E, 0xC8, 0x00, 

0x00, 0x9C, 0x7E, 0x33, 0xB6, 0x48, 0x00, 
};

PROGMEM const unsigned char ardunautDownMask[] = {
// Bitmap Image. No transparency
// Width: 7 Height: 8
7, 8, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
};

PROGMEM const unsigned char ardunautRightMask[] = {
// Bitmap Image. No transparency
// Width: 7 Height: 8
7, 8, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
};

PointXY planetBackground1;
PointXY planetBackground2;
PointXY planetBackground3;
PointXY planetBackground4;

void setup() {
  arduboy.boot();
  arduboy.setFrameRate(10);
  arduboy.clear();
  playerSprite = ardunautDown;
}

void loop() {
  if (!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();
  Sprites::drawExternalMask(64,32,playerSprite,playerMask,playerFrame,0);
  drawPlanetBackground();
  handleInput();
  arduboy.print(planetBackground2.y);
  arduboy.display();
}

void playerAnimate() {
  if (playerFrame < 3) {
    ++playerFrame;
  }
  else
    playerFrame = 0;
}

void drawPlanetBackground(){
  //Bottom right(player start)
  for (int x = 0; x < worldWidth; x += tileWidth ) {
    for (int y = 0; y < worldHeight; y += tileHeight) {
      Sprites::drawSelfMasked(x + planetBackground1.x, y - planetBackground1.y, planetBackgroundTile1, 0 );
    }
  }
  //Top right)
  for (int x = 0; x < worldWidth; x += tileWidth ) {
    for (int y = 0; y < worldHeight; y += tileHeight) {
      Sprites::drawSelfMasked(x + planetBackground2.x, y - planetBackground2.y - 64, planetBackgroundTile1, 0 );
    }
  }
  //Bottom left
  for (int x = 0; x < worldWidth; x += tileWidth ) {
    for (int y = 0; y < worldHeight; y += tileHeight) {
      Sprites::drawSelfMasked(x + planetBackground3.x - 128, y - planetBackground3.y, planetBackgroundTile1, 0 );
    }
  }
  //Top left
  for (int x = 0; x < worldWidth; x += tileWidth ) {
    for (int y = 0; y < worldHeight; y += tileHeight) {
      Sprites::drawSelfMasked(x + planetBackground4.x - 128, y - planetBackground4.y - 64, planetBackgroundTile1, 0 );
    }
  }
  //Bottom right limits and reset
  if (planetBackground1.y < -64){
    planetBackground1.y = 64;
  }
   if (planetBackground1.y > 64){
    planetBackground1.y = -64;
  }
  if (planetBackground1.x < -128){
    planetBackground1.x = 128;
  }
  if (planetBackground1.x > 128){
    planetBackground1.x = -128;
  }
  //Top right limits and reset
  if (planetBackground2.y < -128){
    planetBackground2.y = 0;
  }
  if (planetBackground2.y > 0){
    planetBackground2.y = -128;
  }
  if (planetBackground2.x < -128){
    planetBackground2.x = 128;
  }
  if (planetBackground2.x > 128){
    planetBackground2.x = -128;
  }
  //Bottom left limits and reset
  if (planetBackground3.x < 0){
    planetBackground3.x = 256;
  }
  if (planetBackground3.x > 256){
    planetBackground3.x = 0;
  }
  if (planetBackground3.y < -64){
    planetBackground3.y = 64;
  }
   if (planetBackground3.y > 64){
    planetBackground3.y = -64;
  }
  //Top left limits and reset
  if (planetBackground4.x < 0){
    planetBackground4.x = 256;
  }
  if (planetBackground4.x > 256){
    planetBackground4.x = 0;
  }
  if (planetBackground4.y < -128){
    planetBackground4.y = 0;
  }
  if (planetBackground4.y > 0){
    planetBackground4.y = -128;
  }
}

void handleInput(){
  if (arduboy.pressed(DOWN_BUTTON)){
  playerSprite = ardunautDown;
  playerMask = ardunautDownMask;
  playerAnimate();
  ++planetBackground1.y;
  ++planetBackground2.y;
  ++planetBackground3.y;
  ++planetBackground4.y;
}
if (arduboy.pressed(UP_BUTTON)){
  playerSprite = ardunautUp;
  playerMask = ardunautDownMask;
  playerAnimate();
  --planetBackground1.y;
  --planetBackground2.y;
  --planetBackground3.y;
  --planetBackground4.y;
}
if (arduboy.pressed(LEFT_BUTTON)){
  playerSprite = ardunautLeft;
  playerMask = ardunautRightMask;
  playerAnimate();
  ++planetBackground1.x;
  ++planetBackground2.x;
  ++planetBackground3.x;
  ++planetBackground4.x;
}
if (arduboy.pressed(RIGHT_BUTTON)){
  playerSprite = ardunautRight;
  playerMask = ardunautRightMask;
  playerAnimate();
  --planetBackground1.x;
  --planetBackground2.x;
  --planetBackground3.x;
  --planetBackground4.x;
}
if (!arduboy.pressed(UP_BUTTON) && !arduboy.pressed(DOWN_BUTTON) && !arduboy.pressed(LEFT_BUTTON) && !arduboy.pressed(RIGHT_BUTTON)) {
    playerFrame = 0;
  }
}
(Pharap) #17

This is infinite (or as near infinite at least):

You can never have truly infinite because you’re limited by the ranges of the data types you use.

There are other ways to do it, but you have to be clever with your calculations.

If you just want the same background repeated over and over then there’s a relatively simple way to do it.

That said, I’d need to sit down and work out the maths.

Basically the idea is to just use the remainder (i.e. modulo) of the player’s position divided by the the background tile size to figure out where to draw the background tiles.

I think you should look at your architecture before you reach for image compression.

Your images only cut into progmem, they don’t affect RAM.

It depends on how you’re trying to use them.

Start by putting stuff in a header file and then doing #include "NameOfHeaderFile.h" at the top of your main file.

If you encounter errors, post the errors and ask how to fix them.

The only way to know how to fix errors (other than studying the language in depth enough to be able to make sense of the terminology) is to experience them and learn the common causes.


To me, these scream “all these variables have the same value”:

and that would be the case if it weren’t for:

So if you could figure out a way to do that wrapping during the image calculations then you could reduce yourself to just one background offset.


That said,
there should also be a way to just have the ship/camera move and to keep the background static.
(Like I did in the recent StarTerrain example.)

1 Like
(Andrew Crawford) #18

Ah, thank you! Yeah, I had been looking at the star terrain example you posted just thinking it would be easier to implement here (because it doesn’t use parallax) so I figured this would be a better place to start, I just didn’t quite understand it yet, but that definitely helps to clarify, thanks!

(Pharap) #19

Like I say, it could probably be modified to do parallax,
but I’d have to figure out the maths.

If there’s anything about it that you don’t understand, be sure to ask.

Not stopping to make sure you understand what’s going on is like going on a road trip
(or perhaps more aptly, launching a space rocket) without checking your fuel reserves.

1 Like
(Andrew Crawford) #20

So Im not sure why it hadnt just occurred to me to do this sooner, but as the two separate programs are now they share some structs in common and i was curious to see how much it might slim down if i tried to combine the two, and after removing one of the parallax layers (because two still looks just as good) and putting the separate games together it only took up an additional 16 bytes of RAM (which i may be able to slim down further), and that still leaves me with 1216 (which is way more than i thought). So im thinking i can just use what I have.

Id still like to go back to the starTerrain example you posted in the other thread at some point (probably for that procedural generation project once ive worked my way up to it), but for now another reason why id like to keep it more along the lines how it is isnt just for the background draw repetition but for the coordinate repetition also, so it keeps the player restricted to a certain area while making it seem larger.

But incidentally, now I have to separate things into header files or itll get way out of hand real quick… but I started doing that and noticed my sprites werent drawing correctly (the rocket or the astronaut) and it was cutting about half a pixel off one side (in each program separately) and i needed to address that, wasnt sure why this might be happening (but i think it might just be something in the scaling how i exported it, going back to fix that now).