game mechanics:
Animate Sprite
How to Animate a Sprite!
Here are the sprites we use in this example animation cartridge.
You can copy these sprites or make your own! Make sure you draw each of these sprites in the exact same place we do, because the code will look for their sprite number. But once you understand the code, you can draw as many sprites as you want, where ever you want!
This is where you will want to draw your sprites in the PICO-8 sprite editor.
(We leave the top left square (Sprite #0) as an X because that is a default empty tile in the map editor.)
The first code we write at the top of the file are the variables. These help us set up the game and keep track of data.
When variables are set outside of any function, that means that they will be set before anything even starts running in the game. And that means any code written after these variables will be able to find and use them.
If you are using the _init()
function, then that would be a good place to put these variables for each game object that you want to animate. Or if you are using tables to organize your game objects then these variables can be inside of the objects\' tables.
What is a sprite?
sprite = 1
x = -8
y = 59
timing = 0.25
The first variable is named sprite
. Then the equals sign tells the game to remember it with whatever we write next. sprite
holds a number and that number is the sprite-number of the first sprite in the sprite sheet.
The next variables are named x
and y
. Together these tell the game where the sprite is on the screen.
x = -8
and y = 59
because the screen has 128 pixels from left-to-right and top-to-bottom (# 0-127). The X is where the sprite will start on the X-axis (horizontally) and -8 will be just to the left of the visible screen. The Y is where the sprite will start on the Y-axis (vertically) and 59 places the sprite in the middle of the screen.
The next set of variables are for keeping track of the animation timing.
timing = 0.25
timing
is for setting a delay time between each animation (sprite change). This needs to be a decimal between 0 and 1. Smaller decimal = slower animation. This is explained more in the update section below.
function _draw()
cls()
spr(sprite, x, y)
print(sprite)
end
_draw()
runs after _update and draws to the game screen 30 times every second.
cls()
clears the game screen.
spr()
draws a sprite at X and Y coordinates on the screen.
spr(sprite, x, y)
tells the SPR function to draw the sprite number at the X and Y variables we set above.
print(sprite)
will write the sprite variable to the screen. It will show as the number as it counts up by timing so you can see how fast the animation is happening.
function _update()
--animate
sprite += timing
if sprite >= 5 then sprite = 1 end
--move
x += 1
if x > 127 then x = -8 end
end
The function _update()
makes changes to the game 30 times every second.
You can also use _update60()
to double that speed to 60 times per second. But if you choose to do that, then the animation will move twice as fast, so you should cut the timing
variable in half.
The first thing we will update is the sprite number to create the animation.
sprite += timing
increases the sprite-number by the number you set in the timing
variable.
Why should timing
be a decimal between 0 and 1?
Because the very next sprite is 1 whole number away. If we add 1 every time _update()
runs, then we will be changing the sprite way too fast. Try setting timing
to 1 and run the game to see just how fast 30 times a second is. And obviously, if we add 0, then the sprite number will never change. So it has to be a decimal between those two.
Let\'s say we use 0.5 for our timing
number. That means 30 times a second (every time _update()
runs) the sprite number will increase halfway towards the next sprite. And that means instead of changing the sprite 30 times every second, it will change the sprite 15 times every second. That is how we are controlling the timing with this number.
So play around with this number to get the animation timing how you\'d like it. The closer to 0 you make it, will slow down the animation. The closer to 1 you make it, will speed up the animation.
To make this work, you must draw your sprites next to each other in a row and slightly change each drawing, and that will create the look of one character being animated.
Now that we are increasing the sprite number, what happens when it gets to the end of our character\'s sprites?
Well that means we need to restart the animation loop. So let\'s check when the sprite number gets to the end, and reset it to the start. It\'s as simple as this one line of code:
if sprite >= 5 then sprite = 1 end
Why 5 you ask?
Don\'t forget that we are adding a decimal to the current sprite number. So the sprite starts at 1. In this example, our timing
variable is set to 0.25
. That means our code will basically count up by .25 every tick of the game.
Let\'s do it: 1 ... 1.25 ... 1.5 ... 1.75 ... 2 ... 2.25 ... and so on.
Now the thing you need to know is that PICO-8 will always round down to the lower whole number when trying to draw a sprite. So while the sprite number is anywhere between 1 and 2, then it will only draw sprite number 1. Each tick of the game that the sprite number is between two whole numbers, nothing is actually changing on the screen. So sprite number 2 doesn\'t actually get drawn until the sprite number reaches 2 or more.
Alright, let\'s keep counting: 3 ... 3.25 ... 3.5 ... 3.75 ... 4 ...
So now it gets to our last sprite, number 4. But if we reset it back to sprite 1 at this point, then we are only allowing sprite 4 to be on the screen for a split second. We actually want to allow sprite 4 to be drawn to the screen for the same length of time that the others were drawn. To do that we need to let it keep counting past 4.
4 ... 4.25 ... 4.5 ... 4.75 ... 5
Now we can see that we are letting it count up to 5, which leaves sprite 4 on the screen for the correct amount of time. However, we can\'t let the sprite number reach 5 or more, becuase then it will draw sprite number 5. For this example, sprite 5 is empty, or in your game it could be a totally different character or item and we don\'t want that to flash on the screen at all.
That\'s why we check if the sprite number is more than or equal to 5: if sprite >= 5
The next thing we will update about this sprite animation is the position.
x += 1
will increase the X position by 1 pixel, making the sprite drawn 1 pixel to the right.
if x > 127 then x = -8 end
will check if the sprite moved too far to the right and off the screen. And if that happens, then x = -8
will reset the sprite\'s X position to where it started.
Together, this code will simply start the sprite on the far left, make it move to the right, and then warp it back to the left side to infinitely walk across the screen.
sprite = 1
x = -8
y = 59
timing = 0.25
function _draw()
cls()
spr(sprite, x, y)
print(sprite)
end
function _update()
--animate
sprite += timing
if sprite >= 5 then sprite = 1 end
--move
x += 1
if x > 127 then x = -8 end
end
Warning: If you are new to coding, this may look complicated. It uses tables to organize all of the different game objects. So if you have not used tables before, we suggest reading about tables in our Guide to help understand this.
What is a table?function _init()
--create a table for player data
player = {
sprite = 1,
x = -8,
y = 59,
timing = 0.25,
}
--create an empty table for enemies data
enemies = {}
--set enemy sprites, positions, timing and first and last sprite frames
enemy1 = {
sprite=5,
x = -20,
y = 5,
timing = 0.1,
speed = 1.25,
first = 5,
last = 9
}
enemy2 = {
sprite=9 ,
x = -14,
y = 30,
timing = 0.2,
speed = 0.4,
first = 9,
last = 13
}
enemy3 = {
sprite=13 ,
x = -11,
y = 90,
timing = 0.4,
speed = 0.75,
first = 13,
last = 17
}
--add each enemy to enemies table
add(enemies,enemy1)
add(enemies,enemy2)
add(enemies,enemy3)
--create an empty table for items data
items = {}
--set item sprites, positions, timing and first and last sprite frames
item1 = {
sprite=48 ,
x = 30,
y = 110,
timing = 0.3,
first = 48,
last = 56
}
item2 = {
sprite=56 ,
x = 60,
y = 110,
timing = 0.25,
first = 56,
last = 60
}
item3 = {
sprite=60 ,
x = 90,
y = 110,
timing = 0.15,
first = 60,
last = 64
}
--add each enemy to enemies table
add(items,item1)
add(items,item2)
add(items,item3)
end
function _draw()
cls()
--draw player
spr(player.sprite, player.x, player.y)
--draw enemies
for enemy in all(enemies) do
spr(enemy.sprite, enemy.x, enemy.y)
end
--draw items
for item in all(items) do
spr(item.sprite, item.x, item.y)
end
end
function _update()
--animate player
player.sprite += player.timing
if player.sprite >= 5 then player.sprite = 1 end
--move player
player.x+=1
if player.x > 127 then player.x=-8 end
--enemies
for enemy in all(enemies) do
--animate
enemy.sprite += enemy.timing
if enemy.sprite >= enemy.last then
enemy.sprite = enemy.first
end
--move
enemy.x += enemy.speed
if enemy.x > 127 then
enemy.x=-8
end
end
--items
for item in all(items) do
--animate
item.sprite += item.timing
if item.sprite >= item.last then
item.sprite = item.first
end
--don\'t move
end
end
Run
Here is what we drew in our sprite sheet. The same player as before, then 3 enemies, and 3 items.
Notice that they all use only 4 sprites to create their animation except for the coin which uses 8.
We separate the player
, enemies
, and items
into 3 different tables. For the player, we simply wrote the player data directly into the table. But the enemies and items table will hold smaller tables for each enemy\'s data and each item\'s data. So we wrote out those tables for each enemy as enemy1
, enemy2
, enemy3
, and each item as item1
, item2
, item3
.
You may notice some new variables stored with those objects. After timing
, we also added speed
to the enemies to allow each enemy to move at different speeds. Then we added first
to remember the starting sprite number and last
to remember to last sprite number to be used in the checks that happen in the _update() function.
The _draw()
and _update()
functions use a special table loop.
for entry in all(table)
That will cycle through each entry in a table and run the same code over them. This keeps our code nice and short. The only hard part is imagining what the tables look like and understanding how to prepare the table data for loops like this.
Save this cartridge image and open in your PICO-8 to get the sprites and the code so you can start playing around with it.
4364
11 Jun 2020