Bridging physical events to game events

We now have physical events being passed in a very understandable format. We have a little bit of work left to do to translate these events (which are still specifically about the physical condition of the device) into commands for the player.

Getting ready

The math in this section is almost as abstract as the previous task, because the design specifies that the ship's acceleration is going to be based on how strongly the device's tilt aligns with the ship's facing direction, and the speed with which it turns will be based on how much the direction of tilt runs across the ship's facing.

Getting on with it

Save any other open files and open game.lua.

  1. Make sure the pythagorean distance module is loaded; we'll need it in this file as well:
    local scene = storyboard.newScene()
    
    require "math.pythagorean"
    
    local physics = require "physics"
  2. We'll start by explaining how Tilt events will be translated into Thrust and Yaw events on the game scene, before the event handlers:
    local physics = require "physics"
    
    function scene:Tilt(event)
    end
    
    function scene:createScene( event )
  3. We'll normalize the angles into range from -1 to +1 by using math.sin, then we'll determine the angle and length of the 2D vector they create using familiar methods.
    function scene:Tilt(event)
      local lateral, vertical = math.sin(event.lateral), math.sin(event.vertical)
      local theta, r = -math.atan2(lateral, -vertical), math.pythagorean(lateral, vertical)
    end

    Tip

    Because we're using math.sin, we're also moving the angles onto a quadratic scale as the tilt increases. This can often produce perfectly acceptable results, especially since most people playing tilt-based games don't tilt the device very far.

  4. Since we subtract the player's current rotation from the specified angle, we can redefine the lateral and vertical values as being across the direction of facing and along the direction of facing:
      local theta, r = -math.atan2(lateral, -vertical), math.pythagorean(lateral, vertical)
      theta = theta - math.rad(self.Player.rotation)
      lateral, vertical = math.sin(theta) * r, math.cos(theta) * r
    end
  5. The other thing we want to do is clip small values for both inputs, so that the player has some margin where they're not turning or accelerating, even if they can't hold the device exactly flat (which is nearly impossible):
    local threshold = 1/6
    
    function scene:Tilt(event)
      local lateral, vertical = math.sin(event.lateral), math.sin(event.vertical)
      local theta, r = -math.atan2(lateral, -vertical), math.pythagorean(lateral, vertical)
      theta = theta - math.rad(self.Player.rotation)
      lateral, vertical = math.sin(theta) * r, math.cos(theta) * r
      if math.abs(lateral) < threshold then lateral = 0 end
      if math.abs(vertical) < threshold then vertical = 0 end
    end
  6. Once this is done, we can submit the two values for Thrust and Yaw as events for the player object to catch:
      if math.abs(vertical) < threshold then vertical = 0 end
      self:dispatchEvent{name='Thrust'; value = vertical * 1/3}
      self:dispatchEvent{name='Yaw'; value = lateral * 64}
    end
  7. Then all we need to do is to set the scene to receive Tilt events whenever the scene is fully loaded, and stop receiving them when the scene leaves the foreground:
    function scene:enterScene( event )
      physics.start()
      Runtime:addEventListener('Tilt', self)
    end
    
    function scene:exitScene( event )
      Runtime:removeEventListener('Tilt', self)
    end

Tracking time passage

There are two challenges with running game objects directly off of the enterFrame event sent to Runtime:

  • It lists only the time right before each frame, without indicating how much time has actually passed.
  • If objects that want to update themselves all register individually for enterFrame events of Runtime, then if you want to do something like pause the game, you have to find each of these objects individually and disengage them, or build each listener to check some kind of common isPaused value.

We'll use a custom event, clock, to solve both of these things. The listener for this event will track the time of consecutive enterFrames to include the elapsed duration between them. Because any objects that need to know about time passing in the game will listen to the game scene itself for these clock events, the game scene itself can be responsible for turning them off by disconnecting itself from the Runtime events that drive them. We can turn only one listener on and off instead of an indefinite number.

The player object is already programmed to listen for these events, so we need to create the bridge that will send them.

  1. First, we'll specify how the scene will respond to enterFrame events when it's listening to them:
    local physics = require "physics"
    
    local clock, previous = 0, system.getTimer()
    function scene:enterFrame(event)
    end
    
    local threshold = 1/6
  2. The listener will compare the time of the current frame to the last time recorded:
    function scene:enterFrame(event)
      local elapsed = event.time - previous
    end
  3. It will increment the total accumulated time by the amount elapsed, and record the current time as the new previous time:
    function scene:enterFrame(event)
      local elapsed = event.time - previous
      clock, previous = clock + elapsed, event.time
    end
  4. Next it will post that passage of time to the scene so that any objects that need to can observe it:
      clock, previous = clock + elapsed, event.time
      self:dispatchEvent{name = "clock"; clock = self, delta = elapsed, time = clock}
    end
  5. We'll add a function that starts the clock, saving the current time so that the first frame received has a previous frame time to check against, and register the listener for enterFrame events:
      self:dispatchEvent{name = "clock"; clock = self, delta = elapsed, time = clock}
    end
    function scene:Start()
      previous = system.getTimer()
      Runtime:addEventListener('enterFrame', self)  
    end
    
    local threshold = 1/6
  6. Another function will stop the clock by discharging any time passed since the last update and removing the listener:
      Runtime:addEventListener('enterFrame', self)  
    end
    
    function scene:Stop()
      local elapsed = system.getTimer() - previous
      self:dispatchEvent{name = "clock"; clock = self, delta = elapsed, time = clock}
      Runtime:removeEventListener('enterFrame', self)
    end
    
    local threshold = 1/6
  7. Because time passes primarily within the world, whenever the scene is running, the game will respond to clock events by reposting them to the world object:
      Runtime:removeEventListener('enterFrame', self)
    end
    
    scene:addEventListener('clock', scene)
    function scene:clock(event)
      if self.view then
        self.World:dispatchEvent(event)
      end
    end
    
    local threshold = 1 / 6
  8. Finally, the scene will call these functions when it begins or ends:
    function scene:enterScene( event )
      physics.start()
      Runtime:addEventListener('Tilt', self)
      self:Start()
    end
    
    -- Called when scene is about to move offscreen:
    function scene:exitScene( event )
      Runtime:removeEventListener('Tilt', self)
      self:Stop()
    end

What did we do?

This task has been about interpreting the intended meaning of fundamental events; converting the angle of the device into instructions for the player object, and controlling how the passage of time for the device's clock controls the passage of time for the game.

At this point, you should be able to build the project for the device and test how the player ship moves as you tilt the device in various directions.

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

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