How do I create and control multiple objects (bullets)?

Continuing the discussion from Make Your Own Arduboy Game: Part 9 - Mapping DinoSmasher:

Good afternoon! I worked hard for several days on the shooting. I set up a bullet that flew to the end of the screen and returned to the player. the problem is that I cannot fire multiple shots in a row. the maximum that I managed to do was two bullets in a row:


int ballx; 
int bally; 
int ballx1;
int bally1;
int b = 0;
int n = 0;

void pull() {

     if (ballx> - 3 && b == 1 && ballx > -1) {  
arduboy.fillRect(ballx, bally , ballsize, ballsize, BLACK);
        ballx = ballx - 1;

     }
if (arduboy.justPressed(B_BUTTON) && n == 0){
b = 1;
ballx = PLAYER_X_OFFSET;
bally = PLAYER_Y_OFFSET + 15;
n = 1;
}

     if (ballx1> - 3 && n == 1 && ballx1>-1) {
arduboy.fillRect(ballx1, bally1 , ballsize, ballsize, BLACK);
        ballx1 = ballx1 - 1;

if (arduboy.justPressed(B_BUTTON) && n == 1){
ballx1 = PLAYER_X_OFFSET;
bally1 = PLAYER_Y_OFFSET + 15;
  
     }

}
}

For clarity, I will explain: my character moves along the coordinates of the screen, so I tried to bind each bullet to the position of the player. I’m sure there is a way to make it more efficient.

#define PLAYER_SIZE      30
int PLAYER_X_OFFSET  =  WIDTH / 2 - PLAYER_SIZE / 2;
int PLAYER_Y_OFFSET  =  HEIGHT / 2 - PLAYER_SIZE / 2;
void walk_room(){
if (arduboy.pressed(LEFT_BUTTON)&& PLAYER_X_OFFSET  > 1) {
    PLAYER_X_OFFSET = PLAYER_X_OFFSET - 1;
   
 

}
if (arduboy.pressed(RIGHT_BUTTON)&& PLAYER_X_OFFSET+ PLAYER_SIZE  < 127) {
    PLAYER_X_OFFSET = PLAYER_X_OFFSET + 1;
     
}
if (arduboy.pressed(UP_BUTTON)&& PLAYER_Y_OFFSET  > 1) {
    PLAYER_Y_OFFSET = PLAYER_Y_OFFSET - 1;
      
}
if (arduboy.pressed(DOWN_BUTTON)&& PLAYER_Y_OFFSET + PLAYER_SIZE  < 63 ) {
    PLAYER_Y_OFFSET =PLAYER_Y_OFFSET + 1;
   
} 
} 

I found information that you need to use an array of bullets, but I have no idea how to implement it … Please, tell me what to do. Thank you:) And sorry for my code,
I know it looks terrible, but I try to be better)

Sorry but i think it’s out of the subject. You should not post it here but open your own subject.
It’s easiest to retrieve it and it’s not reopen an old subject for something not linked with

1 Like

I’ve moved this question to a new topic. @NoobGeek, in the future please try to consider if what you’re posting is still on topic. If it’s not, find a more appropriate topic to post in or start a new one. Thanks.

1 Like

Ok, thanks for the clarification!

1 Like

You are correct that using an array for your bullets is what you should do. I would start by reading about what arrays are and how they are used in C++. Here’s one article to start with:

As the article says, an array is a way of having many “things” (known as objects) of the same type, and being able to refer to any one of them by its number. The “objects” that an array contains don’t have to be only simple variable types (like char or int). They can be structures or classes or even other arrays, as long as all the objects in the array are the same. Arrays can also have more than one “dimension” but for your current requirements, a single dimensional array should be fine.

For each of your bullets (though it looks like you call them balls in the code), you’re currently keeping track of 2 things; the X coordinate and the Y coordinate of the bullet’s position on the screen. It also looks like you have a variable called ballsize that gives the size of the square that you use to draw a bullet/ball.

The Arduboy2 library provides a structure named Rect that can describe the location and size of a rectangle. This is what you need for one of you bullets, so I suggest you use it.

To create a single bullet you create a Rect “object”:

Rect bullet;

You now have an object named bullet that has 4 variables: the x position, the y position, the width and the height. To access any one of these variables you use the object name followed by a dot followed by the variable

bullet.x = 5;
bullet.y = 13;
bullet.width = ballsize;
bullet.height = ballsize;

However, this only gives us a single bullet. To get as many bullets as we want (let’s say 10) we can create an array of bullets.

constexpr uint8_t bullets = 10; // define the number of bullets
Rect bullet[bullets];

To access the variables for any one of the bullets, put its number in square brackets (this is called the “index”) after the name. Note that indexes start at 0 so for 10 bullets they will be numbered 0 to 9.

// first bullet (number 0) is at location 5, 13
bullet[0].x = 5;
bullet[0].y = 13;
bullet[0].width = ballsize;
bullet[0].height = ballsize;

// fifth bullet (number 4 starting from 0) is at location 23, 45
bullet[4].x = 23;
bullet[4].y = 45;
bullet[4].width = ballsize;
bullet[4].height = ballsize;

Note that since width and height are variable, each bullet could be a different size if we wanted.

It’s likely that not all your bullets, or even any, will be in use all the time, so we need a way to indicate if each bullet is currently in use. We could create an array of “in use” flags or create a new structure to add a flag. However, if a bullet is always going to be “on screen” when it’s in use, we can use a “trick”. We can set the x value to -1 to indicate that bullet isn’t in use. To initially set all bullets as being not in use, we can use a for loop. We can also initialise the size of the bullets at this time

constexpr int bulletOff = -1; // define the "bullet not in use" value;

for (uint8_t bulletNum = 0; bulletNum < bullets; ++bulletNum) {
  bullet[bulletNum].x = bulletOff;
  bullet[bulletNum].width = ballsize;
  bullet[bulletNum].height = ballsize;
}

Now if we want to use a bullet, we set its x value to be on screen, and set its y value. (If the bullet size never changes, we only have to set width and height once, otherwise they can be changed as required).

To display all the bullets that are in use, we can use a simple for loop

for (uint8_t bulletNum = 0; bulletNum < bullets; ++bulletNum) {
  if (bullet[bulletNum].x != bulletOff) {
    arduboy.fillRect(bullet[bulletNum].x, bullet[bulletNum].y, bullet[bulletNum].width, bullet[bulletNum].height, BLACK);
  }
}

Since any bullet may or may not be in use, we might need a way to find an unused bullet to use. Here’s a function that will do that:

// return the index of the first unused bullet or return the value of "bullets" (10) if all are in use
uint8_t findUnusedBullet() {
  uint8_t bulletNum;
  for (bulletNum = 0; bulletNum < bullets; ++bulletNum) {
    if (bullet[bulletNum].x == bulletOff) {
      break; // unused bullet found
    }
  }
  return bulletNum;
}

I think I’ll leave it at that for now and you can let us know if you understand so far.

There may be other things you have to keep track of, such as each bullet’s direction. We can discuss this later, if necessary.

3 Likes

Thank you very much for the detailed answer! Now I have enough information to move on. In addition, recently I learned about the existence of Arduboy magazines and now, I hope, I will find many answers there. I really appreciate your help! You are amazing!

1 Like

Hello Scott! I am trying to parse your code, I studied arrays, but no matter how much I try I can’t put together every piece of code that you write into one working code that will make the bullets move. Apparently I don’t have enough practice to make such games, but it is the desire to create games that makes me move on, so I want to ask you to show how the code as a whole should look like, if you have time for it.
Thank you for your patience!

First, when addressing someone on this forum it’s best to use an “at” (@) sign followed by their user name. This way they will likely get an alert (via e-mail or some other way) to let them know about the post. So, to address me, it’s better to use @MLXXXp instead of Scott. No one will take any offence from using their user name instead of any given real name.


Making a bullet move is the same as moving any other object, such as a player. You change its x and/or y position and then draw it.

As you’ve seen in the tutorials, most games control the timing of everything based on the frame rate. For a bullet you might update its position every frame. To make it go faster than the fame rate you can change its x and/or y position by more than 1 per frame. To go slower than the frame rate, you could only change position after two or more frames have elapsed.

Things you have to decide about each bullet are:

  • What puts a bullet in use, to start it moving? This would likely be a button press.
  • Where does it start from? This might be a player.
  • What stops a bullet and takes it out of use? This could be “hitting” something or going off the edge of the screen.
  • In what direction is the bullet going to go? It may be that they all only go in one direction. They may go in the direction the player is currently facing.
  • You will probably also want to do something if a bullet hits something.

Here’s an example sketch, as you requested.

  • The bullets are 3 x 3 pixel squares.
  • There can be a maximum of 5 bullets active at a time.
  • A bullet is “fired” (started) by pressing the B button.
  • The bullets will come from the position of a player. The player is an 8 x 12 rectangle that can be moved up and down the left side of the screen using the UP and DOWN buttons.
  • A bullet will stop, and be set as unused, if it hits a “target” that is on the screen. The target will be a simple rectangle. The bullet will also stop if it goes past the right edge of the screen.
  • Bullets only travel horizontally from left to right.
  • If a bullet hits the target a displayed count will be incremented.
  • After a bullet is fired, you must wait 6 frames before another can be fired.

One of the reasons I suggested using the library’s Rect structure is that this type of structure can be used by the library’s collide() functions, which can be used to detect if a Rect is “colliding” with another Rect (or with a Point structure). We’ll use collide() to detect if a bullet has “hit” the target rectangle. The player will be a Rect as well, so that collide() can be used with it in the future, if necessary.

// Multiple "bullet" demo

// Dedicated to the public domain as per CC0 by Scott Allen
// see http://creativecommons.org/publicdomain/zero/1.0/

#include <Arduboy2.h>

Arduboy2 arduboy;

// Bullet definitions
constexpr uint8_t bulletSize = 3; // Size of a square bullet
constexpr uint8_t bullets = 5;    // Maximum number of bullets
constexpr uint8_t bulletWait = 6; // Minimum frames between firing
constexpr int bulletOff = -1;     // "Bullet not in use" value;

// Player definitions
constexpr uint8_t playerWidth = 8;
constexpr uint8_t playerHeight = 12;

// Target definitions
constexpr uint8_t targetWidth = 8;
constexpr uint8_t targetHeight = 24;
constexpr uint8_t targetX = 70;
constexpr uint8_t targetY = 25;

// Create and init the player. Start Y position at centre
Rect player(0, (arduboy.height() / 2) - (playerHeight / 2),
            playerWidth, playerHeight);

// Create the bullets. They'll be initialised in the code
Rect bullet[bullets];

// Create a target rectangle for the bullets to hit
Rect target(targetX, targetY, targetWidth, targetHeight);

// Hit counter
uint16_t hitCount = 0;

// For counting the minimum number of frames between bullets
uint8_t waitCount = 0;

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

  // Set the text to white with transparent background
  arduboy.setTextColor(WHITE);
  arduboy.setTextBackground(WHITE);

  // Init all the bullets
  for (uint8_t bulletNum = 0; bulletNum < bullets; ++bulletNum) {
    bullet[bulletNum].x = bulletOff;
    bullet[bulletNum].width = bulletSize;
    bullet[bulletNum].height = bulletSize;
  }
}

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

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

  // Handle player movement. Make sure the player stays on screen
  if (arduboy.pressed(UP_BUTTON) && (player.y > 0)) {
    --player.y;
  }
  if (arduboy.pressed(DOWN_BUTTON) &&
      (player.y < (arduboy.height() - playerHeight))) {
    ++player.y;
  }

  // Fire a bullet if B is pressed, 
  if (arduboy.justPressed(B_BUTTON)) {
    if (waitCount == 0) {
      uint8_t bulletNum = findUnusedBullet();
      if (bulletNum != bullets) { // If we get an unused bullet
        // Set the start position. (A positive X indicates bullet in use)
        bullet[bulletNum].x = player.width;
        bullet[bulletNum].y = player.y + 3; // Part way down the player
        waitCount = bulletWait; // Start the delay counter for the next bullet
      }
    }
  }

  // Draw the player
  arduboy.fillRect(player.x, player.y, playerWidth, playerHeight);
  // Draw the target
  arduboy.drawRect(target.x, target.y, targetWidth, targetHeight);

  // Move the bullets
  moveBullets();
  // Handle bullet hits
  checkBullets();

  // draw the bullets
  drawBullets();

  // Display the hit count
  arduboy.setCursor(20, 0);
  arduboy.print(F("HITS: "));
  arduboy.print(hitCount);

  // Decrement the bullet wait count if active
  if (waitCount != 0) {
    --waitCount;
  }

  arduboy.display();
}

// Return the index of the first unused bullet or
// return the value of "bullets" if all are in use
uint8_t findUnusedBullet() {
  uint8_t bulletNum;
  for (bulletNum = 0; bulletNum < bullets; ++bulletNum) {
    if (bullet[bulletNum].x == bulletOff) {
      break; // unused bullet found
    }
  }
  return bulletNum;
}

// Move all the bullets and disable any that go off screen
void moveBullets() {
  for (uint8_t bulletNum = 0; bulletNum < bullets; ++bulletNum) {
    if (bullet[bulletNum].x != bulletOff) { // If bullet in use
      ++bullet[bulletNum].x; // move bullet right
    }
    if (bullet[bulletNum].x >= arduboy.width()) { // If off screen
      bullet[bulletNum].x = bulletOff;  // Set bullet as unused
    }
  }
}

// Check and handle bullets hitting the target
void checkBullets() {
  for (uint8_t bulletNum = 0; bulletNum < bullets; ++bulletNum) {
    if (arduboy.collide(bullet[bulletNum], target)) {
      ++hitCount;
      bullet[bulletNum].x = bulletOff;  // Set bullet as unused
    }
  }
}

// Draw all the active bullets
void drawBullets() {
  for (uint8_t bulletNum = 0; bulletNum < bullets; ++bulletNum) {
    if (bullet[bulletNum].x != bulletOff) { // If bullet in use
      arduboy.fillRect(bullet[bulletNum].x, bullet[bulletNum].y, 
                       bulletSize, bulletSize);
    }
  }
}

Bullets.hex (20.5 KB)

@MLXXXp
Thanks!!! Went to try! * collide () * I have already read it and now I’m trying to implement it into my game. Collisions are much easier when looking at the screen area alone. I don’t understand how to implement it with a moving map yet, but I will try. Perhaps I will come to you soon for help)

Thanks again for the code! I already thought to shoot myself with these bullets …
PS: I will soon learn all the rules of the community))

1 Like

@MLXXXp Now tried to compile your code but gives an error: “there is no matching function to call ‘Rect :: Rect (int, int, const uint8_t &, const uint8_t &)’”
I am starting to suspect that the problem is in my Arduboy2 library, as it is adapted for arduino nano and most likely poorly assembled. Tell me, have you tried to compile the code? I want to understand in which direction to move

It’s probably an older version of the library, before Rect had a constructor that allowed initialisation.

I added the compiled .hex file at the end of the post.

@MLXXXp
I am very embarrassed, sorry, this is my mistake … I should have said in advance which library I am using. Sorry for wasting time ( HEX unfortunately did not start either…
I can not update the library, because I use the console I built myself and it does not work correctly with new libraries.

You can initialise the Rect structures using a different method that will work with the older library

// Create and init the player. Start Y position at centre
Rect player = {0, (arduboy.height() / 2) - (playerHeight / 2),
               playerWidth, playerHeight};

// Create the bullets. They'll be initialised in the code
Rect bullet[bullets];

// Create a target rectangle for the bullets to hit
Rect target = {targetX, targetY, targetWidth, targetHeight};

You’ll also have to delete the settings for transparent text, due to a bug in the older library versions. Delete these lines:

  // Set the text to white with transparent background
  arduboy.setTextColor(WHITE);
  arduboy.setTextBackground(WHITE);

You should also be able use the Arduboy emulator to run the posted .hex file. Just click where it says Click for Arduboy Emulator.

@MLXXXp

Вы може
It works! Thanks a lot! Tomorrow I will try to implement shooting in my game) Tell me one more thing: how to change the speed of the bullets without changing the speed of the game?

I’ve already talked about this:

@MLXXXp
Finally I figured it out! Thanks!

1 Like