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():
-- check for collision if checkcol(x,y,ex,ey) then stop() end
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:
-- check for collision foreach(enemies, function(en) if checkcol(x,y,en.x,en.y) then stop() end end)
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.
At the moment, moves are dealt with here:
-- move enemy 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
and again, ex and ey need replacing.
Why not include a move function as part of the enemy object itself, like how we included a draw function? After the en.draw function in make_enemy(), we can do this:
en.move = function(this) if x < this.x then this.x -= this.d end if x > this.x then this.x += this.d end if y < this.y then this.y -= this.d end if y > this.y then this.y += this.d end end
You’ll notice this is essentially the same code, only ex and ey are now this.x and this.y (as they refer to en as “this”), and it’s laid out on fewer lines.
Of course, we need to call this in turn like before, so replace the move code in _update() with:
foreach(enemies, function(en) en:move() end)
Now, we need to replace the d += dinc code in _update(). It currently still refers to a single “d”, whereas every enemy has it’s own. It should be easy enough now:
foreach(enemies, function(en) en.d += dinc end)
And the code works again! Only… there are no enemies. Let’s put some in.
To start with, we’ll create one enemy in _init(). After the enemies = {} code, we’ll call make_enemy() with some attributes:
make_enemy(2,120,120,0.05,0,5)
Remember, in order, these values are:
Sprite number (2), x and y coordinates (120 and 120), d (“speed” of the enemy, 0.05), how many “animation ticks” there are currently (0), and how many animation ticks there are in total (5).
Now we’re back to where we were before we broke the game. But we did that to create more enemies whenever we wanted, right? Right. Let’s do that then. For simplicity, we’ll create one every 200 ticks, at the same time we speed the enemies up. So, after ticks=0 in _update():
make_enemy(2,rnd(128),rnd(128),0.05,0,4)
“rnd” means random number, so “rnd(128)” means pick a random number between 0 and 128. It actually means fractions of numbers too, with 0 being the lowest possible number, and 127.999 the highest. That’s accurate enough for us here, and it provides a way of randomly generating x and y coordinates for our new enemy.
Now run the game. Every 200 “ticks”, we get a new enemy! That seems like a lot of work just to get multiple enemies, but you can see if you implement something like this at the start, updating, moving and drawing many, many enemies (or other objects) all in one go is a breeze.
Next time, we’ll look at making our game a bit more visually interesting, by adding animation to the enemies.
All the code so far:
function _init() -- player start location x = 64 y = 64 ticks = 0 tickstrigger = 200 dinc = 0.05 -- score score = 0 -- initialise enemies table enemies = {} -- make first enemy make_enemy(2,120,120,0.05,0,5) end function _update() -- check for collision foreach(enemies, function(en) if checkcol(x,y,en.x,en.y) then stop() end 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 enemies foreach(enemies, function(en) en:move() end) -- increment score score += 1 -- if ticks are triggered, make enemy faster ticks += 1 if ticks > tickstrigger then foreach(enemies, function(en) en.d += dinc end) ticks = 0 make_enemy(2,rnd(128),rnd(128),0.05,0,4) 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 en.move = function(this) if x < this.x then this.x -= this.d end if x > this.x then this.x += this.d end if y < this.y then this.y -= this.d end if y > this.y then this.y += this.d end end add(enemies,en) end