Thursday, July 19, 2012

Interlude 9 - Control Object Movement with Buttons (Updated 6/4/16)



Interlude 9.1 Introduction


One of the most useful design patterns in game design is being able to move an object on the screen using buttons (up, down, left and right).


Interlude 9.2 Solution 1 - One Tap per Move


Our first solution will move an object on the screen by a defined amount (shipSpeed) each time a button is tapped. We have defined the shipSpeed variable as a parameter so that you can tweak the distance each tap moves the ship. Once again we have used @Vega's Mesh Button class for the four directional buttons. Note that this code only works properly with a landscape orientation.

Let's have a look at the main class first.

--# Main

-- Use this function to perform your initial setup

function setup()

   -- This block of code is optional. We just include it in 
   -- every project so that we have a method of version control.

   version = 1.0

   saveProjectInfo("Description", "Move Object Demonstration")
   saveProjectInfo("Author", "Reefwing Software")
   saveProjectInfo("Date", "11th July 2012")
   saveProjectInfo("Version", version)

   print("MoveShip v"..version.."\n")

   -- Initialise the co-ordinates of your "Ship"
   -- These will be updated by the action methods 
   -- associated with each directional button.

   shipPosition = vec2(WIDTH/2, HEIGHT/2)

   -- This slider parameter will control the ship "speed"
   -- The larger the number the further the ship will 
   -- move with each button tap.
   --
   -- The format of this function is: 
   -- parameter("name", min value, max value, init value)

   parameter("shipSpeed", 1, 10, 4)

   -- Define the four Buttons used to move the ship
   -- They wont be visible until you draw() them.
   -- Note that 50 pixels is the minimum height for the default
   -- button font size.

   local mButtonSize = vec2(100, 50)
   local mLocX = WIDTH - 250
   local mLocY = 100

   leftButton = Button("Left", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   leftButton.action = function() leftButtonTapped() end

   mLocX = mLocX + 150
   rightButton = Button("Right", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   rightButton.action = function() rightButtonTapped() end

   mLocX = mLocX - 75
   mLocY = mLocY + 60
   upButton = Button("Up", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   upButton.action = function() upButtonTapped() end

   mLocY = mLocY - 120
   downButton = Button("Down", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   downButton.action = function() downButtonTapped() end

   -- Assign the colours that you want to use in your game.

   blackColour = color(0,0,0)

end

-- And now for the draw() function which is quite simple:
--
-- This function gets called once every frame
-- Codea will attempt to call draw() 60 times per second, it
-- can be much less than this if you have a lot going on in your code.

function draw()

   -- This sets a black background color 

   background(blackColour)

   -- Do your drawing here
   -- Draw your ship at the current co-ordinates stored in shipPosition

   sprite("Tyrian Remastered:Boss D", shipPosition.x, shipPosition.y)

   -- Draw the four directional buttons

   leftButton:draw()
   rightButton:draw()
   upButton:draw()
   downButton:draw()

end

-- Most of the action happens in the button call back functions...
--
-- Button action methods

function leftButtonTapped()

   -- Update ship position as long as the ship isnt off the screen.
   -- math.max() returns the maximum value of the arguments, so
   -- if shipPosition < 0 it will set it to 0.

   shipPosition.x = math.max(shipPosition.x - shipSpeed, 0)

end

function rightButtonTapped()

   -- Update ship position as long as the ship isnt off the screen.
   -- math.min() returns the minimum value of the arguments, so
   -- if shipPosition > WIDTH it will set it to WIDTH.

   shipPosition.x = math.min(shipPosition.x + shipSpeed, WIDTH)

end

function upButtonTapped()
   shipPosition.y = math.min(shipPosition.y + shipSpeed, HEIGHT)
end

function downButtonTapped()
   shipPosition.y = math.max(shipPosition.y - shipSpeed, 0)
end

-- Handle screen touches

-- Note that you need to pass any touches through
-- to your button touch handlers.

function touched(touch)
   leftButton:touched(touch)
   rightButton:touched(touch)
   upButton:touched(touch)
   downButton:touched(touch)
end


Interlude 9.3 Solution 2 - Tap and Hold Continuous Move


In some cases you want your object to move for as long as you hold down the directional button. This turns out to be an easy change due to the functionality built into the Button class. We can detect whether a user is holding down a button by querying its state and seeing if it is "pressing". In this instance you don't need the button action methods (leftButtonTapped, rightButtonTapped, etc) so you can delete them if you aren't using them for something else.

The new draw() function is shown below. While a directional button is being held down its position will be updated by shipSpeed once per frame (roughly 60 times per second).

You can download the entire moveShipCode here.

function draw()

   -- This sets a black background color 

   background(blackColour)

   -- Do your drawing here
   -- Draw your ship at the current co-ordinates stored in shipPosition

   if leftButton.state == "pressing" then
       shipPosition.x = math.max(shipPosition.x - shipSpeed, 0)
   elseif rightButton.state == "pressing" then
       shipPosition.x = math.min(shipPosition.x + shipSpeed, WIDTH)
   elseif upButton.state == "pressing" then
       shipPosition.y = math.min(shipPosition.y + shipSpeed, HEIGHT)
   elseif downButton.state == "pressing" then
       shipPosition.y = math.max(shipPosition.y - shipSpeed, 0)
   end

   sprite("Tyrian Remastered:Boss D", shipPosition.x, shipPosition.y)

   -- Draw the four directional buttons

   leftButton:draw()
   rightButton:draw()
   upButton:draw()
   downButton:draw()

end

Interlude 9.4 Solution 3 - Adding Flames and Direction




This final version adds sprites to represent the ship engine flames (as shown above) and will point the ship in the right direction using rotation which is based on the directional buttons. You can download the complete program including the Button class.

--# Main
-- Use this function to perform your initial setup

function setup()

  -- This block of code is optional. We just include it in 
  -- every project so that we have a method of version control.
  --
  -- Version 3.0 adds rocket flames and points the ship in the
  -- direction of movement.

  version = 3.0

  saveProjectInfo("Description", "Move Object Demonstration")
  saveProjectInfo("Author", "Reefwing Software")
  saveProjectInfo("Date", "11th July 2012")
  saveProjectInfo("Version", version)

  print("MoveShip v"..version.."\n")

  -- Initialise the co-ordinates of your "Ship"
  -- These will be updated in the draw() function to allow for 
  -- continuous pressing of the directional buttons.

  shipPosition = vec2(WIDTH/2, HEIGHT/2)

  -- The ship sprite used faces down. Initially we want it facing up so 
  -- we need to rotate the sprite 180 degrees when we draw() it. For those new to rotation,
  -- note that there are 360 degrees in a circle, 180 degrees is half a circle.
  -- We will then use this variable to rotate the ship so that it is
  -- facing in the right direction when the up, down, left and right
  -- buttons are tapped.

  rotationDegrees = 180

  -- This slider parameter will control the ship "speed"
  -- The larger the number the further the ship will 
  -- move with each button tap.
  --
  -- The format of this function is: 
  -- parameter("name", min value, max value, init value)

  parameter("shipSpeed", 1, 10, 4)

  -- Define the four Buttons used to move the ship
  -- They wont be visible until you draw() them.
  -- Note that 50 pixels is the minimum height for the default
  -- button font size.

  local mButtonSize = vec2(100, 50)
  local mLocX = WIDTH - 250
  local mLocY = 100

  leftButton = Button("Left", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)

  mLocX = mLocX + 150
  rightButton = Button("Right", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)

  mLocX = mLocX - 75
  mLocY = mLocY + 60
  upButton = Button("Up", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)

  mLocY = mLocY - 120
  downButton = Button("Down", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)

  -- Assign the colours that you want to use in your game.

  blackColour = color(0,0,0)

end

-- This function gets called once every frame
-- Codea will attempt to call draw() 60 times per second, it
-- can be much less than this if you have a lot going on in your code.

function draw()

  -- This sets a black background color 

  background(blackColour)

  -- Do your drawing here
  -- Draw your ship at the current co-ordinates stored in shipPosition
  --
  -- pushMatrix() saves any transformations (rotate, translate or scale) that have been made.
  -- popMatrix() returns to these saved transformations.
  -- If you are going to perform transformations it is a good idea to encapsulate your code
  -- with these so that you don't have unexpected effects elsewhere.

  pushMatrix()

  local buttonDown = false

  if leftButton.state == "pressing" then
      shipPosition.x = math.max(shipPosition.x - shipSpeed, 0)
      buttonDown = true
      rotationDegrees = 270
  elseif rightButton.state == "pressing" then
      shipPosition.x = math.min(shipPosition.x + shipSpeed, WIDTH)
      buttonDown = true
      rotationDegrees = 90
  elseif upButton.state == "pressing" then
      shipPosition.y = math.min(shipPosition.y + shipSpeed, HEIGHT)
      buttonDown = true
      rotationDegrees = 180
  elseif downButton.state == "pressing" then
      shipPosition.y = math.max(shipPosition.y - shipSpeed, 0)
      buttonDown = true
      rotationDegrees = 0
  end

  translate(shipPosition.x, shipPosition.y)
  rotate(rotationDegrees)
  sprite("Tyrian Remastered:Boss D", 0, 0)
  if buttonDown then
      -- draw the engine flames
      sprite("Tyrian Remastered:Flame 1", 17, 100)
      sprite("Tyrian Remastered:Flame 1", -19, 100)
  end

  popMatrix()

  -- Draw the four directional buttons

  leftButton:draw()
  rightButton:draw()
  upButton:draw()
  downButton:draw()

end

-- Handle screen touches
-- Note that you need to pass any touches through
-- to your button touch handlers.

function touched(touch)
  leftButton:touched(touch)
  rightButton:touched(touch)
  upButton:touched(touch)
  downButton:touched(touch)
end