Flapping Shooter Feedback & Tips

In my college programming course, the variables were all named with profanities.

Now, I try to appropriately name all my variables and annotate the code so that someone else could puzzle it out. The someone else being me after the rum wears off.

Fall rate, dive rate, glide rate, etc. can all be changed at any time. I imagine they will get tweaked once there are obstacles and enemies to contend with.

1 Like

Profanity - the one language all programmers know.

Ah, I take it you’ve had that experience where you look at some code you wrote some time ago and realise you have no idea what it does?

Yep. Prototype first, tweak later. Good plan.

2nd Demo

I have added in the shooting mechanic.
The player can only have 5 sonic bullets on screen at once.

I did reduce the fall rate a bit, but you still have to work a good bit to maintain altitude.

Handling the bullets was a challenge for me, especially since I was unable to find a shooter specific tutorial on the forums. I ended up finding the info I needed disguised in filmote’s Steve the Dinosaur tutorial (part 5).

No sprite masking, as of yet. Next up is the scrolling cave floor and ceiling to give the illusion of movement.

batmoveshootdemo.ino.hex (24.1 KB)

2 Likes

Bullets disguised as dinosaurs ? :slight_smile:

The bullets were disguised as cacti and pterosaurs!

I attempted to get the ground/ceiling added in last night, but could not get filmote’s tutorial code to work for me. Some sort of error where variables were undeclared…but they all seemed to be declared prior to their use in the function.
Oh well, I will re-attempt tonight. Perhaps the lack of rum was the problem.

I did rework the owl, so I have something to show. I am much happier with it, now.

batconcept2

3 Likes

I/we could tell you more if we could see the code and/or the exact error message.

Drink and the devil had done for the rest.

Yes, it actually looks like an owl now. (Before I thought it was some kind of RPG-style monster. :P)

Though I must admit, every time I look at your dragon I keep expecting to see a burly arm and burnination.
(See also: Trogdor.)

If I cannot puzzle it out on my second try, I will definitely post my code for all to marvel at.

The dragon was intended to be a winged snake, but now I’m seeing Trogdor too.

Okay, I have a scrolling floor, but I am getting ever increasing gaps between sections. Eventually, all floor segments merge to form one segment.
I know this is some sort of silly error, but I’m having issues actually determining what is causing it.

I’ve eliminated everything from the program except the floor scrolling code. Maybe a fresh set of eyes can tell me where I’m going wrong?

//bat scroll demo
#include <Arduboy2.h>
Arduboy2 arduboy;
//define constants
#define MAX_NUM_BKGND 5 //max number of active player bullets...no = no ; for #defines
#define BKGND_START 128 //start point of new background segment
//declare global variables here

int currentnumbkgnd = 0;
int bkgndspeed = 1; //speed of bullets
int bkgndxlim = -31; //limit as to x position of bkgnds


//declare whatever else
struct Bkgnd {
  int x;
  int y;
  bool enabled;
  };

Bkgnd bkgnd[MAX_NUM_BKGND] = { //first "Bkgnd" refers to structure, second "bkgnd" is name of array
      { 0, 55, true}, // refer to Bkgnd structure. setting initial values in array of bkgnds. x=0, y=0, enabled = false (not active)
      { 32, 55, true}, // total of 5 for max num bkgnds
      { 64, 55, true},
      { 96, 55, true},
      { 128, 55, true},
     
    };


//sprites


const unsigned char PROGMEM flat[] =
{
// width, height,
32, 9,
0x80, 0x40, 0xa0, 0x40, 0x80, 0x40, 0x80, 0x40, 0xa0, 0x40, 0xa0, 0x40, 0xa0, 0x50, 0x80, 0x40, 0xa0, 0x40, 0xa0, 0x40, 0xa0, 0x50, 0xa0, 0x40, 0xa0, 0x40, 0x80, 0x40, 0xa0, 0x40, 0xa0, 0x40, 
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 
};


//runs once on startup
void setup() {
  arduboy.begin();
  arduboy.clear();
  arduboy.setFrameRate(30); //TBD if 60 frames or 30
  
}

//runs continuously 
void loop() {
  

  //Prevent the Arduboy from running too fast
  if (!arduboy.nextFrame()) {
    return;
  }

  arduboy.clear();
  


  batfire();
  updateBkgnds();
  
  
 
  
  
  
  
  

  

  
  arduboy.display();
}






void fireBkgnds(int bkgndnumber) {
  
  bkgnd[bkgndnumber].x = BKGND_START;
  bkgnd[bkgndnumber].enabled = true;
  
}

void batfire() {
   
    if (MAX_NUM_BKGND>currentnumbkgnd){ //if less bulets active than max
       for (uint8_t i = 0; i < MAX_NUM_BKGND; i++) { //this loop finds first inactive slot in bkgnd array

        if (!bkgnd[i].enabled) {  //if disabled,
          fireBkgnds(i); //fire new bkgnd (item i in array)
          break;
        }
       currentnumbkgnd++; //add to count of bkgnds
    }
 }}
void updateBkgnds() {

  for (byte i = 0; i < MAX_NUM_BKGND; i++) {
        
    
    if (bkgnd[i].x < bkgndxlim) { //if bkgnd not visble to right, disable it
        bkgnd[i].enabled = false;
        currentnumbkgnd--; 
      }
    if (bkgnd[i].enabled == true) {

      Sprites::drawOverwrite(bkgnd[i].x, bkgnd[i].y, flat, 0);
      bkgnd[i].x = bkgnd[i].x - bkgndspeed; //move bkgnd to right
    }

    
          

      
   
    }
        
  }

The ultimate idea here is to have 3 different floor segment sprites which are randomly chosen as old segments leave the screen. At the top of the screen, a similar process would be going on with all the sprites flipped vertically. However, I figure I better get this right before I complicate it further.

I’ll be back later to help solve this, but the crux of the problem is that your tiles are going out of sync because of the way this is implemented.

When you start the backgrounds are all 32 pixels apart,
but because of the timing at which they’re being reset they don’t stay 32 pixels apart.

As I was saying before, your background panels start 32 pixels apart, but then they end up lagging behind.

The reason for this is that when you detect a background panel is offscreen, you deactivate it until the next frame,
thus causing it to be 1 pixel out of sync with the other background panels.

The easiest way to rectify this is to skip the part where you disable the background panel and just move it to the back of the queue immediately.

I’ve made a modified version of your code that does precisely that:

// Bat scroll demo
#include <Arduboy2.h>

// Max number of active player bullets
constexpr uint8_t maxBackgrounds = 5;

// Start point of new background segment
constexpr uint8_t backgroundStart = 128;

// Speed of bullets
constexpr int backgroundSpeed = 1;

// Limit of the x position of backgrounds
constexpr int backgroundXLimit = -31;

Arduboy2 arduboy;

int backgroundCount = 0;

struct Background
{
  int x;
  int y;
  bool enabled;
};

// First "Background" refers to structure, second "background" is name of array
Background background[maxBackgrounds]
{
  // Refer to Background structure. setting initial values in array of backgrounds. x=0, y=0, enabled = false (not active)
  // Total of 5 for maxBackgrounds
  { 0, 55, true},
  { 32, 55, true},
  { 64, 55, true},
  { 96, 55, true},
  { 128, 55, true},
};

//sprites
const unsigned char flat[] PROGMEM
{
  // width, height,
  32, 9,
  0x80, 0x40, 0xa0, 0x40, 0x80, 0x40, 0x80, 0x40, 0xa0, 0x40, 0xa0, 0x40, 0xa0, 0x50, 0x80, 0x40, 0xa0, 0x40, 0xa0, 0x40, 0xa0, 0x50, 0xa0, 0x40, 0xa0, 0x40, 0x80, 0x40, 0xa0, 0x40, 0xa0, 0x40, 
  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 
};


//runs once on startup
void setup()
{
  arduboy.begin();
  //TBD if 60 frames or 30
  arduboy.setFrameRate(30);
}

// Runs continuously 
void loop()
{
  //Prevent the Arduboy from running too fast
  if (!arduboy.nextFrame())
    return;

  arduboy.clear();

  updateBackgrounds();
  drawBackgrounds();

  arduboy.display();
}

void updateBackgrounds()
{
  for (uint8_t index = 0; index < maxBackgrounds; ++index)
  {
    if(background[index].enabled)
    {
      // Move background left
      background[index].x -= backgroundSpeed;

      // If background not visble, disable it
      if (background[index].x < backgroundXLimit)
      { 
        background[index].x = backgroundStart;
        background[index].enabled = true;
      }
    }
  }
}

void drawBackgrounds()
{
  for (uint8_t index = 0; index < maxBackgrounds; ++index)
  {
    if (background[index].enabled)
    {
      Sprites::drawOverwrite(background[index].x, background[index].y, flat, 0);
    }
  }
}

While I was at it I made a few other changes to make the code a bit more readable:

  • Avoid contractions, e.g. Background not BKGND
  • Avoid macros, e.g. constexpr uint8_t MaxBackgrounds = 5;, not #define MAX_NUM_BKGND 5
    • Macros are evil for a number of reasons
  • Avoid too much whitespace, especially within code blocks

Technically at this point you could also get rid of all the code pertaining to Background::enabled since none of the backgrounds are being disabled anymore, but I left it in there for now.

Thank you!

I do have a tendency to use contractions in my code. I think it stems from messing around with PICO8. The character count was one of the arbitrary constraints of the system. So, “playerX” became “PX” (and so on). It was only recently that I learned that variable names did not really have an effect on program size.

I see now where the enable/disable was losing a frame. This code is a modified version of the bat’s shooting code (which was adapted from the cactus launching code from the @filmote tutorial). I figured that the bullets and the floor segments were essentially doing the same thing. However, disabling bullets makes sense for shooting, but not here.

Anyway, I will see if I can add the randomized segments and ceiling before updating the demo (and implement the other suggestions). I appreciate the help!

*EDIT - I updated the demo with the randomized scrolling background. There’s a graphical bug where the background segments with spikes meet. I will have to revisit the sprites later.

2 Likes

Once upon a time that was actaully true for certain general purpose programming languages.

In particular, various variants of BASIC used on early home computers like the ZX Spectrum had a similar constraint,
but that’s because BASIC was an interpreted language that kept its variable names in RAM and the computer didn’t have very much RAM.

Thankfully those days are long gone.

PICO8 is a unique environment because it’s extremely artificial.
Near enough every constraint the system has is an arbitrary constraint the designer chose to introduce and enforce.
Hence it’s not really a good baseline to compare more realistic programming to.

Technically they can have an effect on the file size for some programs,
but only when the variable names are stored in the program as debug information.

It’s pretty common for desktop programs to keep debug information,
but for embedded systems like the Arduboy debug information is typically stripped out before the program is uploaded.

Either way, it’s not worth caring about how long a variable name is,
it’s more important that the variable name is easy to understand and as self explanatory as possible.
Clarity is more important than brevity.

Thanks for the info, @Pharap.

As far as game progress goes, I think that enemy behaviors and spawning are the next step.
The idea I am toying with is to have a structured array representing a list of the enemies and the delays between the next enemy spawn. Bosses like the owl, would halt the progression of the list until they are defeated.
I did make a small demo for the behavior of an enemy crow. (Basically, an automated bat) However, I think the spawn logic should be implemented before I do anything further with enemies.

1 Like

If you get stuck, feel free to ask for more assistance.

The spawn timer part is a bit more complicated than you might expect.

You’ll have to accumulate the time that’s elapsed while the game is running (i.e. not when the game is paused).
The timer that millis runs off runs constantly, you can’t pause it while the game is paused, hence you’ll have to do your own time accumulation logic.

I managed to get a bit more done at work during my lunch hour today.
I now have a working prototype for the spawner code. I had actually not considered using the built in delay function. I am instead counting down the frames until the next spawn.
At this point, I still don’t have much of anything to show off. For ease of development, I have been writing each new system as an entirely new program and later copy/pasting the final code into the demo. Before I add this to the demo, I’d like to flesh it out a bit more to keep me from backtracking.

I am starting to wonder when program size will become an issue, though. Ideally, I’d like to implement at least 3 different bosses and 5 different enemies. All of which have a unique behavior.
This doesn’t seem like it’s unrealistic to me, but the arduino IDE shows that the spawner demo code alone uses 48% global variable memory and 26% program memory. Deleting sprites seems to have no effect on these values.

*EDIT: basic spawner is up and running!

1 Like

Bear in mind that some of that memory usage is coming from the Arduboy2 library.
If you test everything as a separate program then each program will be using its own copy of the Arduboy2 library.
When you merge the code into a single program there will only be one copy of the Arduboy2 library,
so the amount of memory usage added to your actual game will be less than what’s being reported in the standalone programs.

There will be ways to save memory.

For example, assuming all your background images will be the same height,
you could get rid of the y component of the Background struct because the y coordinate will always be the same.

Also, after those changes I suggested, which causes a background to be shuffled to the back the moment it goes out of range, you could get rid of enabled too.
And you could get rid of backgroundCount since it’s never actually used.

That would leave you with something like:

// Bat scroll demo
#include <Arduboy2.h>

// Max number of active player bullets
constexpr uint8_t maxBackgrounds = 5;

// Start point of new background segment
constexpr uint8_t backgroundStart = 128;

// Speed of bullets
constexpr int backgroundSpeed = 1;

// Limit of the x position of backgrounds
constexpr int backgroundXLimit = -31;

constexpr int backgroundY = 55;

Arduboy2 arduboy;

struct Background
{
  int x;
};

// First "Background" refers to structure, second "background" is name of array
Background background[maxBackgrounds]
{
  // Refer to Background structure. setting initial values in array of backgrounds. x=0, y=0, enabled = false (not active)
  // Total of 5 for maxBackgrounds
  { 0 },
  { 32 },
  { 64 },
  { 96 },
  { 128 },
};

//sprites
const unsigned char flat[] PROGMEM
{
  // width, height,
  32, 9,
  0x80, 0x40, 0xa0, 0x40, 0x80, 0x40, 0x80, 0x40, 0xa0, 0x40, 0xa0, 0x40, 0xa0, 0x50, 0x80, 0x40, 0xa0, 0x40, 0xa0, 0x40, 0xa0, 0x50, 0xa0, 0x40, 0xa0, 0x40, 0x80, 0x40, 0xa0, 0x40, 0xa0, 0x40, 
  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 
};


//runs once on startup
void setup()
{
  arduboy.begin();
  //TBD if 60 frames or 30
  arduboy.setFrameRate(30);
}

// Runs continuously 
void loop()
{
  //Prevent the Arduboy from running too fast
  if (!arduboy.nextFrame())
    return;

  arduboy.clear();

  updateBackgrounds();
  drawBackgrounds();

  arduboy.display();
}

void updateBackgrounds()
{
  for (uint8_t index = 0; index < maxBackgrounds; ++index)
  {
      // Move background left
      background[index].x -= backgroundSpeed;

      // If background not visble, move it to the back of the queue
      if (background[index].x < backgroundXLimit)
        background[index].x = backgroundStart;
  }
}

void drawBackgrounds()
{
  for (uint8_t index = 0; index < maxBackgrounds; ++index)
      Sprites::drawOverwrite(background[index].x, backgroundY, flat, 0);
}

(Note: completely untested.)

Thanks for the info.

I did remove the unnecessary checks as you had suggested. Before your reply, I compared the size of the spawner program with the full demo. I noticed that there was only a 2-4% difference in memory usages, so that made me feel better about having enough space to finish.

Now, I am working on collision. Because of the shapes and animations of some of the enemy sprites, I will need pixel perfect collision. Once again, @filmote comes to the rescue with his wonderful Steve tutorial (thanks!!!). When this is working, I will be in the home stretch! The only major hurdle left will be boss mechanics.

I am looking forward to finally finishing this project, and am already thinking about the next one. However, I have forbidden myself from starting any work until the bat game is done. I have a tendency to not finish projects and I do not want to sabotage myself. Thankfully this forum, and my wife and friends, give me some accountability.

1 Like

I don’t know if filmote’s tutorial covered it,
but if you’re going to do per-pixel collision,
make sure to do it as a second phase after rectangle collision.

The rectangle collision is the ‘broad phase’ and the per-pixel collision is the ‘narrow phase’.
This sort of trick is used a lot in video games,
even in 3D games there’s usually a cuboid encompassing the model,
which is then used for a ‘broad phase’ check before a more precise check is done (not necessarily per-polygon).

(In all honesty you probably won’t need it,
speed is rarely an issue on the Arduboy,
but it’s good to be aware of the trick at least.)

Have you already got a title screen and the like,
or are you not going to bother for your first game?

When you finish, will you be sharing the source and/or open sourcing it?

*Hides mountain of unfinished projects.*

The approach I took does a basic check before it goes into detail.

“Basic check” being rectangle collisions?