Making gun fire multiple bullets in Arduino game efficiently

I'm currently working on a small spaceship game project to help me learn Arduino code. The hardware is simply an OLED display with some buttons, and I'm programming an ESP32 devkit.

Right now I have the spaceship moving around and I've started adding in a gun effect of shooting a pixel across the screen. It's quite simple when shooting just 1 bullet, but to fire a second one while the first is still on the screen is where it gets difficult for me. The temporary way I've gone about this is to make a separate variable set for each potential bullet that could be fired. This works fine, but is very repetitive and I have a feeling there could be a much more efficient way to go about it. I have looking into different ways of creating and storing values in variables, but I haven't been able to find a good way to create temporary bullet position values without throwing off something running simultaneously.

Here's my code. I think I labeled everything fairly well, let me know if anything is unclear with what I'm going for. Thanks in advance


#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Timer set up
hw_timer_t *timer = NULL;

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
#define OLED_RESET -1  // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS1 0x3c
Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//PinMode Var
const int A = 17;
const int B = 16;
const int start = 4;
const int trig = 2;
const int up = 13;
const int left = 27;
const int right = 12;
const int down = 14;

// Spaceship Var
// x and y are ship position. I know it's bad for variable to be so nondescriptive. this program got too big before I started giving things better variables.
int x = (SCREEN_WIDTH - 5) / 2;
int y = (SCREEN_HEIGHT - 5) / 2;
int xdir = 0;  // ship x facing direction (0 is not facing left or right, 1 is left, 2 is right)
int ydir = 1;  // ship y facing direction (0 is not facing up or down, 1 is up, 2 is down)

// Shoot right Var
bool r1bullet = false;  // Variable for if bullet is active
int r1xbul;             // bullet x position
int r1ybul;             // bullet y position
int r1move;             // bullet movement value

bool r2bullet = false;
int r2xbul;
int r2ybul;
int r2move;

bool r3bullet = false;
int r3xbul;
int r3ybul;
int r3move;

// Shoot left Var
bool l1bullet = false;
int l1xbul;
int l1ybul;
int l1move;

bool l2bullet = false;
int l2xbul;
int l2ybul;
int l2move;

bool l3bullet = false;
int l3xbul;
int l3ybul;
int l3move;

// Bullet Speed
int sbul = 5;

// Reload time
int reload = 500;

// Delay to set Game Speed
int gameSpeed = 30;


void setup() {
  pinMode(A, INPUT_PULLUP);
  pinMode(B, INPUT_PULLUP);
  pinMode(start, INPUT_PULLUP);
  //pinMode(trig, INPUT_PULLUP);
  pinMode(up, INPUT_PULLUP);
  pinMode(left, INPUT_PULLUP);
  pinMode(right, INPUT_PULLUP);
  pinMode(down, INPUT_PULLUP);

  Serial.begin(9600);

  display1.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS1);

  display1.clearDisplay();
  display1.display();
  display1.setTextColor(WHITE);
  display1.setTextWrap(false);
  display1.setTextSize(1);

  // Set timer frequency to 1Mhz
  timer = timerBegin(1000000);
}

void loop() {
  /*
// Button Testing
  display1.setCursor(0,0);
  if (!digitalRead(A)){
    display1.println("A");
  } 
  if (!digitalRead(B)){
    display1.println("B");
  } 
  if (!digitalRead(start)){
    display1.println("start");
  }
 */


  // Move unless touching edge
  // Move up
  if (!digitalRead(up) && digitalRead(down) && y >= 2) {
    y = y - 1;  // Move Ship 1 pixel
    ydir = 2;   // Change facing pixel
    xdir = 0;
  }
  // Move down
  else if (!digitalRead(down) && digitalRead(up) && y <= SCREEN_HEIGHT - 7) {  // <- Change these values to fit screen size (64-7)
    y = y + 1;
    ydir = 1;
    xdir = 0;
  }
  // Move left
  if (!digitalRead(left) && digitalRead(right) && x >= 2) {
    x = x - 1;
    xdir = 1;
    ydir = 0;
  }
  // Move right
  else if (!digitalRead(right) && digitalRead(left) && x <= SCREEN_WIDTH - 7) {  // <- Change these values to fit screen size (128-7)
    x = x + 1;
    xdir = 2;
    ydir = 0;
  }

  // Draw SpaceShip
  if (xdir == 1) {
    display1.drawPixel(x - 1, y + 2, WHITE);
  }
  if (xdir == 2) {
    display1.drawPixel(x + 5, y + 2, WHITE);
  }
  if (ydir == 2) {
    display1.drawPixel(x + 2, y - 1, WHITE);
  }
  if (ydir == 1) {
    display1.drawPixel(x + 2, y + 5, WHITE);
  }
  display1.fillRect(x, y, 5, 5, WHITE);

  // If Shoot Button is pressed, fire a bullet. This is the clunkiest part of the code, that I want to know if there is a better way of doing that achieves an equivilant result.
  if (!digitalRead(A) && timerReadMilis(timer) >= reload) {

    timerWrite(timer, 0);

    if (ydir == 1) {

    }

    else if (xdir == 1) {

      if (l1bullet && l2bullet && !l3bullet) {
        l3bullet = true;
        l3xbul = x - 1;
        l3ybul = y + 2;
      }

      if (l1bullet && !l2bullet) {
        l2bullet = true;
        l2xbul = x - 1;
        l2ybul = y + 2;
      }

      if (!l1bullet) {
        l1bullet = true;
        l1xbul = x - 1;
        l1ybul = y + 2;
      }

    }

    else if (ydir == 2) {

    }

    else if (xdir == 2) {

      if (r1bullet && r2bullet && !r3bullet) {
        r3bullet = true;
        r3xbul = x - 1;
        r3ybul = y + 2;
      }

      if (r1bullet && !r2bullet) {
        r2bullet = true;
        r2xbul = x + 5;
        r2ybul = y + 2;
      }

      if (!r1bullet) {
        r1bullet = true;
        r1xbul = x + 5;
        r1ybul = y + 2;
      }
    }
  }


  // More clunkyness
  // If bullet is being fired, draw it and move it by bullet speed every cycle
  if (r3bullet) {

    display1.drawPixel(r3xbul + (r3move * sbul), r3ybul, WHITE);
    r3move = r3move + 1;

    if (r3xbul + (r3move * sbul) >= 128) {
      r3move = 0;
      r3bullet = false;
    }
  }

  if (r2bullet) {

    display1.drawPixel(r2xbul + (r2move * sbul), r2ybul, WHITE);
    r2move = r2move + 1;

    if (r2xbul + (r2move * sbul) >= 128) {
      r2move = 0;
      r2bullet = false;
    }
  }

  if (r1bullet) {

    display1.drawPixel(r1xbul + (r1move * sbul), r1ybul, WHITE);
    r1move = r1move + 1;

    if (r1xbul + (r1move * sbul) >= 128) {
      r1move = 0;
      r1bullet = false;
    }
  }

  if (l3bullet) {

    display1.drawPixel(l3xbul - (l3move * sbul), l3ybul, WHITE);
    l3move = l3move + 1;

    if (l3xbul - (l3move * sbul) <= 0) {
      l3move = 0;
      l3bullet = false;
    }
  }

  if (l2bullet) {

    display1.drawPixel(l2xbul - (l2move * sbul), l2ybul, WHITE);
    l2move = l2move + 1;

    if (l2xbul - (l2move * sbul) <= 0) {
      l2move = 0;
      l2bullet = false;
    }
  }

  if (l1bullet) {

    display1.drawPixel(l1xbul - (l1move * sbul), l1ybul, WHITE);
    l1move = l1move + 1;

    if (l1xbul - (l1move * sbul) <= 0) {
      l1move = 0;
      l1bullet = false;
    }
  }

  // Display everything
  display1.display();
  display1.clearDisplay();
  delay(gameSpeed);
}

Read about arrays or structures to store xvelocity, yvelocity, xlocation, ylocation of each "bullet."

Thanks for the reply,

I did consider this, but the 2 ways I could think of implementing were:

Create an undefined number of elements array for each factor that would add a new element for each "bullet" fired. (I think this would work well, but it would clog up a lot of memory unless there is a way to reset the elements to contain a limited number (which if so, sorry for my inexperience, where should I look for information on that?))

Create an array for each potential "bullet" to contain it's values. (This would be more efficient but it would still require having to create variables for the "bullets" that might happen.)

I just wanted to make sure I understood if either of those were what you meant or if there's a clever way I'm unaware of?

Thanks Again

No. You want an array of bullets, not an array of values for each bullet.

You can have an array of arrays.

While @xfpd sez

I would say read about arrays and structures. A structured variable can hold a number of variables that don't have to be the same type.

An array can be a number of elements of the same type; C/C++ lets you make an array of structured variables.

You bullets don't take up much space, seven bytes each. If you limited firing rate or made a game concept like gun overheatation cover for it, simply not launching any more bullets after, say, seven are flying wouldn't seem arbitrary.

The for loop goes well with arrays. One loop could update the position and render all the bullets boom!

HTH

a7

Thanks for the reply as well. I'll look into nesting arrays in structures, I didn't know that was a thing :sweat_smile:

And I think I already do have a good limiter for the number of bullets on screen at a time in the simple timer counter in the shoot button statement (which admittedly took me longer than it should have to figure out), which limits the user to only be able to shoot every 0.5 seconds. I thought it would be cool if I was able to increase or decrease the fire speed as the game went on through some different mechanics, but that is to figure out for another time.

Thanks again

Or nesting structures in an array. The possibilities boggle the mind.

To wet your appetite, here is how your bullet might look one day

// the bullet struct
struct bullet {
  bool active;      // Variable for if bullet is active
  int xPos;         // bullet x position
  int yPos;         // bullet y position
  int8_t xVelocity; // bullet horizontal speed
  int8_t yVelocity; // bullet vertical speed
  int moveValue;    // bullet movement value
};

// an array of bullets
bullet myBullets[10] = {         // myBullets array of ten bullets.
    {false, 50, 50, -5, 10, 42}  // one bullet initialised
};

I added struct members to hold the horizontal and vertical speed of the bullet. Maybe bullets are different colours, too. Hmmm...

a7

Thanks again. That actually helps a lot.