2D Space Shooter Part 5: Firing

From GiderosMobile
Revision as of 16:55, 12 July 2023 by Hgy29 (talk | contribs) (Text replacement - "<source" to "<syntaxhighlight")

Last chapter was quick, this one will be longer. We will now deal with weapons!

Cannons

Remember how we defined cannons in our Ship class ? It is now time to implement them. A cannon is basically something that will throw bullets repeatedly. Create a new file named 'cannon.lua' and paste the following code:

<syntaxhighlight lang="lua"> Cannon=Core.class(Object)

function Cannon:init(def,scale,ship) self.x=def.x*scale self.y=def.y*scale self.type=def.type self.rate=def.rate self.angle=def.angle or 0 self.ship=ship self.reload=0 end

function Cannon:fire() if self.reload==0 then local x,y=self.ship:localToGlobal(self.x,self.y) local bullet=Bullet.new(self.type,self.angle+self.ship:getRotation(),x,y) bullet.friendly=self.ship.friendly self.reload=self.rate else self.reload-=1 end end </source>

The Cannon class isn't a Sprite, but a basic Object. In the 'init' method, we mostly copy the cannon definition locally and initialize our reload counter that will be used to count time between each shot. We also compute the ship-relative position of the cannon.

In the 'fire' method, we decrement the reload counter until it reaches 0. When at 0, we create a Bullet and reset our reload counter to the cannon rate value.

Bullets

The Bullet class needs more work: it will be a graphic object (inherited from Pixel), but will also have to check collisions. Here is the Bullet code, to copy into a bullet.lua file:

<syntaxhighlight lang="lua"> local BULLETS_DEF={ laser={ file="laser.png", speed=5, damage=1 }, missile={ file="rocket.png", speed=1, damage=10 }, }

Bullet=Core.class(Pixel,function (type) end)

function Bullet:init(type,angle,x,y) local bullet_def=BULLETS_DEF[type] assert(bullet_def,"No such bullet type: "..type) local texture=Texture.new("gfx/"..bullet_def.file,true) local tw,th=texture:getWidth(),texture:getHeight() local scale=0.3 self:setTexture(texture) self:setDimensions(tw*scale,th*scale) self.damage=bullet_def.damage self.speed=bullet_def.speed self:setAnchorPoint(0.5,0.5) self:setRotation(angle) self:setPosition(x,y) local ax,ay=self:getAnchorPosition() self.dx,self.dy=math.sin(^<angle),math.cos(^<angle) BULLETS:addChild(self) -- Add to actors list ACTORS[self]=true -- Add to collision world BUMP_WORLD:add(self,x-ax,y-ay,tw*scale,th*scale) end

function Bullet:destroy() -- Remove from collision world BUMP_WORLD:remove(self) -- Remove from actors list ACTORS[self]=nil -- Remove from screen self:removeFromParent() end

-- This function will check collisions between this bullet (item) and some other object (other) local function collision_filter(item,other) -- Other can be a Ship or another Bullet -- If it has a property named 'damage' then it is a Bullet, and we don't want bullets to collide with each others if other.damage then return nil end -- We also want to avoid being killed by our own shots, or enemies killing each others if item.friendly==other.friendly then return nil end -- Now we have a Bullet colliding with an enemy ship return 'touch' end

function Bullet:tick(delay) local x,y=self:getPosition() x+=self.dx*self.speed y-=self.dy*self.speed self:setPosition(x,y) local cols,colslen x,y,cols,colslen=BUMP_WORLD:move(self,x,y,collision_filter) for k,col in pairs(cols) do col.other:hit(self.damage) end if x<SCR_LEFT or x>SCR_RIGHT or y<SCR_TOP or y>SCR_BOTTOM or colslen>0 then self:destroy() end end </source>

We borrowed a lot of code from the Ship class: the setting up of the sprite itself is very similar, and the bullet is being added and removed from collision world and actors in the same way.

There are two major changes:

  • The bullet will move by itself.

For that we precomputed its movement direction from its angle with a little bit of help from trigonometry (sin/cos) in 'Bullet:init', and we use that direction (self.dx,self.dy) and the bullet speed in 'Bullet:tick' to increment the bullet position.

  • The collision detection

Once we have computed the new bullet position, we pass it to bump engine and check for eventual collisions. If something is hit, we call its 'hit' method and give it the amount of damage the bullet should cause.

We don't want the bullet to collide with other bullets, and we don't want either a bullet to hit the ship type that fired it. To tell this to bump engine, we use a collision filter function. The filter function returns nil (or false) if objects don't collide. Otherwise it returns a collision type, which is, in our case 'touch'.

Before going further, let's implement the 'hit' function in the Ship class: it will decrease our armour resistance and make and call a future 'explode' function when appropriate:

<syntaxhighlight lang="lua"> function Ship:hit(damage) self.armour-=damage if self.armour<0 then self:explode() end end </source>

Testing

Now that our cannons and bullets are defined, we can uncomment the Cannon instancing in ships.lua, 'init' method: <syntaxhighlight lang="lua"> self.cannons[k]=Cannon.new(cdef,scale,self) </source>

We'll also need to place the weapons graphics in gfx folder.

Now your ship will fire when you hold the mouse button down:

2D Space Shooter Part 6: Enemies