How to use procedural generation?

(Andrew Crawford) #1

I am the proud (soon to be) owner of a brand new arduboy (once it arrives in the mail), and I’ve been waiting for two years or so to get my hands on one and would very much like to make my own game. I have some experience using arduinos for other projects, and I have some other projects I am also juggling, but I am pursuing microcontrollers and electronics engineering as a career here coming up (starting school here in may for two different degrees- one for programming and one for electronics), but I am far from proficient yet, and have always had a special love for video games (as well as the other projects which have drawn me towards arduinos), and would just as much like to create something for my own escapism as much as learn from it (and I feel that programming games would be a more practicable way to learn the language and especially to learn the limitations).

But anyway, all of my excitement aside, I am just kind of sketching out the details of what kind of game to make now and I was wondering, would it potentially be possible to use some sort of procedural generation in the code to generate things like dungeons, for example? I know it’s a very loose idea because I know the hardware limitations are a major factor, but from what I gather EEPROM is what usually comes up short (and I have used oleds to display bitmaps using progmem in the past, is this a similar application?). I am also not so sure how the different memory types work given my level of inexperience, I was just wondering if this is something that could possibly be implemented so as to save space and reallocate resources, potentially. If not it doesn’t deter me any from just getting started more simple in order to better learn the ropes, im just asking out of curiousity and thinking long term as well.

2 Likes

(Kevin) #2

Welcome aboard! If you ordered from the Arduboy store and haven’t got a tracking number yet it might be a few days before we ship it just to give you a heads up!

There are several games that use procedural generation to generate it’s content. I love this idea for Arduboy games because it gives an unlimited gameplay experience on the limited hardware.

Squario is my favorite:

I’ll let the other users chime in with other examples and elaborate if they would like.

1 Like

(Pharap) #3

When you get your degrees you’ll be more qualified than I am.

It would be possible to use some procedural generation,
but not every technique would be viable,
and procedural generation can be quite difficult.

Do you mean RAM or progmem/ROM?
Unless you’re trying to save the world you generate then EEPROM shouldn’t come into the equation.

  • ROM/progmem - Stores code and data at compile time, can’t be altered while the program is running
    • Read Only Memory / Program Memory
    • Implemented on the Arduboy as ‘flash’ memory, the same kind of memory used in USB sticks, SD cards and solid state drives
    • Limited number of read and write cycles, enough for a few years of general use
  • RAM - Can be read and written to freely while the program is running, gets erased as soon as the Arduboy is turned off
    • Random Access Memory
    • The kind of memory that gets erased when it loses power is called ‘volatile’ memory
  • EEPROM - Can be freely read and written to, persists after the Arduboy is turned off
    • Electronically Erasable Programmable Read Only Memory
    • Memory that isn’t erased on power-off is also called non-volatile memory or persistent memory
    • Limited number of read and write cycles, but more so than progmem

PCG wiki is a good place to learn about the existance of various techniques, though they don’t always have easy to understand explanations of how the techniques work:

http://pcg.wikidot.com/category-pcg-algorithms

Unfortunately a lot of the reasearch that goes into procedural generation is done by mathematicians and academics who like to write about their techniques in verbose esoteric academic papers that are nearly impenetrable to us mere mortals.

Fortunately there are a few places where you can find reasonable humanly understandable explanations of some techinques.
Gamasutra occaisionally has some good technical articles that explain ideas in a way that makes them understandable.

Ultimately it depends what kind of random generation you’re thinking of doing though.
Random generation of dungeons in a roguelike is quite a bit different to random generation of sokoban puzzles or chess puzzles.

1 Like

(Andrew Crawford) #4

Oh alright, thank you both so much, that is very helpful! And yeah, I figured it might vary since it’s essentially just using algorithms to generate output (which I know is easier said than done, of course), so how much the output varies (or considering how many variables are involved in the input) I’d imagine this would depend. But I’m still sketching out ideas and trying to figure out limitations so I know what I have to work with, so I don’t have anything in particular in mind just yet. For that I will probably start simple just to learn the programming, limitations, and memory types better.

And that is super helpful, thanks so much! I’d looked up reference material to try to understand the different memory types better before (more than once), but it just wasn’t quite as clear/concise, thanks!

I’d like to make some sort of exploration game with randomly generated maps/levels, though I would also like people to be able to save and pick up again where they left off… I would also like to make some sort of simplistic way to traverse between levels (so given your explanation of memory types I can already see potential issues with this). I could make my sprites and tiles as simplistic/small as possible to try to save as much space as possible, and I could make the traversal between levels a one way trip (so the previous level gets erased and the next level is all that gets saved) but again, given the limitations in general I can already see potential issues. But I’m just gonna get started small, like I said, and see what I have to work with then sketch out an outline from there. Looking forward to getting into it and showing everyone what I come up with!

In the meantime I’ll probably have some more questions ahaha. But there’s plenty of reference material for me to catch up on first. Thanks again!

1 Like

(Pharap) #5

What kind of exploration game?
Just wandering around?
Collecting objects?
Defeating enemies?

If you’re happy with the player not being allowed to manipulate the world then you could easily generate the world from a simple number (a ‘seed’) and just store that seed and the player’s position to regenerate the world.

But any kind of manipulation of the world will also need to be saved, which is where the difficulty comes in.

Probably the most well-known game that relies on a seed for terrain generation is Minecraft,
but after the player alters the world I belive the game ends up saving the terrain manually.
I think only chunks for which no save data is present are generated with the seed.

I think Terraria is a similar situation.

If you’re happy with limiting players so they can only save between levels then that would make matters much easier.

It wouldn’t have to be a one-way trip if you’re happy with erasing any alterations the player has made to the level, but a one-way trip is probably better in general.

Feel free to ask anything.


Aha, found the Gamasutra article I was thinking of:

Hava another one that I just stumbled upon:
https://donjon.bin.sh/code/dungeon/

The source code is available, but it’s in Perl, so good luck making sense of it.
(I did read a book about Perl once, but I can only remember bits and pieces.)

And it links to this page:
http://roguebasin.roguelikedevelopment.org/index.php?title=Cellular_Automata_Method_for_Generating_Random_Cave-Like_Levels

Which is quite good too, although the explanation seems to be a bit hard to follow and the C code is, like most C code, an utter mess.
Thankfully the C# code is a bit easier to follow.

These links might not be that useful because they actually build a whole dungeon floor in memory and the Arduboy doesn’t have much RAM, but they might give you some ideas at least.

And lastly some terms you might end up running into:

  • Pseudo-random number generation
    • Very important for a programmer to know about
    • This is where the seed tend to come into play
  • Hash functions
    • Also very important
    • Used for generating data from a set of coordinates
    • Can also take a seed into consideration (with the seed acting as an extra coordinate)
  • Perlin noise
    • A way of generating 2D pseudo-random data discovered by Ken Perlin, a guy who worked on films like Tron
    • The proper version requires a 256 element lookup table (there’s a canonical Java implementation somewhere on Ken Perlin’s website)
  • Diamond-square algorithm
    • Pretty easy to understand means of generating a heightmap
    • Probably not going to be much use on Arduboy because of limited RAM, but should still be interesting (and of course, it doesn’t have to be interpreted as a heightmap)
0 Likes

(Andrew Crawford) #6

Awesome, thanks so much, I’m sure that will all be very helpful too here once i read through them.

And now you’re making me think this might even be possible :thinking: I wasn’t thinking it to be anything a player could manipulate (at least not yet, unless maybe there can be some minimal aspects like crafting/placing something in front of the player, but that’s something that can be looked at later).

For the premise I was thinking that a player starts crash landed on some randomly generated planet (maybe with one of X number of different race NPCs/enemies) and although that stuff would be random the planet stats would be set, then player could their fix ship and go to the next planet (also randomised but having ‘next’ level stats/items), but if they leave and return to the first planet (or go to the next) it can still randomise the map but retain planet ‘level’ and race/enemy types… Until you get home and that’s the goal of the game (or it can be score based, but that would require a level cap and reworking other details). But making it so you could only save between planets would also likely help to save memory, I would think. I’m just not sure if this even sounds plausible or maybe too much (or, hell, if could add more).

I just love games like Minecraft and No Man’s Sky for the exploration aspect (both of the main creators originally made them just to get lost in themselves), but I also just have a special love for sci fi so would love to try to work that in.

0 Likes

(Pharap) #7

It’s possible, but you’d be limited by how much you can keep in RAM and/or EEPROM.

I had an idea a bit like this once, but instead of fixing your ship the player would travel between planets using stargates, and the seed would be printed on screen as a gate address (having only 16 symbols and interpreting each nibble of the seed as a symbol index, like hexadecimal but with a different font).

0 Likes

(Andrew Crawford) #8

Ah, I suppose that does make sense as to why that would require eeprom for object permanence, but I was just thinking along the lines of simply ‘build X device to get off the planet’ or ‘build X device to access item menu’ such as a workbench, but also in that it gets erased once a person leaves the planet.

This would also require the use of an intermediary travel system though too, so I’m not sure if this would be viable on top of whatever other space requirements there are for the rest of it. The Stargate thing is a cool idea, especially if you could return to the same maps.

Thanks for the info! Honestly I’m just gonna go ahead and start on it as my first, just cuz if I try to work on the one part first (the planetary stuff) and the other second (the interplanetary) then I could always work them into their own different games, if anything. Thanks again!

1 Like

(Andrew Crawford) #9

So just for a quick update (sorry, just excited this is coming along so quickly)… Ive gone ahead and gotten started with my game :smiley: made my sprites for the background tiles, player (and player animation), a crashed ship, a landed ship, and a ship overhead view (for later), as well as set the player movement and, most importantly, an initRandomSeed for player spawn (to be random based off the location of the ship).

Next is for me to learn masking, both for animation and collision detection, but just wanted to post an update that this is actually coming along, to some degree :smiley:

0 Likes

(Simon) #10

You might want to look at the article on masks in the magazine …

Issue 7, page 33.

0 Likes

(Andrew Crawford) #11

Oh awesome, thanks so much!

0 Likes

(Andrew Crawford) #12

That is incredibly helpful, as well as the Pipes example, thanks! Only question is, if I am using drawSlowXYBitmap, would i be able to replace it? Just not sure how that works since thats what I had to use.

EDIT: actually, im sorry, i cant quite wrap my head around how i would change what i have to use masks instead (I kind of understand them on their own, but tried to use that instead and it compiled/uploaded okay but nothing happened at all. Here is my current code (as it works, without my attempts)


// See: https://mlxxxp.github.io/documents/Arduino/libraries/Arduboy2/Doxygen/html/
#include <Arduboy2.h>
Arduboy2 arduboy;
Sprites player;

int playerx;
int playery;
int crashedshipx;
int crashedshipy;
int shiplandedx;
int shiplandedy;

const byte background [] PROGMEM = 
{0x08, 0x00, 0x01, 0x00, 0x02, 0x80, 0x40, 0x00, 
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 
0x50, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

const byte shiplanded [] PROGMEM = 
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x06, 0x20, 
0x00, 0x00, 0x00, 0x0C, 0x10, 0x00, 0x00, 0x00, 
0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x40, 0x00, 0x00, 
0xFF, 0xC0, 0x47, 0xF8, 0x07, 0xFF, 0xFF, 0x4F, 
0xFF, 0xC7, 0xFF, 0xFF, 0x4F, 0xFF, 0xC3, 0xFF, 
0xFF, 0x47, 0xF8, 0x01, 0xFF, 0xE0, 0x40, 0x00, 
0x00, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 
0x10, 0x00, 0x02, 0x00, 0x00, 0x14, 0x00, 0x01, 
0x40, 0x00, 0x38, 0x00, 0x03, 0x80, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00};

const byte crashedship [] PROGMEM = 
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x60, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 
0x01, 0x8F, 0x00, 0x00, 0x03, 0x09, 0x80, 0x00, 
0x06, 0x08, 0xC0, 0x00, 0x08, 0x04, 0x40, 0x00, 
0x04, 0xC2, 0x40, 0x00, 0x02, 0xE1, 0xC0, 0x00, 
0x01, 0x70, 0x20, 0x00, 0x00, 0xB8, 0x10, 0x00, 
0x00, 0x5C, 0x08, 0x00, 0x00, 0x2E, 0x04, 0x00, 
0x00, 0x17, 0x02, 0x00, 0x00, 0x0B, 0x8B, 0x00, 
0x00, 0x0D, 0xD9, 0x80, 0x00, 0x1F, 0xFC, 0xC0, 
0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xF8, 
0x00, 0x00, 0x00, 0x00};

const unsigned char playerdown [] PROGMEM =
{0x00, 0x00, 0x0E, 0x00, 0x1F, 0x00, 0x1F, 0x00, 
0x3F, 0x80, 0x44, 0x40, 0x55, 0x40, 0x75, 0xC0, 
0x55, 0x40, 0x3F, 0x80, 0x15, 0x00, 0x15, 0x00, 
0x15, 0x00, 0x15, 0x00, 0x1F, 0x00, 0x1F, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

const unsigned char playerdown2 [] PROGMEM =
{0x00, 0x00, 0x0E, 0x00, 0x1F, 0x00, 0x1F, 0x00, 
0x3F, 0x80, 0x44, 0x40, 0x55, 0x40, 0x55, 0xC0, 
0x75, 0x40, 0x5F, 0x80, 0x35, 0x00, 0x17, 0x00, 
0x15, 0x00, 0x17, 0x00, 0x1C, 0x00, 0x1F, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

const unsigned char playerdown3 [] PROGMEM =
{0x00, 0x00, 0x0E, 0x00, 0x1F, 0x00, 0x1F, 0x00, 
0x3F, 0x80, 0x44, 0x40, 0x55, 0x40, 0x75, 0x40, 
0x55, 0x40, 0x3F, 0xC0, 0x15, 0x40, 0x15, 0x80, 
0x1D, 0x00, 0x1D, 0x00, 0x07, 0x00, 0x1F, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

void setup() {
  arduboy.begin();
  arduboy.initRandomSeed();
  crashedshipx = random(10,80);
  crashedshipy = random(10, 30);
  arduboy.setFrameRate(60);
  arduboy.clear();
  playerx = crashedshipx + 30;
  playery = crashedshipy + 5;
  // initialize things here

}

void loop() {
  if(!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();
  if(arduboy.pressed(LEFT_BUTTON)) {
    playerx = playerx - 1;
  }
   if(arduboy.pressed(RIGHT_BUTTON))  {
    playerx = playerx + 1;
  }
  if(arduboy.pressed(UP_BUTTON)) {
    playery = playery - 1;
  }
  if(arduboy.pressed(DOWN_BUTTON)) {
    playery = playery + 1;
  }
  //For each column on the screen
    for( int backgroundx = 0; backgroundx < 128; backgroundx = backgroundx + 10 ) {
        //For each row in the column
        for( int backgroundy = 0; backgroundy < 64; backgroundy = backgroundy + 10 ) {
                //Draw a background tile
            arduboy.drawSlowXYBitmap( backgroundx, backgroundy, background, 10, 10, WHITE );
        }
    }
  arduboy.drawSlowXYBitmap(playerx,playery,playerdown,11,17,WHITE);
  arduboy.drawSlowXYBitmap(crashedshipx,crashedshipy,crashedship,30,20,WHITE);
  arduboy.display();
}
0 Likes

(Andrew Crawford) #13

Sorry, i have actually gotten it partially working using a different image converter tool to include the sprite and the mask in the array, although the mask leaves a box around the character and Im still not sure how to animate between the first and two other frames.

0 Likes

(Andrew Crawford) #14

Sorry for a bit of spam, but here is my updated code using the masking, and attempting to animate the second frame (there is also a third frame but I didnt even try to add that yet)

// See: https://mlxxxp.github.io/documents/Arduino/libraries/Arduboy2/Doxygen/html/
#include <Arduboy2.h>
Arduboy2 arduboy;
Sprites player;

int playerx;
int playery;
int crashedshipx;
int crashedshipy;
int shiplandedx;
int shiplandedy;

const byte background [] PROGMEM = 
{0x08, 0x00, 0x01, 0x00, 0x02, 0x80, 0x40, 0x00, 
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 
0x50, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

const byte shiplanded [] PROGMEM = 
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x06, 0x20, 
0x00, 0x00, 0x00, 0x0C, 0x10, 0x00, 0x00, 0x00, 
0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x40, 0x00, 0x00, 
0xFF, 0xC0, 0x47, 0xF8, 0x07, 0xFF, 0xFF, 0x4F, 
0xFF, 0xC7, 0xFF, 0xFF, 0x4F, 0xFF, 0xC3, 0xFF, 
0xFF, 0x47, 0xF8, 0x01, 0xFF, 0xE0, 0x40, 0x00, 
0x00, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 
0x10, 0x00, 0x02, 0x00, 0x00, 0x14, 0x00, 0x01, 
0x40, 0x00, 0x38, 0x00, 0x03, 0x80, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00};

const byte crashedship [] PROGMEM = 
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x60, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 
0x01, 0x8F, 0x00, 0x00, 0x03, 0x09, 0x80, 0x00, 
0x06, 0x08, 0xC0, 0x00, 0x08, 0x04, 0x40, 0x00, 
0x04, 0xC2, 0x40, 0x00, 0x02, 0xE1, 0xC0, 0x00, 
0x01, 0x70, 0x20, 0x00, 0x00, 0xB8, 0x10, 0x00, 
0x00, 0x5C, 0x08, 0x00, 0x00, 0x2E, 0x04, 0x00, 
0x00, 0x17, 0x02, 0x00, 0x00, 0x0B, 0x8B, 0x00, 
0x00, 0x0D, 0xD9, 0x80, 0x00, 0x1F, 0xFC, 0xC0, 
0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xF8, 
0x00, 0x00, 0x00, 0x00};

PROGMEM const unsigned char playerdown[] = {
// Sprite: Image + Mask
// Width: 11 Height: 24
11, 24, 
0x00, 0xFF, 0xE0, 0xFF, 0x90, 0xFF, 0xDC, 0xFF, 0x1E, 0xFF, 0xFE, 0xFF, 0x1E, 0xFF, 0xDC, 0xFF, 0x90, 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 
0x00, 0xFF, 0x01, 0xFF, 0x02, 0xFF, 0xFF, 0xFF, 0xC2, 0xFF, 0xFF, 0xFF, 0xC2, 0xFF, 0xFF, 0xFF, 0x02, 0xFF, 0x01, 0xFF, 0x00, 0xFF, 
0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 
};

PROGMEM const unsigned char playerdown2[] = {
// Bitmap Image. No transparency
 
0x00, 0xE0, 0x10, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x03, 0x05, 0xFF, 0xC2, 0xFF, 0xB2, 0xBF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};

const unsigned char playerdown3 [] PROGMEM =
{0x00, 0x00, 0x0E, 0x00, 0x1F, 0x00, 0x1F, 0x00, 
0x3F, 0x80, 0x44, 0x40, 0x55, 0x40, 0x75, 0x40, 
0x55, 0x40, 0x3F, 0xC0, 0x15, 0x40, 0x15, 0x80, 
0x1D, 0x00, 0x1D, 0x00, 0x07, 0x00, 0x1F, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

void setup() {
  arduboy.begin();
  arduboy.initRandomSeed();
  crashedshipx = random(10,80);
  crashedshipy = random(10, 30);
  arduboy.setFrameRate(60);
  arduboy.clear();
  playerx = crashedshipx + 30;
  playery = crashedshipy + 5;


  // initialize things here

}

void loop() {
  if(!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();
  if(arduboy.pressed(LEFT_BUTTON)) {
    playerx = playerx - 1;
  }
   if(arduboy.pressed(RIGHT_BUTTON))  {
    playerx = playerx + 1;
  }
  if(arduboy.pressed(UP_BUTTON)) {
    playery = playery - 1;
  }
  if(arduboy.pressed(DOWN_BUTTON)) {
    playery = playery + 1;
    player.drawExternalMask(playerx,playery,playerdown2,playerdown, 0, 0);
  }
  //For each column on the screen
    for( int backgroundx = 0; backgroundx < 128; backgroundx = backgroundx + 10 ) {
        //For each row in the column
        for( int backgroundy = 0; backgroundy < 64; backgroundy = backgroundy + 10 ) {
                //Draw a background tile
            arduboy.drawSlowXYBitmap( backgroundx, backgroundy, background, 10, 10, WHITE );
        }
    }
  player.drawPlusMask(playerx,playery,playerdown,0);
  arduboy.drawSlowXYBitmap(crashedshipx,crashedshipy,crashedship,30,20,WHITE);
  arduboy.display();
}
0 Likes

(Simon) #15

To do frames, you list the images one after the other. Your images are 11 wide x 24 pixels high (3 bytes), so it should look something like this:

PROGMEM const unsigned char playerdown[] = { // Sprite: Image + Mask // Width: 11 Height: 24 
11, 24, 
// Frame 0
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,

// Frame 1
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
};

You might also have a mask (note the dimensions are not needed as they are assumed to be the same as the image they are masking:

PROGMEM const unsigned char playerdown_Mask[] = { // Sprite: Image + Mask // Width: 11 Height: 24 
// Mask for Frame 0
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,

// Mask for Frame 1
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
};

Then you can do something like:

uint8_t frame = 0;
player.drawExternalMask(playerx, playery, playerdown, playerdown_Mask, frame, frame);
0 Likes

(Andrew Crawford) #16

Ahh, okay, I was under the impression that you could just call each frame separately, but I’ll go ahead and group all my frames, masks, etc. Thanks!

0 Likes

(Andrew Crawford) #17

Hmm, I went back and added all the frames I wanted to animate (4 frames in total) into one array and changed the masking to a separate array (but only used one of the masking frames because theyre all the same size). The sprite seems to be masked just fine, but still having trouble with the animation. Here is my current code:


// See: https://mlxxxp.github.io/documents/Arduino/libraries/Arduboy2/Doxygen/html/
#include <Arduboy2.h>
Arduboy2 arduboy;
Sprites player;

int playerx;
int playery;
int crashedshipx;
int crashedshipy;
int shiplandedx;
int shiplandedy;

const byte background [] PROGMEM = 
{0x08, 0x00, 0x01, 0x00, 0x02, 0x80, 0x40, 0x00, 
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 
0x50, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

const byte shiplanded [] PROGMEM = 
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x06, 0x20, 
0x00, 0x00, 0x00, 0x0C, 0x10, 0x00, 0x00, 0x00, 
0x7F, 0xFF, 0xFF, 0xC0, 0x00, 0x40, 0x00, 0x00, 
0xFF, 0xC0, 0x47, 0xF8, 0x07, 0xFF, 0xFF, 0x4F, 
0xFF, 0xC7, 0xFF, 0xFF, 0x4F, 0xFF, 0xC3, 0xFF, 
0xFF, 0x47, 0xF8, 0x01, 0xFF, 0xE0, 0x40, 0x00, 
0x00, 0xFF, 0xC0, 0x7F, 0xFF, 0xFF, 0xC0, 0x00, 
0x10, 0x00, 0x02, 0x00, 0x00, 0x14, 0x00, 0x01, 
0x40, 0x00, 0x38, 0x00, 0x03, 0x80, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00};

const byte crashedship [] PROGMEM = 
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x60, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 
0x01, 0x8F, 0x00, 0x00, 0x03, 0x09, 0x80, 0x00, 
0x06, 0x08, 0xC0, 0x00, 0x08, 0x04, 0x40, 0x00, 
0x04, 0xC2, 0x40, 0x00, 0x02, 0xE1, 0xC0, 0x00, 
0x01, 0x70, 0x20, 0x00, 0x00, 0xB8, 0x10, 0x00, 
0x00, 0x5C, 0x08, 0x00, 0x00, 0x2E, 0x04, 0x00, 
0x00, 0x17, 0x02, 0x00, 0x00, 0x0B, 0x8B, 0x00, 
0x00, 0x0D, 0xD9, 0x80, 0x00, 0x1F, 0xFC, 0xC0, 
0x00, 0x3F, 0xFF, 0xE0, 0x00, 0x7F, 0xFF, 0xF8, 
0x00, 0x00, 0x00, 0x00};

PROGMEM const unsigned char playerdown[] = {
// Sprite: Image + Mask
// Width: 11 Height: 24
11, 24, 
//frame 0
0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 

//frame 1
0x00, 0xE0, 0x10, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x03, 0x05, 0xFF, 0xC2, 0xFF, 0xB2, 0xBF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 

//frame 2
0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x90, 0xE0, 0x00, 
0x00, 0x01, 0x02, 0xFF, 0xC2, 0xFF, 0xC2, 0xFF, 0x02, 0x01, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

//frame 3
0x00, 0xE0, 0x90, 0xDC, 0x1E, 0xFE, 0x1E, 0xDC, 0x10, 0xE0, 0x00, 
0x00, 0x01, 0x02, 0xBF, 0xB2, 0xFF, 0xC2, 0xFF, 0x0A, 0x07, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};

PROGMEM const unsigned char playerdown_mask [] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
};



void setup() {
  arduboy.begin();
  arduboy.initRandomSeed();
  crashedshipx = random(10,80);
  crashedshipy = random(10, 30);
  arduboy.setFrameRate(60);
  arduboy.clear();
  playerx = crashedshipx + 30;
  playery = crashedshipy + 5;


  // initialize things here

}

void loop() {
  if(!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();
  if(arduboy.pressed(LEFT_BUTTON)) {
    playerx = playerx - 1;
  }
   if(arduboy.pressed(RIGHT_BUTTON))  {
    playerx = playerx + 1;
  }
  if(arduboy.pressed(UP_BUTTON)) {
    playery = playery - 1;
  }
  if(arduboy.pressed(DOWN_BUTTON)) {
    playery = playery + 1;
    player.drawExternalMask(playerx, playery, playerdown, playerdown_mask, 1, 0);  }
  //For each column on the screen
    for( int backgroundx = 0; backgroundx < 128; backgroundx = backgroundx + 10 ) {
        //For each row in the column
        for( int backgroundy = 0; backgroundy < 64; backgroundy = backgroundy + 10 ) {
                //Draw a background tile
            arduboy.drawSlowXYBitmap( backgroundx, backgroundy, background, 10, 10, WHITE );
        }
    }
  player.drawExternalMask(playerx, playery, playerdown, playerdown_mask, 0, 0);
  arduboy.drawSlowXYBitmap(crashedshipx,crashedshipy,crashedship,30,20,WHITE);
  arduboy.display();
}
0 Likes

(Simon) #18

I am not sure what your are expecting however your code does …

  if(arduboy.pressed(DOWN_BUTTON)) {
    player.drawExternalMask(playerx, playery, playerdown, playerdown_mask, 1, 0);  
  }
  ...
  player.drawExternalMask(playerx, playery, playerdown, playerdown_mask, 0, 0);
  ...
  arduboy.display();
}

It seems to draw from 1 if the down button is pressed then immediately overwrite it with frame 0.

0 Likes

(Andrew Crawford) #19

Ah, sorry, should have specified- I want the sprite to be at frame 0 with no input, then cycle through the animation while the button is pressed.

0 Likes

(Simon) #20

Maybe something like:

uint8_t frame = 0;

void loop() {
  if(!arduboy.nextFrame()) {
    return;
  }
  arduboy.clear();
  if(arduboy.pressed(LEFT_BUTTON)) {
    playerx = playerx - 1;
  }
   if(arduboy.pressed(RIGHT_BUTTON))  {
    playerx = playerx + 1;
  }
  if(arduboy.pressed(UP_BUTTON)) {
    playery = playery - 1;
  }
  if(arduboy.pressed(DOWN_BUTTON)) {
    playery = playery + 1;
    frame++;
    if (frame > 1) frame = 0;  // change the 1 to however many frames you have.
  }
  else {
    // if the down is not pressed then reset the frame.
    frame = 0;
  }
  //For each column on the screen
  for( int backgroundx = 0; backgroundx < 128; backgroundx = backgroundx + 10 ) {
      //For each row in the column
      for( int backgroundy = 0; backgroundy < 64; backgroundy = backgroundy + 10 ) {
              //Draw a background tile
          arduboy.drawSlowXYBitmap( backgroundx, backgroundy, background, 10, 10, WHITE );
      }
  }
  player.drawExternalMask(playerx, playery, playerdown, playerdown_mask, frame, 0);
  arduboy.drawSlowXYBitmap(crashedshipx,crashedshipy,crashedship,30,20,WHITE);
  arduboy.display();
}
0 Likes