Space Invaders Clone Issue

Hi all,

Quick question but probably not a quick solution. When I test the code out ‘Space Invaders clone’ on Project ABE I can clearly see the Tank Missiles but when I load it into Arduboy they vanish but still blow up the invaders. I cant slow the speed of the missiles down so I can see them, even if I mess with the frame rate. Any ideas - take it easy on my coding… I am completing a tutorials from a few different places, so it probably looks more like a patch work quilt :(.
Any guidance would be great !

Working code in Project ABE with visible missiles

Space Invaders clone.hex (24.5 KB)

    /*   Adapted from Xtronical Space Invaders Tutorial
*/
#include <Arduboy2.h>
Arduboy2 arduboy;

// DISPLAY SETTINGS

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// Input settings
#define FIRE_BUT 7
#define RIGHT_BUT A1
#define LEFT_BUT A2

// Alien Settings
#define NUM_ALIEN_COLUMNS 7
#define NUM_ALIEN_ROWS 3
#define X_START_OFFSET 6
#define SPACE_BETWEEN_ALIEN_COLUMNS 5
#define LARGEST_ALIEN_WIDTH 11
#define SPACE_BETWEEN_ROWS 9
#define INVADERS_DROP_BY 4            // pixel amount that invaders move down by
#define INVADERS_SPEED 20             // speed of movement, lower=faster.
#define INVADER_HEIGHT 8

// Player settingsc
#define TANKGFX_WIDTH 13
#define TANKGFX_HEIGHT 8
#define PLAYER_X_MOVE_AMOUNT 1
#define PLAYER_Y_START 56
#define PLAYER_X_START 0

#define MISSILE_HEIGHT 4
#define MISSILE_WIDTH 1
#define MISSILE_SPEED 1

// Status of a game object constants
#define ACTIVE 0
#define DESTROYED 2

// graphics
// aliens

const unsigned char InvaderTopGfx [] PROGMEM = {
  0x98, 0x5c, 0xb6, 0x5f, 0x5f, 0xb6, 0x5c, 0x98
};

const unsigned char InvaderTopGfx2 [] PROGMEM = {
  0x58, 0xbc, 0x16, 0x1f, 0x1f, 0x16, 0xbc, 0x58
};

const unsigned char PROGMEM InvaderMiddleGfx [] =
{
  0x1e, 0xb8, 0x7d, 0x36, 0x3c, 0x3c, 0x3c, 0x36, 0x7d, 0xb8, 0x1e
};

const unsigned char PROGMEM InvaderMiddleGfx2 [] = {
  0x78, 0x18, 0x7d, 0xb6, 0xbc, 0x3c, 0xbc, 0xb6, 0x7d, 0x18, 0x78
};

const unsigned char PROGMEM InvaderBottomGfx [] = {
  0x1c, 0x5e, 0xfe, 0xb6, 0x37, 0x5f, 0x5f, 0x37, 0xb6, 0xfe, 0x5e, 0x1c
};

const unsigned char PROGMEM InvaderBottomGfx2 [] = {
  0x9c, 0xde, 0x7e, 0x36, 0x37, 0x5f, 0x5f, 0x37, 0x36, 0x7e, 0xde, 0x9c
};

// Player grafix
const unsigned char PROGMEM TankGfx [] = {
  0xf0, 0xf8, 0xf8, 0xf8, 0xf8, 0xfe, 0xff, 0xfe, 0xf8, 0xf8, 0xf8, 0xf8, 0xf0
};

static const unsigned char PROGMEM MissileGfx [] = {
  0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};


// Game structures

struct GameObjectStruct  {
  // base object which most other objects will include
  signed int X;
  signed int Y;
  unsigned char Status;  //0 active, 1 exploding, 2 destroyed
};

struct AlienStruct  {
  GameObjectStruct Ord;
};

struct PlayerStruct  {
  GameObjectStruct Ord;
};

//alien global vars
//The array of aliens across the screen
AlienStruct  Alien[NUM_ALIEN_COLUMNS][NUM_ALIEN_ROWS];


// widths of aliens
// as aliens are the same type per row we do not need to store their graphic width per alien in the structure above
// that would take a byte per alien rather than just three entries here, 1 per row, saving significnt memory
byte AlienWidth[] = {8, 11, 12}; // top, middle ,bottom widths


char AlienXMoveAmount = 1; // norm is 2 , this is pixel movement in X
signed char InvadersMoveCounter;            // counts down, when 0 move invaders, set according to how many aliens on screen
bool AnimationFrame = false; // two frames of animation, if true show one if false show the other


// Player global variables
PlayerStruct Player;
GameObjectStruct Missile;


void setup()
{
  arduboy.begin();
  arduboy.setFrameRate(60);

  InitAliens(0);
  InitPlayer();

  pinMode(RIGHT_BUT, INPUT_PULLUP);
  pinMode(LEFT_BUT, INPUT_PULLUP);
  pinMode(FIRE_BUT, INPUT_PULLUP);

}

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

  Physics();
  UpdateDisplay();

}

void Physics()  {
  AlienControl();
  PlayerControl();
  MissileControl();
  CheckCollisions();
}


void PlayerControl()  {
  // user input checks
  if ((digitalRead(RIGHT_BUT) == 0) & (Player.Ord.X + TANKGFX_WIDTH < SCREEN_WIDTH))
    Player.Ord.X += PLAYER_X_MOVE_AMOUNT;
  if ((digitalRead(LEFT_BUT) == 0) & (Player.Ord.X > 0))
    Player.Ord.X -= PLAYER_X_MOVE_AMOUNT;
  if ((digitalRead(FIRE_BUT) == 0) & (Missile.Status != ACTIVE))
  {
    Missile.X = Player.Ord.X + (6); // offset missile so its in the mideel of the tank
    Missile.Y = PLAYER_Y_START;
    Missile.Status = ACTIVE;

  }
}

void MissileControl()
{
  if (Missile.Status == ACTIVE)
  {
    Missile.Y -= MISSILE_SPEED;
    if (Missile.Y + MISSILE_HEIGHT < 0)  // If off top of screen destroy so can be used again
      Missile.Status = DESTROYED;
  }
}


void AlienControl()
{
  if ((InvadersMoveCounter--) < 0)
  {
    bool Dropped = false;
    if ((RightMostPos() + AlienXMoveAmount >= SCREEN_WIDTH) | (LeftMostPos() + AlienXMoveAmount < 0)) // at edge of screen
    {
      AlienXMoveAmount = -AlienXMoveAmount;             // reverse direction
      Dropped = true;                                   // and indicate we are dropping
    }
    // update the alien postions
    for (int Across = 0; Across < NUM_ALIEN_COLUMNS; Across++)
    {
      for (int Down = 0; Down < 3; Down++)
      {
        if (Alien[Across][Down].Ord.Status == ACTIVE)
        {
          if (Dropped == false)
            Alien[Across][Down].Ord.X += AlienXMoveAmount;
          else
            Alien[Across][Down].Ord.Y += INVADERS_DROP_BY;
        }
      }
    }
    InvadersMoveCounter = INVADERS_SPEED;
    AnimationFrame = !AnimationFrame; ///swap to other frame
  }
}


void CheckCollisions()
{
  MissileAndAlienCollisions();
}


void MissileAndAlienCollisions()
{
  for (int across = 0; across < NUM_ALIEN_COLUMNS; across++)
  {
    for (int down = 0; down < NUM_ALIEN_ROWS; down++)
    {
      if (Alien[across][down].Ord.Status == ACTIVE)
      {
        if (Missile.Status == ACTIVE)
        {
          if (Collision(Missile, MISSILE_WIDTH, MISSILE_HEIGHT, Alien[across][down].Ord, AlienWidth[down], INVADER_HEIGHT))
          {
            // missile hit
            Alien[across][down].Ord.Status = DESTROYED;
            Missile.Status = DESTROYED;
          }
        }
      }
    }
  }
}

bool Collision(GameObjectStruct Obj1, unsigned char Width1, unsigned char Height1, GameObjectStruct Obj2, unsigned char Width2, unsigned char Height2)
{
  return ((Obj1.X + Width1 > Obj2.X) & (Obj1.X < Obj2.X + Width2) & (Obj1.Y + Height1 > Obj2.Y) & (Obj1.Y < Obj2.Y + Height2));
}

int RightMostPos()  {
  //returns x pos of right most alien
  int Across = NUM_ALIEN_COLUMNS - 1;
  int Down;
  int Largest = 0;
  int RightPos;
  while (Across >= 0) {
    Down = 0;
    while (Down < NUM_ALIEN_ROWS) {
      if (Alien[Across][Down].Ord.Status == ACTIVE)
      {
        // different aliens have different widths, add to x pos to get rightpos
        RightPos = Alien[Across][Down].Ord.X + AlienWidth[Down];
        if (RightPos > Largest)
          Largest = RightPos;
      }
      Down++;
    }
    if (Largest > 0) // we have found largest for this coloum
      return Largest;
    Across--;
  }
  return 0;  // should never get this far
}

int LeftMostPos()  {
  //returns x pos of left most alien
  int Across = 0;
  int Down;
  int Smallest = SCREEN_WIDTH * 2;
  while (Across < NUM_ALIEN_COLUMNS) {
    Down = 0;
    while (Down < 3) {
      if (Alien[Across][Down].Ord.Status == ACTIVE)
        if (Alien[Across][Down].Ord.X < Smallest)
          Smallest = Alien[Across][Down].Ord.X;
      Down++;
    }
    if (Smallest < SCREEN_WIDTH * 2) // we have found smalest for this coloum
      return Smallest;
    Across++;
  }
  return 0;  // should never get this far
}

void UpdateDisplay()
{
  arduboy.clear();
  for (int across = 0; across < NUM_ALIEN_COLUMNS; across++)
  {
    for (int down = 0; down < NUM_ALIEN_ROWS; down++)
    {
      if (Alien[across][down].Ord.Status == ACTIVE) {
        switch (down)  {
          case 0:
            if (AnimationFrame)
              arduboy.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderTopGfx, AlienWidth[down], INVADER_HEIGHT, WHITE);
            else
              arduboy.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderTopGfx2, AlienWidth[down], INVADER_HEIGHT, WHITE);
            break;
          case 1:
            if (AnimationFrame)
              arduboy.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderMiddleGfx, AlienWidth[down], INVADER_HEIGHT, WHITE);
            else
              arduboy.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderMiddleGfx2, AlienWidth[down], INVADER_HEIGHT, WHITE);
            break;
          default:
            if (AnimationFrame)
              arduboy.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderBottomGfx, AlienWidth[down], INVADER_HEIGHT, WHITE);
            else
              arduboy.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y,  InvaderBottomGfx2, AlienWidth[down], INVADER_HEIGHT, WHITE);
        }
      }
    }
  }

  // player
  arduboy.drawBitmap(Player.Ord.X, Player.Ord.Y,  TankGfx, TANKGFX_WIDTH, TANKGFX_HEIGHT, WHITE);
  //missile
  if (Missile.Status == ACTIVE)
    arduboy.drawBitmap(Missile.X, Missile.Y,  MissileGfx, MISSILE_WIDTH, MISSILE_HEIGHT, WHITE);

  arduboy.display();
}


void InitPlayer()  {
  Player.Ord.Y = PLAYER_Y_START;
  Player.Ord.X = PLAYER_X_START;
  Missile.Status = DESTROYED;
}

void InitAliens(int YStart)  {
  for (int across = 0; across < NUM_ALIEN_COLUMNS; across++)  {
    for (int down = 0; down < 3; down++)  {
      // we add down to centralise the aliens, just happens to be the right value we need per row!
      // we need to adjust a little as row zero should be 2, row 1 should be 1 and bottom row 0
      Alien[across][down].Ord.X = X_START_OFFSET + (across * (LARGEST_ALIEN_WIDTH + SPACE_BETWEEN_ALIEN_COLUMNS)) - (AlienWidth[down] / 2);
      Alien[across][down].Ord.Y = YStart + (down * SPACE_BETWEEN_ROWS);
    }
  }
}
3 Likes

I tried compiling the code in the Arduino IDE and it worked fine.

Are you using something other than the Arduino IDE?

Your problem could be one of the older versions of the code is stuck on your Arduboy and you’ll need either ‘flashlight’ mode or the reset button to upload something new.

Try uploading a ‘hello world’ program before you next upload a fresh copy of the game so you can be sure that uploading is working properly and you’re definitely uploading something new.

Hi Pharap,

Thank you kindly for responding so quickly! Can I just clarify, you loaded the code in the Arduino IDE and loaded it onto Arduboy and you could see the missiles coming from the Tank? Ooh I’ll try your advice and see if it works! I’ve been using Arduino IDE, Project ABE and my Arduboy.

Best regards!

1 Like

Precisely.

I’d have taken a picture, but my camera doesn’t have any power left at the moment.
That’ll have to wait until it’s charged up a bit first.

2 Likes

Brilliant. Must be my end! I’ll test my Arduboy first thing in the morning. Glad the code is working!

1 Like

My camera is old and naff so it’s not easy to get a decent photo, but hopefully this will suffice as evidence:

Space

(For most of the photos,
the capture delay was long enough that the bullet ended up looking like a strand of spaghetti.)

3 Likes

Thanks Pharap, greatly appreciated! Keep well.

Best regards, Steve

1 Like

You were 100% right. works perfectly! Greatly appreciated.
Steve

1 Like

Pharap,

Sorry to bother you. What is the correct way of explaining the use of the code below in simple terms.

Arduboy2 arduboy;

1 Like

That’s fine, it’s not a bother.

‘In simple terms’ is a bit difficult because there’s certain things you need to know first.

At a minimum you need to know:

  • What a variable is
    • In simple terms, a variable is block of memory used for storing data
  • What a ‘type’/‘datatype’ is
    • In simple terms, a data type specifies what kind of data is stored in memory
      • e.g. an integer, a non-negative integer, a character (e.g. letter, digit, symbol), a coordinate (see Point)

The simplest way to describe the code Arduboy2 arduboy; is probably:

A declaration that defines a variable named arduboy, which has a type of Arduboy2

The important part is understanding what a variable is and what a datatype is.

If you understand those then understanding that writing something following a pattern of <type> <variable>; creates a variable with a specific type isn’t too difficult.

(Though it becomes a little bit more difficult when looking at local variables vs global variables because initialisation rules are different.)

If I might ask, are you writing a tutorial and/or course material, or just looking for simple ways to explain programming concepts to people or just trying to further your own understanding?


For future reference, users don’t get a notification unless you either:

  • Reply to one of their comments
    • (You replied to one of your comments instead of one of mine)
  • Use the ‘@’ sign, e.g. @Steve_Daly, @Pharap

Fortunately I like to read all forum posts if I have time so I didn’t miss your question,
but other people probably don’t do that,
so try to remember to use the ‘@’ in future so the person you want to talk to gets notified.

Thanks Pharap, always so prompt and thorough with your explanation! My knowledge is very limited but I can see that Arduboy2 has many classes, which use the word ‘void’. And when we add arduboy as a variable, we then start using arduboy.clear, so I assume this variable attaches itself to the classes? I understand from the info link we can call this anything we want but historically use arduboy. Does this somehow initialise the library functions, because I understand that the variable (object) must inherit the functions of the class.

I think I might of mentioned, I teach Design & Technology, and built a game console last year for Year 8 students. I desperately tried to get the Computer Science Department on board but there not biting:(. I think it would of made a great cross curricular project so I am determined to push my self forward to try to understand the basics as much as possible. The corona virus means the students can no longer build their game consoles at school BUT can practice programming (basics) using Project ABE. I have been mixing pixel art with basic programming but also teaching my self my doing an available online tutorials. I find it a challenge so I picked tutorials I like, hence Space Invaders using Xtronical. I have converted/transformed most of the code to work on Arduboy, which I have learned a lot but the code is not well laid out, compared to how you guys do it so I am also looking at this as well. There are actually lots of teachers like me who would really love to offer something as exciting as the design, make and programming of a game console to their students but they don’t have the confidence so if I manage to get to a basic level I could support other local teachers as well.

I actually think that without the forum, I would have given up long ago!

As always, best regards,

Steve

1 Like

void is a special keyword that can be thought of as ‘the absence of a type’.

A function with void as a return type doesn’t return anything,
and a pointer to void doesn’t know what type of object it points to.

clear isn’t a variable, it’s a function.

It is related to the arduboy variable, and it requires an arduboy object to work.
I typically wouldn’t describe it as being ‘attached’ to the class,
but it certainly is connected to the class.

In technical terms, clear is a ‘member function’ of the Arduboy2 class.

Also, I think perhaps you’re missing some of what’s going on because you haven’t read about OOP and classes yet.

Essentially each ‘class’ is like a blueprint for an ‘object’.
The ‘class’ dictates what ‘member variables’ an object will have and which ‘member functions’ can be used on that object.

A ‘member function’ behaves like a ‘free function’,
but it has an extra hidden parameter in the form of the ‘this’ pointer,
which refers to the object that it operates on.

I think ultimately member functions probably won’t make much sense until you’ve actually written one,
or at least seen how one is implemented.

That is correct.

I have seen people use ard or game for their Arduboy2 variable,
but arduboy is the preferred name.

The object doesn’t ‘inherit’ the functions of the class.
It’s especially important to avoid the word ‘inherit’ in this case, because ‘inheritance’ is a technical term that means something very different.

The functions don’t need to be ‘initialised’,
they are part of the class definition - as long as the class exists, the functions exist.

An object has access to various member functions simply because of its type.
An object of type Arduboy2 always has access to the Arduboy2 member functions.

In regards to the distinction between ‘object’ and ‘variable’,
the simplest way to explain it is that ‘object’ is the actual data and the ‘variable’ is the block of memory that stores the data.

Conceptually an object can in fact be copied and moved between different variables.
For certain types you may want to copy or move the object,
but it usually wouldn’t make sense to copy or move an Arduboy2 object.

I don’t know if you mentioned this to other people,
but you have indeed mentioned this to me.

My question was more of a way to try to understand what kind of information/explanations you are looking for.
E.g. whether:

  • You understand the concepts yourself but are asking for better ways of explaining the concepts to your students
  • You understand the concepts but are particularly looking for ways to explaing the concepts as part of tutorials/documentation for lesson material
  • You are still trying to understand the concepts yourself
  • A mixture of the above

If you’re only looking for simple ways to explain to your students then my answers will tend to be quite minimalistic because I’d take that as being “how do I word this?”/“how can I explain this concept in simple terms?” rather than “what does this mean?”/“how can I understand this?”.

If, on the other hand, you’re struggling with understanding a particular concept yourself then I’d be happy to go into more detail to make sure you really understand the concept.

(I can even explain things you often won’t find in a tutorial,
like what your code might look like during or after compilation,
and some of the quibbles and caveats of the C++ standard.)

Personally I believe there’s no substitute for actually understanding the language,
but I understand how difficult that is and how daunting it can be.

If the code were open source then I could offer to ‘translate’ the code to something Arduboy specific,
but if it isn’t then I’d have to write something from scratch.

It’s not too bad, I have seen much worse.
(I’m particularly glad that it’s using Allman style braces,
but that’s just my personal preference.)

The thing you’re probably struggling with most is that it’s written for general Arduino so it’s using different APIs and reinventing a few things that are already in the library.
All the language related concepts should be applicable to Arduboy code.

(It’s also doing quite a few things I’m not happy about,
but those things aren’t entirely unexpected.)

I’m thankful it’s not using delay at least.

I was actually saying quite recently that the forum is one of the biggest advantages (alongside the documentation) that the Arduboy has over other consoles.

Even when there’s a lack of tutorials, there’s no lack of helpful people willing to offer advice.


If I haven’t mentioned it yet, please look at the C++ section of my ‘Resource Collection’.

It has lots of useful resources, like learncpp’s C++ tutorial,
which is currently the tutorial I recommend to most people for learning C++ as a language.

Thanks Pharap for the information, I have copied it and will incorporate it into my understanding. At the moment I need to provide basic tutorials to my students (currently working on) but clearly need to gain a deeper understanding of C++ good coding practice as well as fulfill my other duties as a teacher! Everything you have supported me with so far I have downloaded, accessed, kept, so nothing wasted! It just seems to take time to interact/absorb so much information. Great advice on the wording, “how do I word this?”/“how can I explain this concept in simple terms?” rather than “what does this mean?”/“how can I understand this?” I keep a note on this for future reference so you know whether the explanations are for me or for students!

I like Allmans style braces, even though I had never heard of him and had to look it up!
I do use your resource collection and learncpp’s C++ tutorial. Just not enough!

Thank again for all your support. I will get there, just might take a very long time!

2 Likes

Very glad that someone end up “porting” it over.
Looks nice. Sprites are original too.
I had a hard time figuring out how to jam the original 11 x 5 array into the screen. Guess you probably can’t.
Interesting fact: The original game by Taito had the missiles located slightly off the center of the ship by 1 pixel.

1 Like

Thanks. That is an interesting fact, I wonder why the missile was off by one? Just glad to have access to the tutorial from Xtronical. It’s taken me awhile to getting working on Arduboy but worth the hassle and lots of learning along the way.

2 Likes