Making the character move

Our game is shaping up nicely, we have the level-building code complete and the camera will, in theory, follow the player around when they move. Let's put that to the test.

As in the previous chapter, we'll get all the input working with the keyboard and then implement the Ouya input.

We are going to make it so the person playing the game can turn left, turn right, and move forward. This will always happen one tile at a time.

Edit your Sokoban script file and add the following code below the int amountOfCrates = 0; line:

GameObject theCrate;
bool isPlayerMoving;
bool isPlayerRotating;
int rotationSpeed;
int movingSteps;
int tRow;
int tCol;

The items these variables will represent are:

  • theCrate: This is a reference to the crate game object that the player is currently pushing
  • isPlayerMoving: This is a Boolean that will be set to true if the player is moving, else it will be false
  • isPlayerRotating: This is a Boolean that will be set to true if the player is moving, else it will be false
  • rotationSpeed: This indicates how fast the player turn will happen
  • movingSteps: This indicates the number of steps it will take the movement method to move the player one unit
  • tRow: This is the row that the player will be attempting to move to
  • tCol: This is the column that the player will be attempting to move to

The method we're going to add next is going to work out if we can move to the tile when we try to. As we're storing the players X and Y tile position and we know their rotation, we can check the element in the array that represents forwards to the player. The code for this is as follows:

// Check if the player is attempting to move
void CheckIfPlayerIsAttempingToMove () {
  
  // Has the player tried to move forwards?
  if (Input.GetKeyDown (KeyCode.UpArrow)) {
    
    // What direction is the player currently facing?
    // Depending on the angle we get a different row and 
    // column in the array for where we are going to move
    switch((int)Mathf.Round(thePlayer.transform.eulerAngles.y)) {
      case 0 :
      tRow = 0;
      tCol = 1;
      break;
      case 90 :
      tRow = 1;
      tCol = 0;
      break;
      case 180 :
      tRow = 0;
      tCol = -1;
      break;
      case 270 :
      tRow = -1;
      tCol = 0;
      break;
    }
    
    // Is the target tile a floor, 0, or a goal, 2?
    if (levels[0][pRow + tRow][pCol+tCol] == 0 ||levels[0][pRow + tRow][pCol + tCol] == 2)
    {
      
      isPlayerMoving = true;
      movingSteps = 0;
      
    } else if (levels[0][pRow + tRow][pCol + tCol] == 3 || levels[0][pRow + tRow][pCol + tCol] == 5) 
    {
      // Is the target tile a crate on a floor tile, 3, or a
      // crate on a goal tile, 5?
      
      // At this point we know the player is pushing a 
      // crate so we need to check if the crate can be 
      // pushed to the target location
      
      // Is the target tile a floor, 0, or a goal, 2?
      if (levels[0][pRow + 2 * tRow][pCol + 2 * tCol] == 0 || levels[0][pRow + 2 * tRow][pCol + 2 * tCol] == 2)
      {
        
        // Store a reference to the moving crate's name
        movingCrate = "crate_" +
                      (pRow + tRow) + 
                      "_" + 
                      (pCol + tCol);
        
        isPlayerMoving = true;
        movingSteps = 0;
      }
    }
  } else {
    
    // Is the player turning left or right?
    // The rotationSpeed variable can be tweaked but MUST go 
    // exactly in to 90
    // Example: 90 / 5 = 18 - OK
    // Example: 90 / 6 = 15 - OK
    // Example: 90 / 7 = 12.85 - FAIL
    if (Input.GetKeyDown (KeyCode.RightArrow)) {
      isPlayerRotating = true;
      rotationSpeed = 5;
    }
    else if (Input.GetKeyDown (KeyCode.LeftArrow)) {
      isPlayerRotating = true;
      rotationSpeed = -5;
    }
  }
}

There are comments in the code so you can see, line by line, what each part does, but let's go over the method now, from top to bottom and explain what it does generally:

  • Check if the Up arrow key has been pressed. If it has:
    • Check the direction the player is facing and establish what forwards means in terms of which array element to access. Depending on the results of the switch statement, it sets tRow and tCol to the correct values to represent the forwards direction.
    • Check if there is an empty floor or goal tile for the player's target row and column. If there is then we set the isPlayerMoving Boolean to true and set the movingSteps variable to 0.
    • Check if the tile in front is a crate.
    • If true then check if there is an empty floor or goal tile for the crate's target row and column. If there is, then we set the isPlayerMoving Boolean to true, set the movingSteps variable to 0 and store a reference crate's name in movingCrate.
  • If it hasn't:
    • Check if the Left arrow key has been pressed. If it has, then we set the isPlayerRotating Boolean to true and set the rotationSpeed variable to -5.
    • Check if the Right arrow key has been pressed. If it has, then we set the isPlayerRotating Boolean to true and set the rotationSpeed variable to 5.

Now we have written the method, let's add it in the Update method:

void Update () {
  
  // Is the player is stationary at the moment?
  if (!isPlayerMoving && !isPlayerRotating) {
    CheckIfPlayerIsAttempingToMove();
  }
}

As you can see, we check if the player is not currently moving or rotating, and if both of those checks pass, we check if the player is attempting to move. While we're working in the Update method lets add the next piece of code, add it just below the end of the if statement:

if (isPlayerMoving) {
  MovePlayer();
}

// Do we need to rotate the character
// This is an interative process and will occur every frame until 
// we have rotated 90 degrees
if (isPlayerRotating) {
  RotatePlayer();
}

Let's look at what is happening in the code now:

  • Update is called once per frame
  • If the player is not moving, check if they are attempting to
  • If an attempt to move forwards is successful, set isPlayerMoving to true
  • If the player has rotated set isPlayerRotating to true
  • If isPlayerMoving is true, then call the MovePlayer method to move the player model
  • IfisPlayerRotating is true, then call the RotatePlayer method to rotate the player

Once the isPlayerMoving or isPlayerRotating Booleans are set to true they will not be set to false until the MovePlayer or RotatePlayer methods have completed. After we add these next two methods we should be able to test our game and play through the level. Add the following two methods:

void MovePlayer () {
  
  // Move the player forward. We move in 10 steps and need to 
  // move 1 unit for 1 complete move
  // so we move 0.1 for each step taken. 0.1 * 10 = 1
  thePlayer.transform.Translate(Vector3.forward * 0.1f);
  
  // Are we moving a crate?
  if (movingCrate != "") {
    
    // Get a reference to the crate
    theCrate = GameObject.Find(movingCrate);
    
    // What direction is the player currently facing?
    switch((int)Mathf.Round(thePlayer.transform.eulerAngles.y)) {
      case 0 :
      theCrate.transform.Translate(Vector3.forward * 0.1f);
      break;
      case 90 :
      theCrate.transform.Translate(Vector3.right * 0.1f);
      break;
      case 180 :
      theCrate.transform.Translate(Vector3.forward * -0.1f);
      break;
      case 270 :
      theCrate.transform.Translate(Vector3.right * -0.1f);
      break;
    }
  }
  movingSteps++;
  
  // Have we finished the move?
  if (movingSteps == 10) {
    
    isPlayerMoving = false;
    
    // 4 is the tile reference for the player so add 4 to 
    // the tile we have moved to
    // Example: A goal, 2, would become 6 as we have added 4 
    //for the player
    // Add to the tile we have moved to
    levels[0][pRow + tRow][pCol + tCol] += 4;
    
    // Remove player from the tile we have moved from
    levels[0][pRow][pCol] -= 4;
    
    // Are we moving a crate?
    if (movingCrate != "") {
      
      // 3 is the tile reference for a crate so add 3 to 
      //the tile the crate moved to
      // Example: A goal, 2, would become 5 as we have added 
      //3 for the crate
      // Add to the tile we have moved to
      levels[0][pRow + 2 * tRow][pCol + 2 * tCol] += 3;
      
      // Remove from the tile we have moved from
      levels[0][pRow + tRow][pCol + tCol] -= 3;
      
      // Adjust the name of the crate and move the
      // reference to it as we are no longer pushing it
      theCrate.name = "crate_" + 
                      (pRow + 2 * tRow) + 
                      "_" + 
                      (pCol + 2 * tCol);
      movingCrate = "";
    }
    
    // Adjust the stored player location on the map and in 
    // the array
    pRow += tRow;
    pCol += tCol;
  }
}

void RotatePlayer () {
  
  // Adjust the transform
  thePlayer.transform.Rotate(0, rotationSpeed, 0);
  
  // Have we rotated a full 90 degrees?
  if (Mathf.Round(thePlayer.transform.eulerAngles.y) % 90 == 0)
  {
    isPlayerRotating = false;
  }
}

Again, there are comments in the code so that you can see what each part does and gives a general break down of what the code path for each method is. For the MovePlayer method:

  • Modify player's transform by 0.1 of a unit forwards (the direction the player is facing). We use 0.1 as this method will run ten times before setting isPlayerMoving to true.
  • Check if we have a string reference in movingCrate.
    • If we do then find the crate
    • Modify crate's transform by 0.1 of a unit in the same direction as the player
  • Increment the movingSteps variable, this is our step counter and we will stop executing this method when this has completed ten steps.
  • Check the value of movingSteps, if this value is 10 then set the array entries and stop this method being called again.
  • Set the value of isPlayerMoving to false, this is what will stop the method being called in Update again until a key press or Ouya controller press occurs.
  • Add 4 to the value of the array entry that the player has moved to, this means we can still establish what the original tile is without the player on it.
  • Remove four from the array entry we have moved from.
  • Check if we have a string reference in movingCrate.
    • If we do then add three to the value of the array entry that the crate has moved to
    • Remove three from the array entry the crate has moved from
    • Adjust the crates name to reflect its new position in the array
    • Set movingCrate to an empty string in case we don't move it next move
  • Modify the pCol and pRow variables by the values stored in tCol and tRow, this way we always have the current index of the player.

The RotatePlayer method is much simpler:

  • Modify player's transform.Rotate of the Vector3 rotation type by the rotationSpeed variable defined earlier around the y axis
  • Check if we have rotated a full 90 degrees by checking that dividing the current rotation by ninety leaves no remainder. In case you are not sure, the % operator is called modulo
  • If we have rotated a full 90 degrees then set isPlayerRotating to false

Close the script, go back to Unity, and press the play icon at the top middle of the Unity screen. Try using the Left arrow and Right arrow keys to turn left and right and the Up arrow key move forwards. The camera should pan smoothly around the player as you turn.

Ouya controller support

As we have already added the OuyaInput.unitypackage package in our previous chapter, we can go right ahead and implement the controller support. The Ouya team have made it so simple to integrate the controller once all the necessary files are imported, just add the following lines inside our public class definition in our Sokoban script:

public bool continuousScan = true;
public OuyaPlayer player = OuyaPlayer.P01;

Also as before, add the following lines to the Awake method:

OuyaInput.SetContinuousScanning(continuousScan);
OuyaInput.UpdateControllers();

At the start of the Update method add:

OuyaInput.UpdateControllers();

Now edit the CheckIfPlayerIsAttempingToMove method and edit the following three lines:

if (Input.GetKeyDown (KeyCode.UpArrow)) {
if (Input.GetKeyDown (KeyCode.LeftArrow)) {
if (Input.GetKeyDown (KeyCode.RightArrow)) {

They need to become:

if (Input.GetKeyDown (KeyCode.UpArrow) || OuyaInput.GetButtonDown(OuyaButton.DU, player)) {

if (Input.GetKeyDown (KeyCode.LeftArrow) || OuyaInput.GetButtonDown(OuyaButton.DL, player)) {

if (Input.GetKeyDown (KeyCode.RightArrow) || OuyaInput.GetButtonDown(OuyaButton.DR, player)) {

Press Build, Run and Compile Application on the Ouya Panel and go through the steps to get to the game, try the directional pad on the controller and you'll see that the game responds just as it did when you were pressing the arrow keys on your keyboard.

Animating the character

While there is already some basic animation in the character in terms of moving them smoothly between tiles, wouldn't it be nicer to have an actual character? It would! Unity Asset Store, TurboSquid, and many others offer models for free or a very low cost. For our game we're going to use an asset that Unity provides in one of their demos. Unity very kindly allows you to include their assets in your own projects and still offer them for sale.

The character in question is from a demo called Astro Dude and he looks like this:

Animating the character

Import the playerModel.unitypackage package from the source files for this chapter. Once all the files are imported you'll have a new Player folder inside your Prefabs folder, click on the playerAnimated prefab inside the PlayerModel prefab and look at the Inspector panel, you should see two animations listed:

Animating the character

Make a note of the two animation names as we'll be using them later. The names are idle and runforward.

Drag the new PlayerModel prefab in to the Player transform slot on the script that is attached to your Sokoban game object.

This model's sizes are a little different so we're going to need to adjust the instantiation code in the BuildLevel method as we're also going to have to trigger the built in animations already in the model when they are required. Let's start with the model position, it's taller than the one unit that our cube is so to adjust the Y position when we instantiate it change the following line from:

thePlayer = Instantiate(player, new Vector3 (i,0, j), Quaternion.identity) as Transform;

To:

thePlayer = Instantiate(player, new Vector3 (i,1, j), Quaternion.identity) as Transform;

The change is small, we've just changed 0 to 1. There is slightly more code for the animations, but don't worry it's not too much. First we need to declare a variable at the top of our script to store the reference to Animation so add the following line inside our public class definition:

Animation thePlayerAnimation;

If we think about the points we need to trigger our character's animation it's only actually a few places. They are:

  • When the script loads, after the BuildLevel method, we need to trigger the idle animation
  • If the Up arrow key is pressed, we need to trigger the runforward animation
  • If the player has finished moving, we need to trigger the idle animation

First we need to get the reference to the animations so add the following lines to the Awake method:

thePlayerAnimation = GameObject.Find("playerAnimated").animation;
thePlayerAnimation.Play("idle");

Next, just after the GetKeyDown method check inside the CheckIfPlayerIsAttemptingToMove method add:

thePlayerAnimation.CrossFade ("runforward");

Finally, add to following after the if (movingSteps == 10) { line in the MovePlayer method:

thePlayerAnimation.CrossFade ("idle");

Test your game and now and behold! Proper animation and a fancy character in your game.

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

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