Flapping Shooter Feedback & Tips

Assuming you have a ‘pollButtons()’ at the start of your loop code, the pressed() function will return true every time you read it within a frame.

Are you looking for character movement more than once in a frame? It would seem like an odd choice but it may make sense in your code. If so, why not read the value into a variable at the start of the loop (after pollButtons()), like this:

bool aPressed = ardduboy.pressed(A_BUTTON);

Then you can clear it to false if need be in side a single frame.

As @filmote says, the state of the buttons is only updated once per frame when you call pollButtons(),
so any call to pressed() or justPressed() will report the state as it was recorded when pollButtons() was called.

Remember that a frame is typically 1/60th of a second,
so it makes sense to only update the buttons once per frame.
Human fingers are slow enough that a button will typically register as ‘pressed’ for multiple frames.
(That’s why justPressed() exists - it’s only true for the first frame the button is pressed for.)

I’m not sure what behaviour you were expecting.

The Arduboy’s screen is 128x64 pixels, and the default framerate for the Arduboy2 library is 60fps,
which means that it only takes ~2 seconds to cross the screen horizontally,
and ~1 second to cross the screen vertically.

I expect Pico8 and Gamemaker were either moving the character a fraction of pixel per second or it was just a case of having a much higher resolution screen.

The most common desktop resolution for modern computers is 1920x1080,
which would take ~32 seconds to cross horizontally at 60fps travelling 1 pixel per second,
and ~18 seconds to cross vertically.


To resolve the speed issue you have two choices:

  • Only move once every few frames
  • Use a fractional datatype to move your character a fraction of a pixel per frame
    • The primary option is float, which is a standard datatype, but it uses a fair bit of memory and some operations are relatively expensive
    • A secondary option is to use some fixed point types from the FixedPointsArduino library (by including it with #include <FixedPointsCommon.h>). These tend to be cheaper than floats, mostly in terms of memory but also in terms of speed for some operations
    • Disclaimer: I wrote the library

I’d recommend starting off by using floats because they’re easier to get used to,
and then when you’re more experienced and/or start running out of memory,
try replacing the floats with fixed points.

Moving once every few frames is viable,
but it can be a bit awkward because of the nuances of timing,
and it quickly complicates the movement code for all entities.


If you have any other questions or would like to see some examples then don’t hesitate to ask,
but it would probably be easier to demonstrate if we knew what your code currently looks like.

pressed() and notPressed() have no dependency on pollButtons(). pressed() will test the state of the button(s) at the time it’s called. pollButtons() is only needed for justPressed() and justReleased().

Making use of the everyXFrames() function is one (but in some cases maybe not the best) way of accomplishing this.

One thing I’ve done in the past with other arduino based designs (not all fun and games) is to store the value in 2x fractions as a uint16_t value. The idea is to set x to be how precise of movement you want (ex. x=4 would means values between 0-3 will move 0 pixels and 4-7 would move 1 pixel, etc.). From here the math becomes something like this:

Click for code example
if (!arduboy.nextFrame())
    return;
if (arduboy.pressed(LEFT_BUTTON))
    --positionX;
else if (arduboy.pressed(RIGHT_BUTTON))
    ++positionX;
//Code goes here for adjusting positionY
//Code goes here to limit positionX to a range depending on need.
arduboy.drawBitmap(positionX >> POSITION_PRECISION, positionY >> POSITION_PRECISION, BITMAP, SPRITE_WIDTH, SPRITE_HEIGHT); //replace POSITION_PRECISION with 1, 2, 3 etc (precision is 1/2^POSITION_PRECISION, 1/2, 1/4, 1/8, etc.)

This makes it so that if POSITION_PRECISION=2 for example your sprite will move 1 pixel every 4 frames and the system is able to handle this very well, with little performance drop, since it’s simple bit shifting. From there it’s adjusting the precision value accordingly (I usually go with 30fps, but some games will be set at 60). The other benefit to this method as opposed to arduboy.everyXFrames is that each sprite can move at different speeds by setting different levels of precision and costs less then different calls to everyXFrames for each sprite. One test I did in the beginning to experiment with sprite movement methods on the arduino was to draw a rectangle on the screen and cap its position to stay on screen, then tried different methods (floats, fixed point arithmetic, dividend precision, and powers of 2 precision) of moving the sprite, playing with the values to get a good fluid motion, while also testing cycle times and memory consumption. Powers of 2 precision typically are the fastest with the best memory footprint, and can achieve very fluid results with the right values.

NOTE: The reason for the powers of 2 is that it uses bit shifting instead of regular division, which can be far faster on the CPU (not something you’ll ever really encounter on a PC/Console platform where you have at least 3-4Ghz processor, unless you’re developing a AAA title, but on a tiny 16Mhz 32u4 the difference is definitely noticeable).

That’s basically fixed points.

if (!arduboy.nextFrame())
    return;

if (arduboy.pressed(LEFT_BUTTON))
    --positionX;
else if (arduboy.pressed(RIGHT_BUTTON))
    ++positionX;

arduboy.drawBitmap(positionX.getInteger(), positionY.getInteger(), image, width, height);

Not always though.

AVR chips (the kind the Arduboy uses) don’t have a barrel shifter, so after a certain size the compiler’s likely to translate the shift into the equivalent of a for loop, which can be quite slow.

Thank you all!
The nextFrame() function was what I needed. The game was displaying frames too quickly. My bat now moves at an appropriate speed.
I also had several issues uploading code to the Arduboy (was previously using ABE), but all is fixed.
I am working on the bat movement demo, now!

1 Like

Behold!
A demo!

The demo is for player movement only, and does not include shooting. It does have the appropriate sprites for the bat, though.
Controls:
L/R for L/R
DOWN to Dive
A BUTTON to Flap and gain altitude
Running at 30 FPS.

The code is a wreck, but I am pretty satisfied with the control of the bat. Next step, is shooting.
Flapping to maintain altitude should be timed so that you can pop off a few shots between flaps.

batmovementdemo.ino.hex (23.4 KB)

1 Like

Make sure to at least attempt to clean it up at some point.

Messy code has a tendency to breed more messy code,
which has tendency to spiral out into spaghetti and before you know it you can’t even ‘read your own handwriting’ (so to speak).


I hasten to add: if you need any help or advice, don’t hesitate to ask.

This is good advice, I had a habit of naming variables like: stuff, things, foo, bar, stuff1, stuff2 and things like that. It’s a tough habit to break!

I like the controls, I don’t know if maybe reduce the amount you need to press the button to reduce fatigue but otherwise it’s great!

1 Like

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.