Friday, 18 December 2015

Moon Lander: Movement Tutorial

This tutorial is a basic game of physics. The player controls a rocket ship's rotation and thrust. With limited fuel and the effects of gravity, the player must try to land the rocket safely. Hit the ground too hard, and your rocket explodes.

In this tutorial, if code is crossed out like this...

if code == "CROSSED OUT" then
    print "Add this line of code"

...it means this line of code will already be there. You only need to add the code in bold. In this part of the tutorial we will be going into the movement of the rocket and the gravity effect. Let start by grabbing a copy of my Corona Template.

The first thing we need of course is our rocket ship. I personally found this little ship on google. Find yourself a little rocket like mine and place it in the sprites folder. Now we need to get that rocket on the screen. To do this we are going to make ourselves a function to create a new player. Add the following code just before the scene:enterScene function

function newPlayer(x, y)
    local player = display.newGroup()
    player.x                 = x
    player.y                 = y
    player.image             = display.newImage("sprites/rocket.png")
    player:insert(player.image) --Inserts the image into the display group
    return player

end

How this works
  1. Create a new display group within the function
  2. Move that display group to the x, y coordinates given to the function
  3. Create a new image from the file we have given.
  4. Place that image inside the display group
  5. Returns the display group
The reason I created a display group first is I can easily add more images or variables to the object created. Later we will need to add some variables to this, but lets first get this rocket on the screen first. To create a rocket on your screen all you now need to do is call the function you just created. Place this next line of code inside the scene:enterScene function, just after the mainScene = self.view line

mainScene = self.view
--Create a player
player = newPlayer(DISPLAY_W_CENTER, DISPLAY_H_CENTER)
mainScene:insert(player) 

So as you can see we are creating a new player using the constants pre-made in the template. These constants are for the center of the screen. We are then storing the player in a variable called player.
This should create our rocket on the screen.

Image of Corona Simulator With Rockey On Screen

The next step is to work on the rotation of the rocket. The first thing we need for that is a couple of buttons to press. One for rotation left and one for rotating right. I found these great images on google. Find some images of your own and then to add them to the screen add the follow code after the main function



----------------------------------------------------------------
-- MAIN LOOP
----------------------------------------------------------------

local function main( event )

end

--Buttons
btnRotateLeft = display.newImage("sprites/object_rotate_left.png")
        btnRotateLeft.y = 640


btnRotateRight = display.newImage("sprites/object_rotate_right.png")
        btnRotateRight.x = btnRotateLeft.width * 1.5
        btnRotateRight.y = 640

mainScene:insert(btnRotateRight)
mainScene:insert(btnRotateLeft)



Okay now if we touch these images, nothing happens. We need to add event listeners to them. Add the following code under the buttons.

btnRotateLeft:addEventListener("touch", player.turn)

and

btnRotateRight:addEventListener("touch", player.turn)

What these lines of code are doing is telling the program to look for a touch event on the button. If a touch event is found, it will run the player.turn function. However if you run this now, you will get an error because we do not have a function turn in the player object. So let's create this function now. In the newPlayer function add the following code.

player.turn = function(event)
                 if event.phase == "began" then
                                       
                 end
              end


We now have our function, however both buttons call this function so we need some way to check which button was pressed. To do this we add the following code just below the buttons.

btnRotateLeft.dir = "LEFT"

and

btnRotateRight.dir = "RIGHT"

So your button code should look similar to this.

btnRotateLeft = display.newImage("sprites/object_rotate_left.png")
        btnRotateLeft.y = 640
        btnRotateLeft.dir = "LEFT"
        btnRotateLeft:addEventListener("touch", player.turn)
 btnRotateRight = display.newImage("sprites/object_rotate_right.png")
        btnRotateRight.x = btnRotateLeft.width * 1.5
        btnRotateRight.y = 640
        btnRotateRight.dir = "RIGHT"
        btnRotateRight:addEventListener("touch", player.turn)

mainScene:insert(btnRotateRight)
mainScene:insert(btnRotateLeft)


Now all we need to do is check in the player.turn function which button was pressed. Inside the event variable there is heaps of information about the event. As you can see we are using phase so the function only runs when the button is first pressed and not again when its released. Another piece of information is target. The target is the button pressed in our case. Each of our buttons have a dir variable so now we can check which button was pressed using event.target.dir. Add the following code straight after the event.phase check.

if event.phase == "began" then
    if event.target.dir == "LEFT" then
                               
    else
                                       
    end


Now all we need to do is rotation the ship left or right. Add the following code.

if event.phase == "began" then
    if event.target.dir == "LEFT" then

        player.rotation = player.rotation - 2                       
    else
       
player.rotation = player.rotation + 2                            
    end


Now, if we run our code and touch the buttons, you will see the ship is now rotating. However its not quiet centered. Add this code to the player function.

player.image.x         = player.image.x - player.image.width/2
player.image.y         = player.image.y - player.image.height/2


This will move the image so when the ship rotates, it rotates on it's center. Another annoying thing is it's only moving 2 degrees every click. It would be much better if while we were holding the button it would continuously rotate. So lets change our code to implement this.

The first thing we need to do is give the player another variable. Add the following code to the player function.

player.rotating = {false, nil}

This line enables us to do a check to see if the ship should be rotating and by how much. Now in the turn function change

player.rotation = player.rotation - 2

to

player.rotating = {true, -2}

and

player.rotation = player.rotation + 2

to

player.rotating = {true, 2}

Now add and else on the event phase check.

elseif event.phase == "ended" then
    player.rotating        = {false, nil}end

Your full player.turn function should look like this.

player.turn= function(event)
               if event.phase == "began" then
                  local direction = event.target.dir
                  if direction == "LEFT" then
                      player.rotating = {true, -2}
                  else
                      player.rotating = {true, 2}
                  end
               elseif event.phase == "ended" then
                  player.rotating        = {false, nil}
              
end
                            end

The last part of the rotation is to do a check to see if the ship is rotating. This is done in the main loop. Add the following code in the main loop

--Rotate Player
if player.rotating[1] then
    player.rotation = player.rotation + player.rotating[2]
end


Now when we click and hold on our buttons. The ship rotates continuously. We are half way there. Now we need to add the movement. First lets limit the ships rotation so it can not turn upside down. Add the following lines of code after we do the rotation in the main loop.

if player.rotating[1] then
     player.rotation = player.rotation + player.rotating[2]

     if player.rotation < 270 and player.rotation > 180 then player.rotation = 270 end
     if player.rotation > 90 and player.rotation < 180 then player.rotation = 90 end

And finally, so our rotation value does not go into a negative number add these line of code right under the ones you just wrote.

if player.rotation <= -1 then player.rotation = player.rotation + 360 end
if player.rotation >= 360 then player.rotation = player.rotation - 360 end 

With these lines of code, if the rotation drops below 0 we add 360 to the rotation to get a true value. Likewise when it raises higher then or equal to 360 we then remove 360.

Now lets get onto thrust by adding a thrust button. Once again I found a cool button on Google. You can find whatever button you wish to use. This button is way to big to use on the screen but it does give me a chance to show how to scale the size of a button.
It is done by using the scale function.

imageName:scale(x,y)

I'll be using this function when I scale this image in code. Lets go ahead and add this button to our screen. Add the following code where you made the rotating buttons.



btnBoosters = display.newImage("sprites/Boosters.png")
        btnBoosters:scale(.5, .5)
        btnBoosters.y     = 640
        btnBoosters.x     = 1000
        btnBoosters:addEventListener("touch", player.
fireThruster)

So like before I have added an event listener so when the player touches the image, the function player.startEngine runs. You can also see I have scaled the image to half the x and half the y. Running the code now will cause an error as there is no player.startEngine function. So lets get that one made now. Add the following code inside the newPlayer function.

player.fireThruster = function(event)
                        if event.phase == "began" then
                                   
                        elseif event.phase == "ended" then
                                   
                        end
                     end


Now we can run our code and see our button. Here is what is should all look like now.


Okay so lets start our engines. In the fireThrusters function all I will be doing is adding some value to a new variable we are about to make. In the newPlayer function add this line of code.

player.thrust = 0

And now in the fireThrusters function add the following code.


player.fireThruster = function(event)
                        if event.phase == "began" then
                            player.thrust = 0.5    
                        elseif event.phase == "ended" then
                            player.thrust = 0     
                        end
                     end


Now when we touch the button, our new thrust variable will get a value of 0.5 and when we release our touch it will drop back to 0. Now to add some movement. Add the following variable to newPlayer.

player.velocity = {x=0, y=0}

This will be used to determine how far to move our ship on the x and y axis. We will work on this section one direction at a time. Start by adding a move function to the newPlayer function.

player.move = function()

              end

And in the main loop, just after we make our ship rotate, call this new function.

local function main( event )
       
         --Rotate Player
    if player.rotating[1] then      

                ....
                ....
    end
     --Move the Player based on thrust
     player.move()



Okay now we need to make the ship move. There are a few sections to this. Lets start by making the ship move when facing straight up. In the move function add the following code.

local angle = player.rotation
if angle == 0 then --Ship Facing Up
    player.velocity.y = player.velocity.y - player.thrust

end

When the ship is facing up, we only need to move on the y axis. To do this we simply take the thrust from the y axis velocity. This will make the ship move up the screen when the velocity is applied. To make the ship move add the follow code after what you just entered.

player.y = player.y + player.velocity.y
player.x = player.x + player.velocity.x


These two lines will move the ship based on the velocity. If you run your code now your ship should fly upwards when you hit thrust. However, if you turn the ship, the thrust button will now work. We will add these a little later. Now lets work on when the ship is laying on its left side. In the angle check add the following elseif statement.

if angle == 0 then -- Straight line
      player.velocity.y = player.velocity.y - player.thrust

elseif angle == 270 then-- Straight line
      player.velocity.x = player.velocity.x - player.thrust

 end


This is almost identical to when facing straight up except we need to take the thrust from the x velocity making the ship move left. Save your code and try it now. You should be able to turn your ship left and it will stop when it is perfectly laid down and thrust. Now lets add this movement when the ship is laid on the right side. Add the follow code, also to the angle check.

if angle == 0 then -- Straight line
      player.velocity.y = player.velocity.y - player.thrust

elseif angle == 270 then-- Straight line
      player.velocity.x = player.velocity.x - player.thrust

elseif angle == 90 then-- Straight line
      player.velocity.x = player.velocity.x + player.thrust

 end


Now if you test your code the ship should fly when laid on its right side. Okay now we move into the more tricky movements. First lets do when the ship is leaning to the left. First lets add that elseif statement in the angel check. This check need to see if the angle is greater than 270.


if angle == 0 then -- Straight line
      player.velocity.y = player.velocity.y - player.thrust

elseif angle == 270 then-- Straight line
      player.velocity.x = player.velocity.x - player.thrust

elseif angle == 90 then-- Straight line
      player.velocity.x = player.velocity.x + player.thrust

elseif angle > 270 then
 
 end


For this one we need to work out what percentage of that 90 degree section the ship is leaning. Lets for example say the rotation of the ship is 296 degrees.
We need to first find out what the angle is just in that quarter of the angle. So if we take away 270 from the angle we will have the specific angle we need. In our example our angle to work with would be.

296 - 270 = 26 degrees
  
Now we need to work out what percentage of the 90 degrees the ship has turned through.

26 / 90 =  0.29

The angle we measure has come from the x axis so this number is worked out to find how much thrust to put on the y axis. To get the x axis thrust we simply take this number from 1.

1 - 0.29 = 0.71

 To implement this is code add the following lines to the new elseif statement.

elseif angle > 270 then
    angle = angle - 270
    local yPercent = angle / 90
    local xPercent = 1-yPercent

 end 



Now to place the thrust on the x and y velocity we simply add this code.

elseif angle > 270 then
    angle = angle - 270
    local yPercent = angle / 90
    local xPercent = 1-yPercent

    player.velocity.x = player.velocity.x - (player.thrust * xPercent)
    player.velocity.y = player.velocity.y - (player.thrust * yPercent)

 end 


Save and run your code and you will see you can now fly leaning left. Now lets follow the same procedure and add the ship flying to the right. The difference is because the angle is now measured from the y axis, the first part measures the x axis thrust. Also because the angle is already under 90 we do not have to adjust it. Add the following else statement to your angle checks.

else
    local xPercent = angle / 90
    local yPercent = 1-yPercent

    player.velocity.x = player.velocity.x - (player.thrust * xPercent)
    player.velocity.y = player.velocity.y - (player.thrust * yPercent)
 end



Now your ship should fly anyway. However, we don't have any gravity to bring our ship back down. So lets put that in now. Simply add this line right after the if statements we have just been writing and before we add the velocity to the x and y coordinates.

player.velocity.y = player.velocity.y + 0.1

Now your ship should fly up, left and right while being effected by gravity. Now only one more thing to implement. Lets stop our ship from flying off the screen and zero the velocity when it hits the side. Add the following lines of code after we add the velocity to the x and y coordinates.

--Stop the ship flying off the screen
if player.x < 0 + player.width then
       player.x = 1 + player.width
       player.velocity.x = 0
elseif player.x > DISPLAY_W - player.width then
       player.x = DISPLAY_W - player.width
       player.velocity.x = 0
end
if player.y < 0 + player.width then
       player.y = 1 + player.width
       player.velocity.y = 0
elseif player.y > DISPLAY_H - player.width then
       player.y = DISPLAY_H - player.width
       player.velocity.y = 0
end


Now our ship won't fly off the screen.

Well that's it for now. Keep an eye out for the next tutorial for the moon lander game. Next time we will make the ship crash if we land too hard and maybe add some fuel restrictions.

You can download the code and all the images right here.

No comments:

Post a Comment