Cleaning up and making the game playable

There are three issues left before the game fulfills its design:

  • Filtering collisions based on groupIndex alone doesn't prevent bullets from hitting each other, or allow the player ship to fly over turrets without colliding with them.
  • One enemy before the boss is not going to provide a satisfying challenge. The level should provide several fighters and turrets to challenge the player over the level's course.
  • The player can't actually lose lives or fail the game. This isn't a bug, but a feature that's deliberately been disabled until the end, in order to make the game easier to test.

Getting on with it

In order to control collisions more accurately, we'll use an additional feature of collision filters—category masking. This feature allows you to specify up to 16 categories that an object can belong to, as well as which of those categories the object will collide with. An object is not required to collide with objects from its own category. If two objects overlap and only one of them can collide with the other, no collision takes place and the two objects pass through each other.

The category and mask for an object are defined as bitmasks, numbers where each bit in the representation is treated as a separate on-off value. The easiest way to write these values in your code is as hexadecimal integers: 0x0001, 0x0002, 0x0004, and so on. You can then add these values together to make composite bitmasks.

We want the player ship to collide with enemy bullets and enemy ships; it is already prevented from colliding with its own bullets or any friendly ships the game could contain by sharing the same negative groupIndex value. We will use the bit value 0x0001 to signify ships, and the bit 0x0002 to signify bullets; to keep this straight and clear, we'll define these in a file. Open the file category.lua and add three new entries to the table being returned:

return {
  ship = 0x0001,
  bullet = 0x0002,
  all = 0xFFFF;
  general = 0,

Save this file and open player.lua; set the filter used by the player object to create an object that belongs to the ship category and collides with objects from either the ship or bullet category.

local playerFilter = {
  groupIndex = category.player; 
  categoryBits = category.ship, maskBits = category.ship + category.bullet
}

The dogfighter.lua and boss.lua files get the same treatment; these flying ships can collide with the player's ship (destroying them both) or the player's bullets, as shown in the following code:

local enemyFilter = {
  groupIndex = category.enemy;
  categoryBits = category.ship, maskBits = category.ship + category.bullet;
}

Make this change to both files using the following code, then open turret.lua. This one requires slightly different treatment; player bullets can destroy the turrets, but the player's ship can fly freely over them without either being destroyed.

local groundFilter = {
  groupIndex = category.enemy;
  categoryBits = category.ship, maskBits = category.bullet;
}

Notice that the turret's mask doesn't include other ships; this means that even if those ships are set to collide with ships (which the turrets still count as), they won't, since one-way collisions don't count.

Once you've saved this change, open weapon.lua. This one is slightly different because each weapon is owned by a particular group, and fires bullets that belong to that group, not hitting other objects from it. Find the anonymous function that is returned from the internal local function weapon, where the filter for each new weapon is defined from the group index used to create it as shown in the following code:

    self.filter = {
      groupIndex = groupIndex; 
      categoryBits = category.bullet, maskBits = category.ship
    }
    return self

Collision filters are now set up; bullets don't collide with each other, ships only collide if they're on different sides, and turrets can be shot but don't directly prevent flyovers.

Adding challenges to the level

Adding new enemies to the schedule can be simple or complex. Creating a turret is as simple as specifying a position, and optionally waiting for a specified time by using the at (time) function. As we've seen, creating a fighter is still simple, but requires supplying a function that lays out the ship's flight plan. Fighters and their patterns constitute the main attraction of a game like this, so we'd probably like quite a few, flying in variable patterns. Rather than copying and pasting the flight plan code for each spawn command, we can produce variations on common flight patterns by using function factories.

A function factory is another function that takes some arguments of the programmer's choice, and produces another function that doesn't have to run right away, but, when it does run, it produces a common behavior adjusted by the arguments that were used to create it. Each time the factory is used, it produces a new version of the desired behavior function, which also means that each of these functions can have its own environment (which is important for our ship controllers). We've already created one factory, actually, in the form of the orbit function that controls the boss. Now we'll produce a few more for a variety of flight behaviors.

In level/marsh-enemies.lua, create a new function, cross. This function will produce variable versions of the flight plan used by the dogfighter module we already created, that flies straight down across the screen, firing, then turns off the gun and veers off to one side before leaving the screen. The cross function will take one argument, the angle to turn left or right when it changes course as shown in the following code:

local schedule = require "schedule"
local function cross(angle)
  return function()
  end
end
local function orbit()

The body of the returned function will resemble the anonymous function supplied to our sample dogfighter function. You can enter it again or cut and paste the previous code; note that the fixed turn, 60, is replaced with the enclosing angle argument.

local function cross(angle)
  return function()
    face(90)
    go(100)
    fire("MachineGun", 50, 0)
    after (1) turn(angle, 0.75)
    release("MachineGun")
  end
end

Trim out the existing entry for the dogfighter at the 0.5 seconds, and add in a few lines that use this function about halfway through the level:

      at (0.3) spawn.turret(50, -20)
      at (26) spawn.dogfighter(140, -20, cross(-75))
      at (27.5) spawn.dogfighter(180, -20, cross(75))
      at (29) spawn.dogfighter(140, -20, cross(-75))
      at (58) spawn.boss(160, -40, orbit(), bossOneDestroyed)

You can test this, if you like, to see how the fighters come out one by one.

We're going to add new patterns for a few more fighters, but first let's drop in a couple more turrets over the course of the map.

      at (0.3) spawn.turret(50, -20)
      at (12) spawn.turret(245, -20)
      at (26) spawn.dogfighter(140, -20, cross(-75))
      at (27.5) spawn.dogfighter(180, -20, cross(75))
      at (29) spawn.dogfighter(140, -20, cross(-75))
      at (35) spawn.turret(275, -20)
      at (42.5) spawn.turret(40, -20)
      at (58) spawn.boss(160, -40, orbit(), bossOneDestroyed)

We'll add two more flight plan factories using the following code: one that just flies right across the screen at a chosen angle, and one that flies in a wide arc and travels back the way it came. Both start and stop shooting during their course.

    release("MachineGun")
  end
end
local function strafe(angle)
  return function()
    face(angle)
    go(100)
    fire("MachineGun", 50, 0)
  end
end
local function swoop()
  return function()
    face(90)
    go(100)
    after (0.1) turn(30, 0.75)
    fire("MachineGun", 50, 0)
    turn(-240, 3)
    release("MachineGun")
    turn(30, 0.75)
  end
end
local function orbit()

We can spice things up after the first turret with a couple of the simpler flyers using the following code:

      at (0.3) spawn.turret(50, -20)
      at (6.5) spawn.dogfighter(330, -15, strafe(120))
      at (9) spawn.dogfighter(-10, -15, strafe(60))
      at (12) spawn.turret(245, -20)

An advantage of presenting the flight plans as functions in Lua is that they can contain loops and choices like any code. We'll generalize the fliers using the swoop pattern using a for loop:

      at (12) spawn.turret(245, -20)
      for i=1,3 do
        at (20 + i) spawn.dogfighter(180, -20, swoop())
      end
      at (26) spawn.dogfighter(140, -20, cross(-75))

Save marsh-enemies.lua and try running through the whole level and see how the different enemies appear. Right now you're still invincible!

Enabling finite lives

In this particular case, we'll take the easy way out. The code to subtract lives when the player is hit was in the code when we started the project, and was turned off to make debugging and testing easier. This code is extremely similar to a module that was created in the Deep Black project, which is why it isn't covered in detail here.

Open game.lua and find the commented line that registers the game object to track Destroy events on the player and add the following code:

  local Player = player(self, display.contentCenterX, display.contentCenterY)
--Player:addEventListener('Destroy', self)
  self.Player = Player

Uncomment the line to allow the game to remove a life or end the game whenever the player's ship is destroyed:

  local Player = player(self, display.contentCenterX, display.contentCenterY)
  Player:addEventListener('Destroy', self)
  self.Player = Player

It's often advantageous to do things like this, creating systems early to make sure they work, then disabling them while they're inconvenient. It's time to try out the game! Careful, it's pretty hard.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.115.195