[WIP] Ardunaut (previously Parallax rocket launch)

I am not sure what you mean by ‘separate game states’. Obviously you mean different parts of the game but are you trying to put them all in the same .ino file or are you planning on splitting them across different classes.

However, I can see your code:

#define GAME_LAUNCH	0
#define GAME_SPACE	1

int gamestate = GAME_LAUNCH;

How did you envisage the various phases of the game being handled? It is typical for people to structure their code soemthing like this:

void loop() {

  if (!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();

  switch (gamestate) {

    case GAME_LAUNCH:
      gameLaunch();
      break;

   case GAME_SPACE:
     gameSpace();
     break;

  }

  arduboy.display();

}

void gameLaunch() {
  .. do stuff

  If (move to game space?) {
    gameState = GAME_SPACE;
  }

These are all in the same program so you shouldn’t have any issues using the functions you have created.

2 Likes

Ohh alright, that makes more sense. I had attempted to do what you showed and forgot to delete the parts at the beginning, but when I tried it I just hadn’t put them in their own separate functions (which is probably
why it wasn’t working). I saw something in the Arduboy magazine (issue 3 page 14) that showed that and was thinking to ask, actually. Thanks!

1 Like

It’s possible to add some extra code so that you could work with a bottom left origin cartesian coordinate system (i.e. by changing the coordinate system before any drawing), but that would obviously cost speed and memory.

@filmote solved your problem, so instead I’ll give you some advice.

If you’ve got a bug and you’re not sure why,
stop looking at it like a programmer and start looking at it like the processor.
If you step through the code line by line, doing what the processor does,
then often you’ll realise what’s actually happening and how you can solve it.

The best way to solve that is to print the variables onto the screen to keep an eye on their values and look for anything out of the ordinary.

Edit
Through using this technique, I’ve discovered an off-by-one error.

As the ship goes up to the point where the background starts scrolling and then drops back down below the background scrolling threshold, background1.y ends up being 1 unit less each time.

It’s probably a logic error.
Most likely an issue with using a <= or >= when an < or > is needed.

As far as I’m aware launchpad and arrow are.

If in doubt, throw a constexpr in front and try to compile.
If they’re not being modified the code will compile.
If they are being modified, you’ll get an error.

This is more or less what I was going to suggest,
but I was also going to introduce enum classes.

@CatDadJynx, if you want I can explain enum classes now.
They’re not particularly hard to understand (which is why I don’t understand why so few people use them).

Yeah, I wouldn’t want to bother at the cost, plus I’m on your guys’ turf now :stuck_out_tongue:

Ah yeah, I actually did this in my other game to figure out how to move the map origin within the appropriate boundaries, just hadn’t thought to do it with this.

And I’ll go ahead and do that for the those features now and see (printing their values), I just didn’t think they were constant because I had to add or subtract the background in order to keep them oriented during movement, was thinking that maybe their values were getting too large (but they’re the same data type as the background position so I wouldn’t think this should happen) but I’ll try to make them constant also and see.

And sure, if it’s a better way of doing things (or even just something more to keep in mind), then by all means! Thanks!

Edit: Sorry, just saw that you caught the bug. Thanks!

1 Like

The values of launchpad are constant.
Adding two numbers together (with + rather than +=) doesn’t modify either of the numbers, it creates a new number.
(Addition is what’s known as a pure function, a good concept to be aware of, though not something you’re required to know.)

I haven’t pinned it down completely, but I’ve narrowed it down.
I know the symptoms, not the cause.

I’ll cover that once I’ve squashed the off-by-one error.


Unrelated: it seems Kevin’s playing with the site’s CSS again. :P

Oh alright, i still wasnt entirely clear on the difference between a single or compound operator, thanks for clarifying. And yeah, i think its either a max height or width thing (or both) in that after a while it seems the player character gets slightly offset lower and lower also so it has something to do with something adding too much.

If you want the really specific difference, then for:

int a = 0;
int b = c;
int c = a + b;
a += b;

The expression a + b would be using the operator with a type of int operator+(int, int),
and the expression a += b would be using the operator with a type of int & operator+=(int &, int).
(Alternatively the types could be int operator+(const int &, const int &) and int & opterator+=(int &, const int &).)

But now I’m just being overly technical on purpose. :P
(I’ll explain the & another day, you won’t need to know about that for quite a while yet.)


Here’s the ‘flight data’ for a single trip:

Rising:
h: 2 p: -2 b: 0
h: 3 p: -3 b: 0
h: 4 p: -4 b: 0
h: 5 p: -5 b: 0
h: 6 p: -6 b: 0
h: 7 p: -7 b: 0
h: 8 p: -8 b: 0
h: 9 p: -9 b: 0
h: 10 p: -10 b: 0
h: 11 p: -11 b: 0
h: 12 p: -12 b: 0
h: 13 p: -13 b: 0
h: 14 p: -14 b: 0
h: 15 p: -15 b: 0
h: 16 p: -16 b: 0
h: 17 p: -17 b: 0
h: 18 p: -18 b: 0
h: 19 p: -19 b: 0
h: 20 p: -20 b: -1
h: 21 p: -20 b: -2
h: 22 p: -20 b: -3
h: 23 p: -20 b: -4

Falling:
h: 22 p: -20 b: -3
h: 21 p: -20 b: -2
h: 20 p: -19 b: -1
h: 19 p: -18 b: -1
h: 18 p: -17 b: -1
h: 17 p: -16 b: -1
h: 16 p: -15 b: -1
h: 15 p: -14 b: -1
h: 14 p: -13 b: -1
h: 13 p: -12 b: -1
h: 12 p: -11 b: -1
h: 11 p: -10 b: -1
h: 10 p: -9 b: -1
h: 9 p: -8 b: -1
h: 8 p: -7 b: -1
h: 7 p: -6 b: -1
h: 6 p: -5 b: -1
h: 5 p: -4 b: -1
h: 4 p: -3 b: -1
h: 3 p: -2 b: -1
h: 2 p: -1 b: -1
h: 1 p: 0 b: -1
h: 0 p: 1 b: -1

Notice the numbers start going out of sync around h=20 on the ‘falling’ half.

I’ve found thee solutions to the problem.

Solution 1 is in gravity:

// if height counter is less than 20 move player, if greater scroll background
if (heightCounter <= 20) {
  movePlayerDown();
}
if (heightCounter >= 20) {
  backgroundScrollUp();
}

// lower height counter
--heightCounter;

Solution 2 is in handleInput:

// raise height counter
++heightCounter;

// if height counter is less than 20 move player, if greater scroll background
if (heightCounter < 20) {
  movePlayerUp();
}
if (heightCounter > 20) {
  backgroundScrollDown();
}

Solution 3 is the same as solution 1, but also doing this in handleInput:

// if height counter is less than 20 move player, if greater scroll background
if (heightCounter <= 20) {
  movePlayerUp();
}
if (heightCounter >= 20) {
  backgroundScrollDown();
}

// raise height counter
++heightCounter;

To make the two ‘symmetrical’.

Ah, thanks! I went ahead and fixed that, now everything works just fine, thanks!

1 Like

I went back and tried to change the arrow to static constexpr and get errors, figured maybe it was because i needed to use the scope resolution operator and tried that and got a different error saying ‘arrow’ is not a class, namespace, or enumeration.

Try this instead:

constexpr Arrow arrow { 61, 73, 67, 58, 58, 64 };

If you’d tried to change the member variables of Arrow into static constexpr member variables then you would have had to change all the arrow. to Arrow:: and your arrow variable would then be redundant.

Instead, if you just mark arrow as constexpr then the Arrow type can still be used for other arrows, but the arrow variable becomes constant.

(I still think Arrow would make more sense as Triangle, since that’s pretty much what it is.)

That’s correct.

The scope resolution operator’s left hand side must be a class,
namespace or enumeration (either enum class or enum).
Arrow is a class (because all structs are actually classes),
but arrow is not because arrow is a variable.

1 Like

Ah, okay, thanks! I just tried referencing your other reply regarding that for the other game but hadnt got any further with it since so just got myself more confused.

Was looking into class enums now though, also (figured it would be more sensible to do my own homework before asking you to explain again) and i can certainly make some sense of it, at least. Im trying to think how to use it in place of/with a gamestate and can kind of see how it might work, just need to do some more reading (and make a bit of a mess first) before giving you any more headaches :stuck_out_tongue:

In that case…

Intro

An enumeration is the proper way of associating names with values.
They’re designed for cases where you have a finite number of related entities (and usually where those entities are mutually exclusive).

For example, you could have:

enum class DayOfTheWeek
{
	Monday,
	Tuesday,
	Wednesday,
	Thursday,
	Friday,
	Saturday,
	Sunday,
};

DayOfTheWeek day = DayOfTheWeek::Monday;

And thus you’d have a way to represent your entities (the days of the week) without any extra effort.

Enumerations are particularly useful for gamestates:

enum class GameState
{
	Launch,
	Space,
};

GameState gameState = GameState::Launch;

Why scoped enums?

There are several reasons why ‘scoped enumerations’ (enum classes) are better than the alternatives.
I won’t bother talking about ‘unscoped enumerations’ (plain enums) because scoped enumerations make them effectively obsolete.
Since you’ve seen the macro-based approach, I’ll compare it to that instead.

Firstly, with the macro-based approach you have to manually select the values:

#define GAME_LAUNCH 0
#define GAME_SPACE 1

Whereas a scoped enumeration means you don’t have to bother:

enum class GameState
{
	Launch,
	Space,
};

Secondly, scoped enumerations are type safe.
That means you can’t do this:

// Compiler error
GameState gameState = 10;

Because you’ll get a compiler error.
But if you use bare ints:

// Oops, 10 isn't a valid game state
int gamestate = 10;
// Now you have a silent, hard to find bug...

Thirdly, scoped enumerations are scoped.
That means you can do this:

enum class Colour
{
	Red,
	Blue,
	Orange,
};

enum class Fruit
{
	Apple,
	Orange,
	Pear,
};

And Colour::Orange will be different from Fruit::Orange, and neither name will clash.
With macros, if you tried to just #define ORANGE twice then the second definition would overwrite the first.
That’s why people who use macros instead of enumerations end up writing stuff like COLOUR_ORANGE and FRUIT_ORANGE.

Following on from the previous point,
using macros you could end up doing:

// Oops, that's not a fruit
int fruit = COLOUR_ORANGE;
// And again, the code compiles with a silent bug...

Finally, some specific benefits over macros:

  • Macros can be redefined (leading to weird bugs),
    whereas enumerations can’t be.
  • Macros don’t actually exist by the time the compiler gets to see the code, because another program (called the preprocessor) substitutes them all before the code is given to the actual compiler
  • Macros are evil, enumerations aren’t :P

What are enums anyway?

Enums may look like magic, and they kind of are (they’re compiler magic),
but underneath the type safety and names they’re actually just plain old integers.

As a result, you are free to choose your own specific values for them:

enum class GameState
{
	Launch = 0,
	Space = 10,
};

But if you don’t, the compiler will just assign them values starting from 0 and increasing with each new name (‘enumerator’).

enum class GameState
{
	Launch, // This is 0
	Space, // This is 1
	// The next value will be 2
};

You can also choose an integer type to use as the ‘underlying type’ of the enumeration, which will dictate the enumeration’s size.
Very handy on Arduboy, because you can specify uint8_t (or char, or unsigned char et cetera) to ensure that your enumeration values only take up 8 bits.

enum class GameState : uint8_t
{
	Launch,
	Space,
};

// gameState will use as much RAM as a uint8_t, which is 1 byte
GameState gameState = GameState::Launch;

I think one of the reasons people don’t use enumerations often is because they think they somehow use more memory.

The enumerators themselves don’t consume any memory,
only the variables consume memory,
so the final code will be exactly the same size as if you had just been doing:

#define GAME_LAUNCH 0
#define GAME_SPACE 1

uint8_t gamestate = GAME_LAUNCH;

And then some people ask “if the final code is the same then what’s the point?”.

The point is what I pointed out earlier:

// Compiler error
GameState gameState = 10;

Type safety is very important and very useful.
It’s very good for preventing bugs (especially if you’re tired and keep misspelling things).

2 Likes

Ah, thank you, that definitely makes more sense! So would I just do the switch case/gamestate part the same and substitute it or would i substitute it for switch case entirely? Sorry, just want to be sure im following

1 Like

Nevermind, I got it. No sure if its entirely correct but it works :stuck_out_tongue: Here it is so you can take a look:

#include <Arduboy2.h>
Arduboy2 arduboy;

uint16_t heightCounter = 0;

enum class GameState : uint8_t
{
	Launch,
	Space,
};

GameState gameState = GameState::Launch;

struct Timer {
  unsigned long currentMillis;
  unsigned long previousMillis = 0;
};

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

struct Launchpad {
  int x1;
  int y1;
  int x2;
  int y2;
};

struct FuelBar {
  uint16_t x;
  uint16_t y;
  uint8_t height;
  uint8_t width;
  uint16_t rate;
};

struct Arrow {
  uint16_t x1;
  uint16_t x2;
  uint16_t x3;
  uint16_t y1;
  uint16_t y2;
  uint16_t y3;


};

Timer timer;
PointXY playerXY;
PointXY background1;
PointXY background2;
PointXY background3;
FuelBar fuelBar { 0, 20, 30, 5, 2000 };
constexpr Launchpad launchpad { 61, 58, 73, 58 };
constexpr Arrow arrow { 61, 73, 67, 58, 58, 64 };
constexpr int worldWidth = 128;
constexpr int worldHeight = 128;
constexpr int tileSize = 64;
uint8_t const *playerSprite = nullptr;

PROGMEM const unsigned char playerUp[] = {
  // Bitmap Image. No transparency
  // Width: 9 Height: 9
  9, 9,
  0x00, 0x00, 0xE0, 0x7C, 0x47, 0x7C, 0xE0, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  0x00, 0x00, 0xE0, 0x7C, 0xC7, 0x7C, 0xE0, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  0x00, 0x00, 0xE0, 0x7C, 0xC7, 0x7C, 0xE0, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,

  0x00, 0x00, 0xE0, 0x7C, 0xC7, 0x7C, 0xE0, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

PROGMEM const unsigned char playerLeft[] = {
  // Bitmap Image. No transparency
  // Width: 9 Height: 16
  9, 9,
  0x01, 0x06, 0x2A, 0x74, 0xA8, 0x1C, 0x08, 0x10, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  0x01, 0x06, 0x2A, 0x74, 0xA8, 0x3C, 0x48, 0x10, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  0x01, 0x06, 0x2A, 0x74, 0xA8, 0x3C, 0x48, 0x90, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  0x01, 0x06, 0x2A, 0x74, 0xA8, 0x3C, 0x48, 0x10, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

PROGMEM const unsigned char playerRight[] = {
  // Bitmap Image. No transparency
  // Width: 9 Height: 16
  9, 9,
  0x00, 0x10, 0x08, 0x1C, 0xA8, 0x74, 0x2A, 0x06, 0x01,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  0x00, 0x10, 0x48, 0x3C, 0xA8, 0x74, 0x2A, 0x06, 0x01,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  0x00, 0x90, 0x48, 0x3C, 0xA8, 0x74, 0x2A, 0x06, 0x01,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  0x00, 0x10, 0x48, 0x3C, 0xA8, 0x74, 0x2A, 0x06, 0x01,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

PROGMEM const unsigned char backgroundTile1[] = {
  // Bitmap Image. No transparency
  // Width: 64 Height: 64
  64, 64,
  0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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 backgroundTile2[] = {
  // Bitmap Image. No transparency
  // Width: 64 Height: 64
  64, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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 backgroundTile3[] = {
  // Bitmap Image. No transparency
  // Width: 64 Height: 64
  64, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

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

uint8_t playerFrame = 0;

void loop() {

  if (!arduboy.nextFrame()) {
    return;
  }

  timer.currentMillis = millis();
  arduboy.clear();
  
  switch (gameState) {
    case GameState::Launch:
      AtLaunch();
      if (heightCounter >= 110){
        gameState = GameState::Space;
      };
      break;

    case GameState::Space:
      InSpace();
      break;
  }
  arduboy.display();
}

void gravity(){
  if (!arduboy.pressed(UP_BUTTON) && heightCounter >= 1) {
    //if  height counter is less than 20 move player, if greater scroll background
    if (heightCounter <= 20) {
      movePlayerDown();
    }
    if (heightCounter >= 20) {
      backgroundScrollUp();
    }
    //lower height counter
    --heightCounter;
  }
}

void handleInput(){
  //movement
  //if up button pressed
  if (arduboy.pressed(UP_BUTTON)) {
    //if height counter is less than 20 move player, if greater scroll background
    if (heightCounter <= 20) {
      movePlayerUp();
    }
    if (heightCounter >= 20) {
      backgroundScrollDown();
    }
    //if time elapsed is greater than fuel rate, lower bar
    if ((timer.currentMillis - timer.previousMillis >= fuelBar.rate && fuelBar.height >= 1)) {
      ++fuelBar.y;
      --fuelBar.height;
      timer.previousMillis = timer.currentMillis;
    }
    //raise height counter
    ++heightCounter;
    //change player sprite and frame
    playerSprite = playerUp;
    playerAnimate();
  }
  //if left button is pressed and background is less than 75, scroll background and animate sprite
  if (arduboy.pressed(LEFT_BUTTON)) {
    backgroundScrollRight();
    playerSprite = playerLeft;
    playerAnimate();
  }
  //and right
  if (arduboy.pressed(RIGHT_BUTTON)) {
    backgroundScrollLeft();
    playerSprite = playerRight;
    playerAnimate();

  }
  //if no buttons are pressed, frame for player animation is 0
  if (!arduboy.pressed(UP_BUTTON) && !arduboy.pressed(DOWN_BUTTON) && !arduboy.pressed(LEFT_BUTTON) && !arduboy.pressed(RIGHT_BUTTON)) {
    playerFrame = 0;
  }
}

void movePlayerUp() {
  --playerXY.y;
}

void movePlayerDown() {
  ++playerXY.y;
}

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

void drawBackground() {
  //For each row in the column, draw background
  for (int x = 0; x < worldWidth; x += tileSize ) {
    for (int y = 0; y < worldHeight; y += tileSize) {
      Sprites::drawSelfMasked(x + background1.x, y - background1.y - 75, backgroundTile1, 0 );
      Sprites::drawSelfMasked(x + background2.x, y - background2.y - 200, backgroundTile2, 0 );
      Sprites::drawSelfMasked(x + background3.x, y - background3.y - 300, backgroundTile3, 0 );
    }
  }
}

void backgroundScrollLeft(){
  if (background1.x >= -75) {
    background1.x -= 1;
    background2.x -= 2;
    background3.x -= 3;
  }
}

void backgroundScrollRight(){
  if (background1.x <= 75) {
    background1.x += 1;
    background2.x += 2;
    background3.x += 3;
  }
}

void backgroundScrollUp(){
  background1.y += 1;
  background2.y += 2;
  background3.y += 3;
}

void backgroundScrollDown(){
  background1.y -= 1;
  background2.y -= 2;
  background3.y -= 3;
}

void drawLaunchpad() {
  arduboy.drawLine(launchpad.x1 + background1.x, launchpad.y1 - background1.y, launchpad.x2 + background1.x, launchpad.y2 - background1.y);
}

void drawPlayer() {
  Sprites::drawSelfMasked(playerXY.x + 63, playerXY.y + 50, playerSprite, playerFrame);
}

void drawArrow() {
  if (heightCounter >= 30) {
    arduboy.fillTriangle(arrow.x1 + background1.x, arrow.y1, arrow.x2 + background1.x, arrow.y2, arrow.x3 + background1.x, arrow.y3);
  }
}

void drawHUD() {
  arduboy.print("alt.");
  arduboy.print('\n');
  arduboy.print(heightCounter);
  arduboy.print("00");
  arduboy.fillRect(fuelBar.x, fuelBar.y, fuelBar.width, fuelBar.height);
}

void drawGame() {
  drawBackground();
  drawPlayer();
  drawHUD();
}

void AtLaunch() {
  drawGame();
  drawLaunchpad();
  gravity();
  handleInput();
  drawArrow();
}

void InSpace() {
  drawGame();
  handleInput();
}

That’s right.

And it’s reminded me of something I forgot to mention.
If you handle enum values in a switch statement and you forget to handle one of the possible values then the compiler will give you a warning (assuming you’ve got warnings set to all).
(And if you don’t want that, there’s a way to disable it.)

Though I’m not entirely sure how well using a state machine to represent launch vs space will work.

If this was going to be made into something more than a demo I’d recommend using proper physics calculations, but if it’s just going to be a demo or something simple then this will suffice.


If you want to see what a physics system can do on the Arduboy,
here’s a demo I wrote a while back:

How do you mean? I was hoping to turn it into something more than a demo, im just trying to piece it out a bit so i have a better idea where my limitations are (so i know what i can plan for) and dont come up against a wall, either.

I picked the game states so I could have Earth, Space, Moon, (maybe more depending) and change values like gravity, launchpad size, etc. as some of the game mechanics, likely. Im hoping to reuse as much as i can to save space so im not sure how much more will really needed added, just also not so sure what all i have to work with yet.

Still not opposed to redoing anything along the lines of whatever you had in mind but have been playing around with the next gamestate and started making a basic menu (in a separate program for testing purposes) just to see what my memory limitations would look like adding all that.

Had to duplicate a player input function because it was dependent on the height counter, had to duplicate the background draw function because at launch it’s offset a certain amount, and had to duplicate the drawHUD function to display distances instead of altitude, but even adding all that it’s only taken up an additional 2 or 3% or program space, plus I can reuse the bulk of the first part for landing on other planets, so I may have a decent bit of space to work with yet.

Like I said, not at all opposed to hearing a better/different way to do it, was mostly just messing around to see what all I had to work with

Pretty excited, I’ve added two gamestates now, one is an intermediary space area and the other is a menu with three options you can move between (earth, moon, and mars), each displaying a little planet preview on the bottom corner (so far just empty circles, the moon white and other two black). You can’t select anything yet but you can exit in and out of the menu, next I will add a button input so that selecting an option will display an arrow pointing to it. Also added three new sprites (and their animations) for displaying player down and all the way left and right

I started out using functions, structs, and enums, but I’m sure my code could do with some clean up before I wanted to bombard everyone with it :stuck_out_tongue: (looking forward to getting my Arduboy soon so I can separate things into header files). At the moment all the new features only added an additional 3% or so of each memory type so thats pretty good! Still leaves me with half. Also still open to doing something different, if anything this just helps familiarize me more with stuff I just learned, nothing I couldn’t backtrack.

1 Like

Sorry, dont need to post pointless updates, just excited on my progress… so far have it set up so player starts at launch, reaches space and can open a menu, move between options and select to target the earth and display the distance, and reenter. Still have a lot of cleanup to do before posting, though (started out using functions and structs from the beginning, but can probably probably refine it more, and could maybe do to go back over it and try to add more enums) so thats what ill do next, but unexpectedly received my Arduboy in the mail today so thats where the rest of my night is going :stuck_out_tongue:

2 Likes

Sorry for the lack of replies, I’ve been a bit busy.

Usually with anything physics based people tend to just mirror real-world physics as much as possible.
So usually they’d simulate actual gravity with gravity falloff so that as the ship moves away from the planet the force of the gravity steadily decreases.

Basically planets are kind of like giant magnets and gravity is the magnetic pull - the closer you are to a planet the more pull there is, but once you’re clear of it there’s no gravity to weigh down on you.
That sort of phenomenon can be modelled with equations or with code.

(There’s probably at least 12 astrophysicists crying right now at my blasphemous analogy.)

A state machine could work, but it’s slightly a less ‘natural’ way to do it.
Admittedly it might be a bit cheaper.
Physics equations can sometimes be a bit processor intensive.

There are ways to do that other than using a state machine,
but for now I think we’ll just wait and see how it goes.

Depending on how you’ve done it there’s probably a way to reduce the duplication.

You’re better off working with actual amounts of bytes.
2-3% of progmem is a lot more bytes than 2-3% of RAM.

Be careful of “I have a hammer, everything is a nail” disease.
Sometimes when people learn a new technique they go a bit over-the-top and start using it everywhere.

Knowing when and where to apply each tool/feature/technique takes practice/experience.

That’s good. Now you can start separating things out into .h files.

1 Like