TinyCircuits

Creating sprites dynamically (on run time). Is it possible?

Hi there. One question I have (as I started coding my first game) is: is there a way of creating multiple instances of a sprite? I see that the source code for Tiny Brick and Flappy Birdz they both do not do this. Even though they could use the same bitmaps, the source code specify many instances of the same sprite and they live offscreen until they are needed. But ideally I would like to create the instances on the fly, as I need them (like for example, bullets, or different enemies).

Is this an Arduino limitation? I looked online on how I could push items to an array but it seems that is not possible. Is there a different way?

Thanks.

Re: Creating sprites dynamically (on run time). Is it possible?
« Reply #1 on: July 20, 2018, 01:37:23 PM »
Hi John,

I believe that instantiating each individual sprite (characters or bullets) and keeping them off screen is probably the best way to go. I'm imagining this to be a platformer game similar to Contra or something of the sort, so what you may want to do (referencing our game development tutorial found at https://tinycircuits.com/blogs/learn/how-to-develop-a-game-for-the-tinyarcade-new) is create different spriteList arrays and allow the drawBuffer function to be passed this array depending on the level of the game. Each array would then include the bullets, characters, and enemies for the specific level that the player is on. As sprites are needed they can be moved on screen. I'd imagine you want multiples of bullets and enemies using the same sprites so those will have to be created separately in the arrays.

I hope this helps answer your question. If you have more, feel free to reach out!

Thanks,
-Hunter

Re: Creating sprites dynamically (on run time). Is it possible?
« Reply #2 on: July 20, 2018, 09:34:48 PM »
Hey Hunter, thanks for getting back to me. Makes sense, I'll create an array of bullets and bring them onscreen when I need them.

Cheers,

Re: Creating sprites dynamically (on run time). Is it possible?
« Reply #3 on: July 22, 2018, 01:24:24 AM »
I was able to create the bullets for the game my son and I are working on. I wanted to share my solution in the thread. Please let me know if this is the right approach or if there is a way to improve it.

The first thing I did is to create an offscreen global variable I can use throughout the game:

int offscreen = -100;

I then created a bulletBitmap, and 5 sprite instances for it:

ts_sprite bullet1 = { offscreen, offscreen, 3, 4, 0, bulletBitmap };
ts_sprite bullet2 = { offscreen, offscreen, 3, 4, 0, bulletBitmap };
ts_sprite bullet3 = { offscreen, offscreen, 3, 4, 0, bulletBitmap };
ts_sprite bullet4 = { offscreen, offscreen, 3, 4, 0, bulletBitmap };
ts_sprite bullet5 = { offscreen, offscreen, 3, 4, 0, bulletBitmap };


I added these sprites to the spriteList array, but I also created a playerBulletList array, pointing to the same references. This way I could use the spriteList array just for the drawBuffer function, and keep a separate array to iterate through when I'm updating bullets:

int amtSprites = 6;
ts_sprite * spriteList[6] = { &player, &bullet1, &bullet2, &bullet3, &bullet4, &bullet5 };
int amtPlayerBullets = 5;
ts_sprite * playerBulletList[5] = { &bullet1, &bullet2, &bullet3, &bullet4, &bullet5 };


I then added two global variables:

int playerBulletIndex = 0;
int playerLastShot = 0;


playerBulletIndex allows the function to go through an index of bullets, and know which of the 5 I used last, and the playerLastShot variable records the last time in millis when a bullet was last shot.

Here is the function (added to loop() to run in every cycle):

void playerBulletsMovement() {
  //Check if button is pressed
  if (checkButton(TAButton1) && millis() > playerLastShot + 400) {
    ts_sprite *cb = playerBulletList[playerBulletIndex];
    cb->x = player.x;
    cb->y = player.y;
    playerBulletIndex += 1;
    if (playerBulletIndex > 4) playerBulletIndex = 0;
    playerLastShot = millis();
  }
 
  //Update all bullets
  for (int playerBulletIndex = 0; playerBulletIndex < amtPlayerBullets; playerBulletIndex++) {
    ts_sprite *cb = playerBulletList[playerBulletIndex];
    if (cb->y != offscreen) {
      cb->y = cb->y -1;
      if (cb->y < 0) cb->y = offscreen; 
    }
  }
}


The first part checks if the player pressed Button1 to fire a shot. But so the player doesn't fire indiscriminately, we only allow the player to fire every .4 of a second. So this part of the function only runs when both of these conditions are met.

if (checkButton(TAButton1) && millis() > playerLastShot + 400)

If the condition is met, the next step is to create a variable in order to access the bullet reference I need:
 
ts_sprite *cb = playerBulletList[playerBulletIndex];

So the first time the player presses Button1, the program requests the first bullet in the playerBulletList.

Right away I set the position of the bullet (currently offscreen) to be where the player is:

cb->x = player.x;
cb->y = player.y;


And then I move the index to the next bullet I can use:

playerBulletIndex += 1;

But I make sure that if the playerBulletIndex goes beyond the size of the array, then I reset the index to the beginning:

if (playerBulletIndex > 4) playerBulletIndex = 0;

Lastly, I store the time in millis since this shot was executed, so it can be compared by this if statement in every loop cycle:

playerLastShot = millis();

The next part of the function runs in every cycle. It is the part of the function that updates the positions of all the bullets. The structure is inspired in how the drawBuffer function works:

 //Update all bullets
  for (int playerBulletIndex = 0; playerBulletIndex < amtPlayerBullets; playerBulletIndex++) {
    ts_sprite *cb = playerBulletList[playerBulletIndex];
    if (cb->y != offscreen) {
      cb->y = cb->y -1;
      if (cb->y < 0) cb->y = offscreen; 
    }
  }

The for loop goes through all the bullets in the playerBulletIndex. The first thing I do is to create a variable to reference one bullet at a time:

ts_sprite *cb = playerBulletList[playerBulletIndex];

Then I check if the bullet is offscreen (because if it is, there is no need to update it):

if (cb->y != offscreen)

I am making a game where bullets fly vertically, from the bottom to the top of the screen:

cb->y = cb->y -1;

And lastly I check if the bullet has left the screen, to move it to the offscreen area and not update it anymore until it's used again:

if (cb->y < 0) cb->y = offscreen;

And that's it! As a last note, the amount of bullets you need may vary depending on how many you effectively can have on the screen at the same time. Be sure to include enough, but not too many as to not waste memory.

Cheers,

Re: Creating sprites dynamically (on run time). Is it possible?
« Reply #4 on: July 30, 2018, 10:17:21 AM »
Hi John,

What you have here looks pretty good! There are a few minor things you could do to save on memory and minor execution time.

As far as saving memory goes, the game could function the same using just one sprite array. All instances in which the playBulletList[] array is used, you could just use the spriteList[] array. My recommendation if you do choose to make this change is to save a global variable for the index at which the bullet sprites begin along with another global variable for the number of bullets. This way you can still iterate through the bullets section of the array from startingBulletIndex through startingBulletIndex + numberOfBullets. This would save on memory as it would remove the array of pointers to the bitmaps already being referenced within the spriteList[] aray.

A very, very minor change you could also make is modifying the offscreen variable to the negative value of the bullet height (bullet1.y). This way the program will have to preform fewer calculations between when the bullet is fired and when it reached its offscreen position. This is a minor change, but if you plan on adding more to the game, this could make a difference.

I hope this helps. Things seem to be coming together well!

-Hunter

Re: Creating sprites dynamically (on run time). Is it possible?
« Reply #5 on: August 03, 2018, 01:05:08 AM »
Hi Hunter! Great recommendations, thank you. I'll work on these changes on my game.

Cheers,