Remember That Movie From The 80s
Coming up with the title or this post made me remember that movie Enemy, Mine from the 80s. I think it was starring Dennis Quaid and Lewis Gossit Jr. They were enemies crash landed on a hostile deserted world and had to work together to survive. Then of course they become friends and have a bunch of adventures.
Interesting that they were both starship pilots and I'm working on a space shooter game... cause you know I liked them when I was a kid... in the 80s...
Making Sparks
To copy another element from Cave Berries, because I really like the way the laser sparks when it hits a wall, I added a sparks function/method to the bulletconstruct function:
obj.sparks = function(this) if (this.des > 0) then for i = 1,4 do rnd_x = flr(rnd(12)) modx = 2 rnd_y = flr(rnd(4)) c = 11 if (i % 2 == 0) then rnd_x += modx, c = 3 end pset(this.position.x - 4 + rnd_x, this.position.y + rnd_y, c) end end end |
The sparks method (I'm going to go ahead and refer to a function on an object/table as a method) also needs to be called in the _draw method in order to actually have the pset function work:
foreach(bullets, function(obj) spr(obj.sprite, obj.position.x, obj.position.y) obj.sparks(obj) end) |
The above is the updated foreach bullets in the _draw function. Also, inside the bulletconstruct destroy method call it:
obj.destroy = function(this) this.des += 1 this.sparks(this) this.hitbox = {x=0,y=0,w=0,h=0} end |
There now we have a cool visual... but no sound effect cause you know... space and all.
First Enemy
As you might be able to tell I whipped up an enemy ship that shoots out a red and orange bullet/bomb/beam out our plucky hero. To get things working we'll copy 'n paste a lot of code from the asteroid and bullet objects. Add an enemy function:
function enemy() local obj = {} -- only come in from random top area. x = flr(rnd(127)) -- set the direction randomly left, right, or center xs = {0, 1, 2} rnd_x = xs[flr(rnd(3)) + 1] if(rnd_x == 2)then rnd_x = -1 end obj.dir = rnd_x obj.position = {x=x, y=0} obj.sprite = 22 obj.update = function(this) this.position.y += 1 this.position.x += obj.dir if(this.position.x == hero.position.x or this.position.x + 8 == hero.position.x + 8)then obj.fire(obj) end -- remove it when it goes off screen if(this.position.y>127)then del(asteroids, this)end if(this.hp <= 0)then this.des += 1 end if(this.des >= 20)then this.sprite = 24 end if(this.des >= 40)then del(enemies, this) end end obj.explode = function(this) this.sprite = 23 end obj.hitbox = {x=0, y=2, w=8, h=8} obj.hp = 10 obj.des = 0 obj.fire = function(this) if (this.des == 0) then text = "enemy fire!!! ..." add(bullets, bullet(this.position.x, this.position.y + 10, 'down', 6)) end end return obj end |
At this point the enemy is pretty much the same as an asteroid. Notice the single line if in the update method though. If the enemy happens to line up with the hero on the x axis, then FIRE! The fire method is new and basically adds a bullet to the bullets array.
Notice that the function call is different than what we used before. I did some refactoring of the $construct functions and made them "first class". Not sure what assigning a function to a variable gets you in Lua, but it seems to work the same as using the function keyword.
Also, I setup the symbols-tree-view package for Atom to be able to list the functions in a list in the right-hand side of the editor window. I find it super useful when jumping around between functions. The details can be found in this thread. Thanks to @Sanju for pointing it out to me.
Updated bullets
Here's the updated bullet definition:
function bullet(x, y, dir, sprite) local obj = {} obj.position = {x=x, y=y} obj.sprite = sprite obj.des = 0 obj.dir = dir obj.update = function(this) if(this.des == 0 and this.dir == 'up')then this.position.y -= 6 end if(this.des == 0 and this.dir == 'down' and this.sprite == 6)then this.position.y += 4 end -- it's hit something if(this.des > 0)then this.destroy(this) end if(this.des >= 5)then del(bullets, this) end -- remove it when it goes off screen if(this.position.y<0)then del(bullets, this)end end obj.hitbox = {x=0, y=0, w=8, h=8} obj.destroy = function(this) this.des += 1 this.sparks(this) this.hitbox = {x=0,y=0,w=0,h=0} end obj.sparks = function(this) -- if(this.des > 0)then pset(this.position.x, this.position.y, 10) end if (this.des > 0) then for i = 1,4 do rnd_x = flr(rnd(12)) modx = 2 rnd_y = flr(rnd(4)) c = 11 if (i % 2 == 0) then rnd_x += modx, c = 3 end pset(this.position.x - 4 + rnd_x, this.position.y + rnd_y, c) end end end --return the bullet return obj end |
The main changes are in the parameter list. Since enemies will be firing down and will use different beams we need to specify a direction and a sprite. Then in the update method the direction is checked and the bullet's y position is either incremented or decremented.
Updating update
Next, the _update function needs adjusted to take into account enemies and having enemy bullets hit our hero. Before we dive into that let's take a look at the each_enemies function. The _update function was starting to get a little crowded so I moved some looping code into another function:
function each_enemies() foreach(enemies, function(enemy) enemy.update(enemy) -- collide enemies with bullets foreach(bullets, function(bullet) if (enemy.hp > 0) then if (collide(enemy, bullet)) then text = "enemy hit!!!" -- display bullet sprite bullet.destroy(bullet) enemy.hp -= 1 -- knock it back a little enemy.position.y -= 2 if(enemy.hp <= 0) then enemy.explode(enemy)end end end end) -- enemy collides with hero? if (collide(hero, enemy) and enemy.des == 0 and hero.des == 0) then sfx(01) enemy.hp -= 1 hero.hp -= 1 -- knock it back a litte enemy.position.y -= 2 enemy.position.x -= enemy.dir + enemy.dir if(enemy.hp <= 0) then enemy.explode(asteroid) end if(hero.hp <= 0) then hero.explode(hero) end end end) end |
Basically the same as the asteroids loop this function uses a foreach to loop the enemies array (added to the _init function) and inside the enemies loop the bullets array is looped an collide is used to check for hits on the enemy ship. Like the astroids loop collide is also used to check for collisions with the hero ship. Thinking to have a "drone" type enemy that will try to blast into the hero, but we're not quite there yet.
Then inside the _update function add:
each_enemies() |
Getting due for some major refactoring, but that'll probably be a post all by itself.
Hero Bullet hits
In the hero object setup inside the _init function we need to do another bullet loop:
-- bullet hits hero foreach(bullets, function(bullet) if (this.hp > 0) then if (collide(this, bullet)) then text = "hero hit!!!" sfx(4) -- display bullet sprite bullet.destroy(bullet) this.hp -= 1 -- knock it back a little this.position.y += 2 if(this.hp <= 0) then this.explode(this)end end end end) |
The loop is placed in the hero's update method and is similar to the other bullet loops. This loop uses collide to check if they are hitting the hero, sets the text for debug purposes, plays a sound cause there's probably be some noise when something hits the outside of a ship in space, subtracts a hp while knocking the hero back a little, and then a check to see if the hero is out of hp and if so call the explode method.
Conclusion
Currently the enemies only enter the screen when the btn(5) is pressed. So I think next I'll work on some AI/attack patterns. Should make the game more fun.
Then the great refactor might happen. Not sure when I'll hit the token ceiling, but I feel like I've still got some space... (heh)
Party On!
[Please log in to post a comment]