How to make a PICO-8 game: Part 7

How to make a PICO-8 game: Part 7

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:

en = {}

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

en.x = 10
en.y = 10
en.d = 0.05

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.

Firstly, initialise the enemies table at the end of the _init function:

enemies = {}

and add this function to the end of our code:

function make_enemy(espr,ex,ey,ed,eticks,eticksmax)
 en = {} -- create new enemy object
 en.sprite = espr
 en.x = ex
 en.y = ey
 en.d = ed
 en.frame = 0
 en.ticks = eticks
 en.ticksmax = eticksmax

 add(enemies, en) -- add this new enemy to the enemies table
end

To create an enemy we call the make_enemy() function, sending it the variables for sprite number, x and y coordinates, d (“speed” of the enemy), how many “animation ticks” there are currently, and how many animation ticks there are in total. en.frame is also added in the function to give the starting animation frame modifier. I’ll explain the animation stuff later.

In PICO-8, these objects can also have functions inside them. This is handy if we want to, say, draw all our enemies – we can just call each enemy’s draw function.

Before the “add” command in the make_enemy function, add this:

 en.draw = function(this)
  spr(this.sprite,this.x,this.y)
 end

The “this” in this function refers to “what you pass to the function”, which is the object en. So this.x means en.x, and this.y means en.y. Got it?

We can call this function, for all enemies, with just one line of code, which we put in the _draw() function:

 foreach(enemies, function(en) en:draw() end)

Which literally means “for each item in the enemies table, trigger that item’s draw function”.

And that’s enough for today. However, we need to do some code tidying as a lot of bits are no longer needed now we’ve turned enemies into objects! The stuff in _init() about ex and ey, can go, as can d. In the _draw() function, we don’t need the single-enemy spr code any more either.

At this point, our code is actually broken as there are still references to the old way of dealing with enemies (collisions and moving them in particular), but don’t worry – we’ll fix that next time!

Our code so far, broken and all:

function _init()
 -- player start location
 x = 64
 y = 64
 
 ticks = 0
 tickstrigger = 200
 dinc = 0.05
 -- score
 score = 0
 
 -- initialise enemies table
 enemies = {}
end

function _update()
 -- check for collision (currently broken!)
 if checkcol(x,y,ex,ey) then
  stop()
 end

 --[[ check button presses and screen edge
  btn(0) is left
  btn(1) is right
  btn(2) is up
  btn(3) is down
 ]]
 if btn(0) and x > 0 then
  x -= 1
 end
 if btn(1) and x < 120 then
  x += 1
 end
 if btn(2) and y > 0 then
  y -= 1
 end
 if btn(3) and y < 120 then
  y += 1
 end

 -- move enemy (also broken!)
 if x < ex then
  ex -= d
 end
 if x > ex then
  ex += d
 end
 if y < ey then
  ey -= d
 end
 if y > ey then
  ey += d
 end
 
 -- increment score
 score += 1
 
 -- if ticks are triggered, make enemy faster (broken!)
 ticks += 1
 if ticks > tickstrigger then
  d += dinc
  ticks = 0
 end
 
end

function _draw()
 cls(2) -- clear the screen to mauve
 print(score,100,0,7) -- print score
 spr(1,x,y) -- draw sprite 1 at x, y
 -- draw all enemies
 foreach(enemies, function(en) en:draw() end)
end

function checkcol(ax,ay,bx,by)
 if bx+8>ax and bx<ax+8 and by+8>ay and by<ay+8 then
  return true
 else
  return false
 end
end

function make_enemy(espr,ex,ey,ed,eticks,eticksmax)
 en = {}
 en.sprite = espr
 en.x = ex
 en.y = ey
 en.d = ed
 en.frame = 0
 en.ticks = eticks
 en.ticksmax = eticksmax
 
 en.draw = function(this)
  spr(this.sprite,this.x,this.y)
 end
 
 add(enemies,en)
end

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.