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