Controlling the boss

No game like this is complete without an awe-inspiring boss craft or base that offers a more complex fight. Because boss crafts don't typically fly around in circles, a new action is called for in plan.lua; hover. It's very similar to turn in its implementation, so we won't cover it in too much detail.

Getting on with it

First, open plan.lua and add the hover function to the environment:

    self.Speed = speed
  end
  function actions.hover(xMove, yMove, duration)
    duration = duration * 1000
    local start = system.getTimer()
    while duration > 0 do
      local elapsed = coroutine.yield()
      elapsed, start = elapsed.time - start, elapsed.time
      local fraction = math.min(1, (elapsed / duration))
      local xDrift, yDrift = xMove * fraction, yMove * fraction
      xMove, yMove = xMove - xDrift, yMove - yDrift
      self.x, self.y = self.x + xDrift, self.y + yDrift
      duration = duration - elapsed
    end
  end
  function actions.turn(angle, duration)
    duration = duration * 1000

Creating the boss object

Now that the AI script actions are complete, we'll create the file that will accept these actions and advance the game when a boss is destroyed. Make a copy of dogfighter.lua in the top level of the project and name it boss.lua. The following are the steps to create a boss object:

  1. The module function will accept a flight plan function like the dogfighter module does, but also accepts a function that dictates what will happen when the boss is defeated:
    return function (game, x, y, path, advance)
      local self = ship.dreadnought(game, game.Mobs.Air, enemyFilter)
    
  2. Record the advance function in the boss's table so that we can retrieve it and run it when the boss dies:
      self.Speed = 0
      self.Advance = advance
      local plan = require "plan" (self, path)

Using a supplied function gives us choices about how bosses appear. For instance, you could make a level where a boss appeared halfway through, and its defeat started a new schedule to proceed through the rest of the level.

Making the boss module available

In order for the schedule to be able to spawn a new boss object, the game's spawn table needs a reference to the module. Open game.lua and add a new line to the table near the top of the file.

scene.Spawn = {
  turret = require "turret";
  dogfighter = require "dogfighter";
  boss = require "boss";
}

Driving the boss's behavior

To launch the boss at the end of the level, we'll modify the level schedule. Open marsh-enemies.lua and find the end of the function being passed to the schedule.

  1. We'll add an entry a second or two before the schedule runs out to create the boss object:
          at (58) spawn.boss(160, -40, orbit(), bossOneDestroyed)
        end
      )
      
      return duration
  2. Because it's easy in this case, we'll specify what happens when the boss is defeated first:
    end
    local function bossOneDestroyed(self, game)
      game:dispatchEvent{name = 'Game'; action = 'ended', outcome = 'won'} 
    end
    return function(game)
  3. Then we'll create a function that returns a set of flight orders moving in a figure-eight pattern; first, the boss will move down from its spawning point at the top of the screen and slide over to one corner of its loop:
    local function orbit()
      return function()
        hover(0, 100, 2)
        after (1.5) hover(55, 0, 1.5)
      end
    end
    local function bossOneDestroyed(self, game)
  4. Then, its flight plan will go into a loop that will last until it is destroyed, moving down, then up and over, and then the same on the other side.
      return function()
        hover(0, 100, 2)
        after (1.5) hover(55, 0, 1.5)
        while true do
          after(1) hover(40, 50, 1.5)
          after(1) hover(-150, -50, 4)
          after(1) hover(-40, 50, 1.5)
          after(1) hover(150, -50, 4)
        end
      end
  5. As the boss moves, it will switch some of its guns on and off as it changes direction. Because these calls are repetitive, the flight plan can define reusable functions for them.
        after (1.5) hover(55, 0, 1.5)
        local function crossFan()
          fire(1, 50, 80); fire(2, 50, -80)
          release(3); release(4)
          fire(5, 50, 13); fire(6, 50, -13)
        end
        local function focusForward()
          release(1); release(2)
          fire(3, 48, -15); fire(4, 48, 15)
          release(5); release(6)
        end
        while true do

    Tip

    This ability to keep using loops, functions, and other programming semantics inside a sandbox like this makes it especially powerful.

  6. Once it calls those functions appropriately, the flight plan is complete.
        while true do
          focusForward()
          after(1) hover(40, 50, 1.5)
          crossFan()
          after(1) hover(-150, -50, 4)
          focusForward()
          after(1) hover(-40, 50, 1.5)
          crossFan()
          after(1) hover(150, -50, 4)
        end

Handling the boss's defeat

The default onDestroy handler falls short in two regards where the boss is concerned. It needs to end the level (as determined by the boss's advance function), and frankly, one little explosion isn't impressive enough for a boss's victory. The following are the steps to handle the boss's defeat:

  1. Open ship.lua and frame in an onDestroy override for the dreadnought entry:
      dreadnought = ship {
        sprite = chassis [4];
        MaxHealth = 1000;
        onDestroy = function(game, event)
          local self = event.unit
        end;
        Weapons = {
  2. Most of the functions are still the same as the default onDestroy function, so we'll start by bringing them in. However, we'll hold off on removing the boss sprite as shown in the following code:
        onDestroy = function(game, event)
          local self = event.unit
          timer.cancel(self.Guidance)
          for id, weapon in pairs(self.Weapons) do
            weapon:stop()
          end
        end
  3. First, we'll start a repeating timer to set off explosions continuously on top of the boss sprite:
          local w, h = self.width / 2, self.height / 2
          local chainReaction = timer.performWithDelay(100, 
            function(event) 
              explosion(self.parent, self.xOrigin + math.random(-w, w), self.yOrigin + math.random(-h, h))
            end,
            0
          )
    
  4. Then, we'll start a one-use timer that will clean things up. It will stop the endless explosions, remove the boss, and trigger the boss's Advance function.
          self.Destruction = timer.performWithDelay(1000, 
            function(event) 
              timer.cancel(chainReaction)
              self:Advance(game)
              self:removeSelf()
            end
          )
    

Test things out! Once you make it past the other enemies and dodge the boss's guns, you'll see the boss going down in a blaze of glory. Having your character be invulnerable during development makes things like this much easier to test out.

What did we do?

We expanded the selection of actions available to the enemy's AI code. We created a new object type with a continuously repeating set of controls and internal logic to its actions. We also used one timer to regulate another, in order to create a visually interesting effect with a fixed duration.

What else do I need to know?

If you want to create functions for your control coroutines, they need to be defined inside the function that contains the instructions. Otherwise, they'll be created without the special environment that provides their instructions, and they won't respond properly.

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

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