TinyShips - Arcade Shooter

TinyShips

The version on github has been tested on the developer kit and Arduboy. You can download the code for the game here: https://github.com/icodealot/TinyShips

Feedback is welcome. I will continue to make updates on this. The actual game is out of sync with the Vimeo preview but only slightly.

This is an early prototype for a game I am making for Arduboy. I have built out a lot of the basic structure for the game engine… points system, health / damage, layered animation, etc… along with a first pass at increasing difficulty in an endless/arcade mode.

Some things I have left to complete:

  • Story mode
  • Saving high-scores and Audio preferences to EEPROM
  • Health crates and upgrades (for the tougher levels in survival mode and story mode)
  • Some additional logic for enemies and a few new ships

So far the arcade mode is a lot of fun and my kids love to play it. Look forward to getting this one out there almost as much as getting the Arduboy pre-orders delivered :slight_smile:

7 Likes

Great job! I especially like how the ship looks

One thing that I have to comment on; I wish there was a little more feedback when I get hit. Maybe a low boop or the flicker the ship sprite.

One way to visually indicate hits, weapons fire, etc. is to invert the screen for a split second and then change it back to normal. This can be done easily with the invert() function.

E.g.

  arduboy.invert(true);
  delay(20);
  arduboy.invert(false);

1 Like

I wish there was a little more feedback when I get hit. Maybe a low boop or the flicker the ship sprite.

Yeah, definitely need a few more sound effects and visual queues on things like damage. Thanks for the feedback and fueling the creative juices.

1 Like

One way to visually indicate hits, weapons fire, etc. is to invert the screen for a split second and then change it back to normal. This can be done easily with the invert() function.

Thanks for the tip! I’ll give this a try and see how it plays out.

In addition to invert() you can similarly experiment with allPixelsOn(true)/allPixelsOn(false), which switches the display to full white (without affecting the contents of the buffer). This would be most effective for games with a mostly black background, such as this one.

puts in back pocket

Ty sir

1 Like

OK, so I’ve had my developer kit for quite a few months. So long that my Arduboy library did not contain the methods in the Arduboy class you referenced. I updated to the latest and we are good to go. Liking it so far but not seeing a significant difference between the two approaches. I’ll keep playing with it. Much improved though so thank you for the tip :slight_smile:

For anyone following along with this I have uploaded the current version of TinyShips to GitHub. Feel free to check it out and let me know what you think. https://github.com/icodealot/TinyShips

1 Like

I Know I’m a bit late but was it hard to do the sprites?

Not too bad at all. It was a lot of fun because the resolution of the Arduboy is so small you can keep your work at the pixel level. Note, my images are not optimized to an 8 or 16 pixel width but I didn’t find that to be a problem for my game so far. Here’s an overview:

  1. I started with a canvas at the resolution of the Arduboy to get the scale of the sprites correct.
  2. I drew my sprite sheet from there in Photoshop but you could use anything that can export a PNG at the resolution you want as long as you can tweak the individual pixels as needed. I used Photoshop tools to copy/cut images into smaller canvases for each sprite.
  3. Once I had the PNG files for each sprite I used the Img2Ard code from @Dreamer3 to convert the images into byte arrays. https://github.com/yyyc514/img2ard
  4. From there it was simply a matter of referencing the images (as byte arrays) in my game with meaningful names and calling arduboy.drawBitmap(…)

For example:

arduboy.drawBitmap(x, y, Enemy1, width, height, WHITE);

Where Enemy1 is a byte array from my converted PNG sprite as noted above. That array looks like this in my game’s code:

// 13x13
PROGMEM const unsigned char Enemy1[] = {
0x00, 0x40, 0x40, 0xE0, 0xA0, 0x58, 0x08, 0xA8, 0xF8, 0x48,
0x44, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02,
0x02, 0x03, 0x02, 0x04, 0x04, 0x00
};

I hope that helps. Let me know if you need any more info about how I did anything.

1 Like

Couldn’t you just use grid paper to draw them out? It would make a better and easier view.

You could do that… You could also use MS Paint to do it, too. What @icodealot is saying is that you have to draw them out however you’d like, then convert the pictures to a byte array for the Arduboy to be able to understand the picture.

Ohhh now it makes sense ok thanks @crait

Hello! This is neat, I have not seen it before: enemyIndex = (enemyIndex + 1) % MAX_SHIPS is using the modulo in this way an elegant method to cap/cycle the enemyIndex value?

Screen Shot 2021-11-24 at 11.05.22 pm

Wow!

2 Likes

You’re better off worrying about readability than so-called ‘elegance’.
It’s (almost always) more important that people can read and understand your code than how ‘elegant’ your code is.

Both enemyIndex = (enemyIndex + 1) % MAX_SHIPS; and if(enemyIndex < MAX_SHIPS) ++enemyIndex; else enemyIndex = 0; will achieve the same overall effect, but you should really be asking yourself which one better expresses your intent. (Though for embedded systems programming, sometimes “which is smaller?” can tip the scales.)

Recommended reading: Elegance vs. good code - Gary Robinson's Rants

I never considered whether or not it was elegant but as others have noted, if program memory is not a factor then it is a style choice. Your understanding of the behavior in context is correct. The game loop updates a fixed size array of enemy ships and the modulo operator “%” caps the array index at some “MAX” minus 1. This works because of how the modulo operator behaves, returning the remainder of an integer division. If MAX_SHIPS == 5 and enemyIndex + 1 == 5, then the modulo would return 0 (zero) as the remainder since 5 / 5 is 1 with a remainder of 0.

It’s worth noting that the assembly generated for the inline modulo expression sacrifices a couple of extra bytes, compared to a wordier if/else block. (at least in my experience) Thus, clever code is not always smaller in the eyes of the machine :slight_smile:

I hope that helps.

1 Like

Yes Justin, thank you for confirming my understanding, that helps my learning to code. I appreciate your wading back in even though I found you code so late. Cheers.

2 Likes

Originally I was just going to handwave the low-level details, but since we’re already halfway there…

On AVR, this will almost always be the case for a modulus/divisor that isn’t a power-of-two because AVR has no ‘modulo’ instruction. That means it must simulate modulo calculation with other operations, and typically that will require more instructions than the test/compare and branch instructions generated by an if.

For powers-of-two, the compiler will opt to use a bitwise-and instruction to do the modulo operation, which will likely require fewer instructions than the test-and-branch option.

However:

  • The difference is so tiny that it’s almost never worth caring about. If you’re looking for memory savings, there’s usually much better candidates.
  • The above rules are for AVR, other CPUs behave differently and on some the modulo would be the one that generates fewer instructions, or there would be other factors to take into consideration (e.g. branch prediction and pipeline stalls).
1 Like