2D Space Shooter Part 3: Ships
The objective in a space shooter game is to destroy enemy spaceships, so lets work on that part. There are two kind of ships in the game:
- The player ship, which is controlled by the player and makes the game end when it is destroyed
- The enemy ships, which are computer controlled
However both kind of ships have common characteristics: armour, cannons, they can be hit by bullets, etc. Since we want to reuse code as much as possible, we'll create a Ship class to handle common features. But first lets define the actual ships we will have in our game.
Ships models
Our ships will have a texture, a few cannons, and a certain armour resistance. We'll define them in a lua table. Create a ships.lua file and insert the ships description:
local SHIPS={
A={ file="ship_1.png", armour=10, size=1,
cannons={
{ x=49,y=0, type="laser", rate=30 },
}},
B={ file="ship_2.png", armour=15, size=2,
cannons={
{x=20,y=30, type="laser", rate=25 },
{x=96,y=30, type="laser", rate=25 },
}},
B={ file="ship_3.png", armour=50, size=3,
cannons={
{x=108,y=0, type="missile", rate=80 },
{x=172,y=0, type="missile", rate=80 },
{x=40,y=105, type="laser", rate=20, angle=-15 },
{x=240,y=105, type="laser", rate=20, angle=15 },
}},
Z={ file="ship_0.png", armour=100, size=2,
cannons={
{x=22,y=45, type="laser", rate=15 },
{x=155,y=45, type="laser", rate=15 },
}},
}
local SHIP_SIZE=512/6 --Logical size of a 1 unit ship
Each key in our table is a ship type. I choose single letter type names to make it easier to create our levels later. Each entry describe the ship characteristics:
- the texture file to use
- the armour strength
- the relative size of the ship
- an array of cannons. Cannons have a position and angle in the source image, a bullet type and a fire rate
We'll want to arrange our ships on a grid when they arrive on screen, as if they were part of a wing. To do so, we need our ships to be scaled appropriately to fit cells. That is what the relative size field is for, it will tell how many cells this ship is supposed to span. The final line of the above code block specifies the size of a grid cell. We computed it so that five small ships can span the screen width, but divided by 6 to leave a bit of margin.
The Ship class
We'll use the same technique as the Background, and have our Ship class extend Gideros Pixel. It will look like this:
Ship=Core.class(Pixel,function (type) end)
function Ship:init(type)
local ship_def=SHIPS[type or "Z"]
assert(ship_def,"No such ship type: "..type)
-- Set up our ship based on its definition
...
-- Add to actors list
ACTORS[self]=true
end
function Ship:destroy()
-- Clean up
ACTORS[self]=nil
-- Remove from screen
self:removeFromParent()
end
function Ship:tick(delay)
-- Do whatever needs to be done on the game loop (move, fire cannons, etc)
end
The 'Ship:init' method will look up the given ship type from the ships definition list, defaulting to the "Z" type if no type was supplied, then initialize itself from that ship description. As a last step of its initialization, it will register itself to a global list of actors in the game. The idea here is to have each ship take care of itself without cluttering the main game loop.
We'll change our game loop to call the 'tick' method of each object in the ACTORS list. Add this early in your main.lua code:
-- A list of all objects that should receive frame ticks
ACTORS={}
And the following code inside your game loop:
for k,_ in pairs(ACTORS) do
k:tick(1)
end
The game loop code should look like this now:
-- This is our game loop
stage:addEventListener(Event.ENTER_FRAME,function ()
background:advance(1)
for k,_ in pairs(ACTORS) do
k:tick(1)
end
end)