Thursday, July 26, 2012

Tutorial 8 - A Directional Pad (dPad) Class


8.1 A Skinned Directional Pad (dPad) Class



In Interlude 9 we looked at moving an object on the screen using 4 buttons. This is such a common requirement for games that the directional pad evolved and is now a ubiquitous part of any console controller. While this is not always the best control mechanism for touch screen device it has the advantage of being intuitive and simple.

We have used the earlier moveShip program to demonstrate this new dPad class. The program includes the optional standard Codea function orientationChanged(newOrientation), to demonstrate how the dPad can be automatically positioned based on the iPad orientation (i.e. landscape or portrait).




To see the dPad in action, you can download the entire moveShip code including the dPad class or just download the individual classes as required.
  1. Main v3.1 - The main moveShip class. In setup() a new dPad is instantiated using the statement: dPad = DirectionalPad(x, y). Where (x, y) are the CENTER screen co-ordinates for the dPad. CORNER alignment is not supported in v1.0 of the DirectionalPad class.
  2. Bullet v1.0 - A simple prototype bullet class. Every time you tap on any part of the screen that isn't on the dPad a new bullet will be spawned in the current ship direction. Note that this isn't a good implementation as you can only fire one bullet at a time (or the earlier bullet will stop). This will be refined in due course.
  3. DirectionalPad v1.0 - The new dPad class. The contents of this class are described in the next section. This class requires v1.2 of the modified Vega Mesh Button class.
  4. Button v1.3 - The (updated faster) modified Vega mesh button class. The following has been added to the base class: call back functionality, pushStyle() & popStyle(), tapped status, pointInRect() function and the location vec2 has been changed to x and y points.
  5. Twinkle v1.1 - The twinkling star background class courtesy of Ipad41001.
Because we are using sprites for the dPad skin you will need to add at least one of the following images to your linked dropbox account. The dimensions of the skins vary from 200 x 200 pixels to 250 x 250 pixels to match up with the four underlying directional buttons (up, down, left and right). If you want to create you own skins, you will need to play with the dimensions to best match the button orientation. 
  1. White dPad v1.0 (dPadW200x200.png).
  2. Coloured Buttons v1.0 (dPadCB200x200.png).
  3. Black dPad v1.0 (dPadTran250x250.png).
  4. PS3 Inspired v1.0 (dPadPS250x250.png).
  5. XBox Inspired v1.0 (dPadXB250x250.png).
The first three skins have transparent sections which allow the button glow to show through when the directional buttons are pressed.

We would suggest not using the PS3 or Xbox inspired versions if you plan submitting the associated App to Apple for approval!




8.2 The Directional Pad Class Code



We will now look at the complete Directional Pad Class code. 

--# DirectionalPad
DirectionalPad = class()

-- DirectionalPad Class
-- Reefwing Software (www.reefwing.com.au)
--
-- 21 July 2012
-- Version 1.0
--
-- Requires the modified @Vega Mesh Button Class v1.2

function DirectionalPad:init(x, y)

   -- These parameters are used to customise your dPad

The dPad class uses (x, y) to define the centre location on the screen for the dPad. The width and height is a constant in this version (250 x 250 pixels). The sprite skin may be less than this depending on the design. At the moment, only CENTER alignment is available but we will update this to handle CORNER alignment in a future version. 

The four directional buttons are then defined. You can access the button parameters via the dPad class (e.g. dPad.upButton.status is valid).

Finally there are two booleans, visible which is used to determine whether to draw the dPad and tapped which is true if the last touch was on the dPad.

   self.x = x
   self.y = y
   self.width = 250
   self.height = 250
   self.alignment = CENTER
   self.upButton = Button("", x - 25, y + 35, 50, 50)
   self.downButton = Button("", x - 25, y - 85, 50, 50)
   self.leftButton = Button("", x - 80, y - 25, 50, 50)
   self.rightButton = Button("", x + 35, y - 25, 50, 50)
   self.visible = true
   self.tapped = false

end

function DirectionalPad:draw()

   -- Codea does not automatically call this method
   -- The buttons are drawn under the sprite "skin"

   if self.visible then
       self.upButton:draw()
       self.downButton:draw()
       self.leftButton:draw()
       self.rightButton:draw()

After drawing the four directional buttons we overlay the sprite skin. Change the sprite name to change the "skin".

       sprite("Dropbox:dPadLight250x250", self.x, self.y)
   end
end

function DirectionalPad:moveBy(offset)

   -- This function is used to move the position of the
   -- dPad if the iPad orientation changes. This ensures
   -- that it is always visible.

This function is not called automatically. Have a look at the main class to see how to use this capability. You need to implement the orientationChanged(newOrientation) function and then define your offset (e.g. local offset = dPad.x - (WIDTH - dPad.width/2 - 20)) and apply it using dPad: moveBy(offset).

   self.x = self.x - offset
   self.upButton.x = self.upButton.x - offset
   self.rightButton.x = self.rightButton.x - offset
   self.leftButton.x = self.leftButton.x - offset
   self.downButton.x = self.downButton.x - offset

end

function DirectionalPad:touched(touch)

   -- Codea does not automatically call this method
   -- You need to pass through the touches to the
   -- Button class.
   --
   -- The tapped boolean keeps track of whether the tap
   -- was on the dPad (true) or elsewhere (false).

   self.tapped = false

   -- Note that the pointInRect() function and the Button class in general
   -- assume a CORNER alignment but the DirectionalPad uses CENTER (because
   -- it is easier with sprites) so we need to translate between the two.

   local x = self.x - self.width/2
   local y = self.y - self.height/2

   if self.visible and pointInRect(touch.x, touch.y, x, y, self.width, self.height) then
       self.tapped = true
       self.upButton:touched(touch)
       self.downButton:touched(touch)
       self.leftButton:touched(touch)
       self.rightButton:touched(touch)
   end

end




2 comments:

  1. Looks nice, but I can't download the button class - the link leads to the Bullet class instead. Can you fix this, please?

    ReplyDelete
    Replies
    1. Cameron - you are correct, the link is now fixed. And as a bonus I updated the button class with the latest Vega go faster modifications. This should at least double the frame rate you get using this class.

      Thanks for taking the time to point it out.

      Delete