2D Space Shooter Part 6: Enemies

This won't be a space shooter without enemies to shoot. Let's define our enemies. Again we will subclass our ship class for enemies.

Create a new file named 'enemy.lua'. This new file will depend on the Ship class too, so right click on the file name in gideros studio and access 'Code dependencies'. From there tell Gideros that enemy.lua will depend on ships.lua.

Enemy class

Enemies will come from the top of the screen, so ships will head toward the bottom.

EnemyShip=Core.class(Ship)

function EnemyShip:init(type,x,y)
	self.posx=x
	self.posy=y
	self:setRotation(180)
	self.fire=true
	ENEMIES:addChild(self)
end

function EnemyShip:advance(amount)
	self.posy+=amount
end

function EnemyShip:tick(delay)
	self:setPosition(self.posx,self.posy)
	Ship.tick(self,delay)
end

function EnemyShip:explode()
	-- Here we could show some explosion animation and play a sound
	self:destroy()
end

Level management

Now we need to make the enemies appear in waves. This part is a bit trickier, as it involves choosing a format to design our levels.

I decided to use a plain text representation: enemies will be placed in up to five positions on each row, possibly spanning multiple rows. So each line of our level definition will be six character long: one character for each possible position, indicating if a ship you be spawn at the location, and which kind of ship, plus a sixth character to control the ship waves: the sixth character with tell how much time to wait before the next row of ship shows up, or whether the row is the last of a wave.

In a new file called 'level.lua' we'll define our level manager, starting by our first (and only in this tutorial) level.

local LEVEL1=[[
..A..0
.....!
.A.A.0
.....!
..A..1
.A...1
...A.0
.....!
..A..0
.B.B.0
.....!
A...A0
..C..0
.....!
]]
local LEVELS={ LEVEL1 }

So this is our level data. We can almost see how ships will be shown on screen.

Now our loading (init) and sequencing (tick) code:

local SCROLL_DELTA=0.02

Level=Core.class(Object)

function Level:init(number)
	-- Will hold our level info
	self.levelinfo={}
	-- Will hold our position in the level
	self.levelline=1
	-- Hold all the enemy ships in the current wave
	self.enemies={}
	-- Hold the enemy line scroll amount
	self.scroll=0
	-- Fill in level info
	for level_line in LEVELS[number]:gmatch("[%w%p]+") do
		assert(#level_line==6,"Level description line is not 6 characters long.")
		local wait=tonumber(level_line:sub(6)) or 0
		local clear=level_line:sub(6)=="!"
		table.insert(self.levelinfo,{ enemies=level_line:sub(1,5), wait=wait, clear=clear})
	end
	--Register as actor
	ACTORS[self]=true
end

function Level:tick(delay)
	if self.scroll==0 then 
		--Not started scrolling: build an enemy row
		local lineinfo=self.levelinfo[self.levelline]
		for i=1,5 do
			local type=lineinfo.enemies:sub(i,i)
			if type~="." then
				local enemy=EnemyShip.new(type,SHIP_SIZE*i+SCR_LEFT,SCR_TOP-SHIP_SIZE/2)
				self.enemies[enemy]=true
				enemy:advance(SCROLL_DELTA*SHIP_SIZE)
			end
		end
		self.scroll=SCROLL_DELTA
	elseif self.scroll<1 then
		self.scroll+=SCROLL_DELTA
		for k,_ in pairs(self.enemies) do
			if ACTORS[k] then
				k:advance(SCROLL_DELTA*SHIP_SIZE)
			end
		end
	else
		local lineinfo=self.levelinfo[self.levelline]
		if lineinfo then --If no lineinfo, it means that we reached the end of the level
			if lineinfo.wait>0 then
				lineinfo.wait-=SCROLL_DELTA
			else
				local enemy_count=0
				if lineinfo.clear then
					for k,_ in pairs(self.enemies) do
						if ACTORS[k] then enemy_count+=1 end
					end
				end
				if enemy_count==0 then
					self.levelline+=1
					if lineinfo.clear then
						self.enemies={}
					end
					if self.levelinfo[self.levelline] then
						self.scroll=0
					else
						-- Level finished
						print("FINISHED")
					end
				end
			end
		end
	end
end

Kicking off our level

It is time to see what we just did: let's start our level from main.lua by adding a single line:

Level.new(1)


2D Space Shooter Part 7: Boosters