Adding the interface

Now that the game displays a level correctly, we need to start adding the ability to communicate with it.

Getting ready

We imported a pre-packaged interface.lua file from version 1, but it doesn't do anything. Actually, it does one thing: it returns a blank group, which fills in the space that the rest of the game needs occupied to work, but doesn't actually send or respond to any events.

Note

This practice, when programming complex systems, of leaving a minimal dummy placeholder for a larger chunk of code in place until the actual implementation can be created is called making a stub.

Open the interface.lua file and remove the starting placeholder that says:

  return display.newGroup()

Getting on with it

We'll expand the interface group with the required behaviors:

  1. The interface will still be represented by a display group, so we'll break it out into a variable so that we can add to it before we return it:
    return function(game)
      local self = display.newGroup()
      return self
    end
  2. The design says that the interface will display a move count in the upper-left, so we'll create a text object, which starts out displaying 0:
      local self = display.newGroup()
      local counter = display.newText(self, "0", 10, 10, native.systemFont, 24)
      counter:setReferencePoint(display.CenterLeftReferencePoint)
      return self
    end
  3. When a Move event happens on the game object, this count needs to be updated. The following pattern to adjust the text and keep it aligned should be familiar:
      counter:setReferencePoint(display.CenterLeftReferencePoint)
      function counter:Move(event)
        if event.count then
          local x, y = self.x, self.y
          self.text = event.count
          self:setReferencePoint( display.CenterLeftReferencePoint)
          self.x, self.y = x, y
        end
      end
      game:addEventListener('Move', counter)
      return self

    In other words, the counter listens for the Move events on the game; if the event has an associated count of moves taken, the counter updates itself to display that count.

  4. It was also stated that the counter fades if the game is left undisturbed. We need to know what to monitor for touch events that would wake it up; while we could use Runtime, that creates problems when the scene stops being active (we'll discuss that problem in more detail in a later project). The interface will collect all its touch and tap events from the scene's associated group, which collects all touches on its children (such as the world scene) that aren't handled by those children.
    return function(game)
    	local self = display.newGroup()
      local target = game.view
      local counter = display.newText(self, "0", 10, 10, native.systemFont, 24)
  5. Whenever a touch on this target is detected, the counter needs to cancel any previous fading it had planned, and start a new timer. We can make the timer start fading and the fade over time simple by using a transition with an initial delay, which is reusable:
    local fade = {delay = 5000, time = 750; alpha = 0}
    return function(game)
  6. The touch handler becomes fairly easy to write at this point; we need to track the transition for the fade event, so it can be cancelled, which is easily done as a property of the text object:
          self.x, self.y = x, y
        end
      end
      function counter:touch(event)
        if self.Fade then 
          transition.cancel(self.Fade)
        end
        self.alpha = 1.0
        self.isVisible = true
        self.Fade = transition.to(self, fade)
      end
      target:addEventListener('touch', counter)
      game:addEventListener('Move', counter)
  7. Finally, we need to start the timer running when the interface is created, or the counter will sit on the screen at the game start without fading until the player touches the screen:
        self.Fade = transition.to(self, fade)
      end
      counter:touch()
      target:addEventListener('touch', counter)

Creating the Move requests

The last element of the interface is not complicated code, but it takes some real math to design properly. We need to identify taps as being in one of the four compass directions compared to the screen. To reduce user mishaps, we will discard taps that happen too near a boundary between one direction and another.

Creating the Move requests

Black regions indicate where touches will move the character

We have two challenges here: recognizing whether a touch is in one of the active zones or not, and "snapping" the directions being output to the exact compass points. We can get the angle of the touch with the math.atan2 function, but the tough part is the east quadrant. The polar axis, the point where the zero angle lies on graphs, is right in the middle of it. Angles that should be registered for the eastern direction range from 0-π/8 (Lua's trigonometric functions all return results in radians), but also from 15π/8-0. While this problem could be solved with a long if/elseif statement, we can solve the whole problem fairly simply by just adding π/8 (one-sixteenth of a circle), moving the angles under consideration to line up with the zero line.

Creating the Move requests

At this point, solving both questions becomes much easier. We can slice the circle into eight wedges, and determine which wedge the specified angle falls into by dividing the angle by one-eighth of a circle, π/4. You can clip the fractional part of the wedge off with math.floor() and normalize it into a range (eliminating negative angles or angles over a full circle) by taking the result modulus 8 (that is, dividing by eight and keeping only the remainder).

In this way, a fractional number from 0 to 2π is converted into an integer from zero to seven. Because we're only interested in the wedges zero, two, four, and six, we can compare the wedge index modulus 2 to 0 to determine if it's a valid touch, and then multiply the wedge number by an eighth of a circle, π/4, to obtain the final direction, pointing straight right, down, left, or up.

local function checkMove(self, event)
    local direction = math.atan2(event.y - display.contentCenterY, event.x - display.contentCenterX) + math.pi / 8
    direction = math.floor(direction * 4 / math.pi) % 8
    if direction % 2 == 0 then
      self:dispatchEvent{name = 'Move'; action = 'attempt', direction = direction * math.pi / 4 }
    end
  return true
end
return function(game)
  local self = display.newGroup()

This new function just has to be attached to the interface to handle touches on the target using the following code:

  local target = game.view
  self.tap = checkMove
  target:addEventListener('tap', self)
  local counter = display.newText(self, "0", 10, 10, native.systemFont, 24)

The dispatchEvent call triggers an event targeted on the interface that indicates that the player tried to move in a particular direction. This event has no clue about whether the attempted move is legal or will produce any results. It only says that the player tried to move, and which way.

What did we do?

We fleshed out the interface so that the user knows how deep they are into the game, and added a little polish so the UI feels more professional. We also created a layer that takes a complex event and reduces it to just the information the game needs to know.

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

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