How to make a PICO-8 game: Part 10

The last animation

It’s time to put the final touches to our game, namely animating the player sprite. Previously I’ve shown you how to do this for the enemies, and also how to create the enemies as objects, so we’re combining those for this task.

Part 10: Player Animation

As before, we need to draw some frames of animation to use. I’ve created some additional frames in sprite slots 2, 3 and 4 to carry on from 1.

Again, it’s pretty simple but is just for illustration.

Now, we need to create the player as an object. We could have a function to house this, make_player() like we have make_enemy(), but we only need to create one player so that’s overkill. Instead, we’ll create it in one go in the _init() function, replacing the x and y we had there before:

You’ll notice I’ve put a player.draw function in there, which works just like the enemy draw function from last time.

Continue reading “How to make a PICO-8 game: Part 10”

How to make a PICO-8 game: Part 9

Animated

Sprites are all well and good, but if they animate they look so much better. Let’s head towards the end of this project by polishing it a bit.

Part 9: Animation

First up, we need to have some frames of animation for the enemy sprites. To make it a bit more logical on the sprite screen, I’ve moved the first frame from “slot” 2 to “slot” 17, then edited it and created three more frames of animation:

It’s nothing fancy, but you can change that if you like.

Now, when we create the enemy, we need to pass an extra parameter to the make_enemy() function – the total number of animation frames for the sprite. Of course we could hard code this into the enemy object, but lets assume we might want to reuse this function at another time for a range of objects each with a varying number of animation frames.

Currently, the function starts with this:

We’ll add “eframemax” to this:

and then set:

in the function itself. You’ll see why we had en.frame = 0 in there now!

Next, we need to add some code to change which sprite to use, changing it every so many ticks so it animates like a flickbook. We already have variables for ticks and ticksmax to help with this.

Continue reading “How to make a PICO-8 game: Part 9”

How to make a PICO-8 game: Part 8

Enemy objects

Last time, we got halfway through butchering our previously working game, leaving it in a state where it refused to even run. This time, we’ll fix that.

Part 8: Enemy Objects

Now that there are multiple enemies, and they’re all objects, we can no longer use any code that refers to the old variables of ex and ey, or any other “single enemy” legacy code. Currently, that also means that collisions no longer work.

Luckily, the function checkcol() only needs to be sent two lots of x and y values, so still works fine providing we send it the right variables, so we can leave that alone. However, we need to replace the code that calls it.

At the moment, it says this in _update():

which doesn’t work – note the ex and ey. Instead, lets call this function for each enemy, and pass that enemy’s x and y to the function. Like this:

Be extra careful with the brackets here – the foreach encloses the code in them!

Hopefully by now what this code does is clear, as it’s similar to the enemy  draw routine: It runs through each enemy in enemies{} and triggers checkcol for each.

That’s collisions sorted. Our code won’t run yet, because we still have to fix the enemy move and enemy speed increase routines.

Continue reading “How to make a PICO-8 game: Part 8”

How to make a PICO-8 game: Part 7

Outnumbered

If you remember (it was before Christmas, so I realise you might not), the last thing we did on our game was make it harder. This was done by making the enemy speed up, but there’s another way – why not spawn more enemies?

Part 7: Spawning

Up until now, we’ve referred to the enemy’s position variables as ex and ey, and used the variables d and dinc to determine their speed and how much they speed up by. If we decide to add a second enemy, we can use more varables – ex2, ey2, d2 and dinc2 perhaps. And for a third enemy, ex3 and ey3 and so on. Simple, yes?

Well yes, but terribly inefficient. Not least because we need the associated code to alter them too. What if, instead, we can just say “make an enemy with these attributes” as many times as we like, and then say “just update all the enemies and draw them on the screen please and thanks”? That’d be like magic, right?

Magic in the form of an enemy object. In PICO-8, objects take the form of specially constructed tables, with keys and values. For example, we can create an “empty” enemy called “en” like this:

and then give it some attributes as key/value pairs:

and so on.

Then, we can be clever and create lots of these and put them all in another table, called “enemies”. We can then perform tasks on all the enemies in the enemies table in one go!

Let’s start with a function to make enemies and add them to the enemies table.

Continue reading “How to make a PICO-8 game: Part 7”

How to make a PICO-8 game: Part 6

Die Hard

We have a complete, if dull and far too easy, game. Now we’ve the basics set up, let’s make it harder – and hopefully a bit more playable as a result!

Part 6: Die Hard

There are a number of ways we can make the game harder, but the simplest one is just to make the enemy faster. We can do this just by changing the value of d from 0.05 to a bigger number. Instead of that, though, why not slowly increment d so that the enemy gets faster and faster the more time the player has spent alive?

We can do this with a counter that increments every loop of the game logic. When it hits a certain threshold, increment d and reset the counter. Let’s call this series of counting “ticks”, and after 200 ticks (so 200 loops of the game logic – essentially 200 frames) d increases. We’ll also choose that d will increase by 0.05 each time.

For this, we need three new variables: ticks (the tick counter), tickstrigger (the number at which ticks triggers a speed increase), and dinc (how much d increased by each time).

We’ll put these in _init():

Now we need a simple routine to increment ticks, check if ticks has spilled over the tickstrigger, and if so, increment d by dinc. We can put that in _update(), after the rest of the game logic:

Now, you’ll notice that ticks increases by one each loop, as does score, so you can watch the score counter on the screen increase and at 200, 400, 600 and every other multiple of 200, the enemy will speed up.

That’s made the game somewhat harder. Of course, eventually the enemy will be faster than the player and there’ll be no way to continue playing, but we can deal with that another time.

Our code so far, then:

Next time, we’ll make the game harder in a different way by creating multiple enemies, and to do that we need to delete and rewrite half our code. It’ll be worth it!

How to make a PICO-8 game: Part 5

Score!

Now that things can collide and the player can effectively die (see last time), the next job is to score some points. Doing this is surprisingly easy, and so this lesson is going to be very short.

Part 5: Score!

For the purposes of this game, simply staying alive is how you score points. Maybe we’ll add bonus points or something later, but for now, the player will get one point for every frame they stay alive.

We need to do three things: set an initial score, update that score, and draw the score on the screen. Hopefully you can see where this is going.

In _init(), we’ll initialise the score to zero:

Then, in _update(), we’ll increment it (so it goes up by one point each time _update() is called. We’ll do that at the end of _update() in case we die that frame:

And finally, we’ll draw this on the screen in – you guessed it – _draw(). We need to draw it after we clear the screen with cls (or we won’t see it), but it’s an artistic choice as to whether our sprites are “on top” of the score, or the score is “on top” of the sprites.

Of course, we could have a status area on the screen for this (where the gameplay area won’t encroach), but for now we’re keeping it simple. Let’s print it at 100,0 so it’s in the top right but has space to allow for multiple digits in case we’re that good, and in colour 7 (white):

And that’s literally all there is to it.

I said it was going to be short! Let’s catch up with the code so far:

 

How to make a PICO-8 game: Part 4

Crash! But not that sort of crash.

In Part 3, we made an enemy chase the player. Which was nice, but nothing actually happened when the player was caught. Why not? Because we had no code to check for collisions! Let’s fix that.

Part 4: Collision check

What is a collision? Well, for the purposes of our game – and most games – it’s when two objects overlap. In our case, it’ll be our player sprite and our enemy sprite. That’s pretty simple to understand, but how do we code it?

Let’s look at the definition in plain English. Both sprites are 8 pixels wide, and we need to check if any part of each sprite is “inside” the area of the other. Things will be different for different sized sprites, but let’s keep it simple for now.

The player sprite’s four corners are:

And the enemy sprite coordinates are:

So when they overlap, all of the following must be true (follow along with two squares of paper if that helps!):

  • The right edge of the enemy must be to the right of the left edge of the player (ex+8 > x), AND
  • The left edge of the enemy must be to the left of the right edge of the player (ex < x+8), AND
  • The bottom edge of the enemy must be below the top edge of the player (ey+8 < y), AND
  • The top edge of the enemy must be above the bottom edge of the player (ey > y+8)

In PICO-8, that translates to:

Let’s plan ahead a little, and assume we’re going to reuse the collision code for other things later on. After all, we might add more enemies, right? Let’s create a function to check collisions between two objects, using this code as the base. The function will return true if there’s a collision, and false if not:

Now we can send the coordinates of any two objects to that function and get a true or false in return. We’ll put a call to it in the _update() function (before we move anything), and stop the game if there’s a collision. Later, we can lose a life or something instead, but for now we’ll just stop().

Remember that “if checkcol(x,y,ex,ey) then” implies “if checkcol(x,y,ex,ey) == true”.

Here’s a catchup of all the code so far:

 

How to make a PICO-8 game: Part 3

Chase me.

Last time, we got a square moving round the screen and accounted for bumping into the walls. This time, we’ll give old Squarey a reason to move around – something to chase him. Or her.

Part 3: Chase me

Right back at the start, we created an extra sprite to use as an enemy – Sprite 2. We’ll make use of that now.

Firstly, we need an initial position for our enemy. As we’re using x and y for the player, let’s use ex and ey for the enemy, and start it in the bottom right of the screen. 120, 120 perhaps. We’re also going to assign a speed to it – how far it moves each frame – and call it d (for distance, in pixels). We put these assignments in our _init() function:

We also need to display this enemy on the screen, so in the _draw() function, remembering the enemy sprite is in slot 2, we can use:

All this does so far, though, is show a square in the bottom right. We still need to make it move, and to do that we need to check where it is in relation to the player. In English, we want this:

“If the player is to the left of the enemy, move the enemy left”

“If the player is to the right of the enemy, move the enemy right”

That is, if x is less than ex then the player is to the left, and if x is greater than ex then the player is to the right. We can do the same for up and down with y and ey.

So how do we make it move left? Decrement ex. Right? Increment ex. Up? Decrement ey. Down? Increment ey. And how much do we increment or decrement by? Our variable d. Easy! Let’s do the if checks in _update(), after we’ve moved the player:

Now run the code, and watch how the enemy chases the player. Very, very slowly. Have a play with the value of d in the _init() function to see how this changes the speed of the enemy – try 0.1 or 1 instead of 0.05, for example.

Chase me!

Obviously, the game is very easy at this point, and not least because nothing happens if the enemy catches you. Why not? Because we’ve not coded any collisions yet! That’s what we’ll do next time. For now, here’s all the code so far, with some additional annotations so we’ll remember what everything does: