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 pushingisPlayerMoving
: 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 happenmovingSteps
: This indicates the number of steps it will take the movement method to move the player one unittRow
: This is the row that the player will be attempting to move totCol
: This is the column that the player will be attempting to move toThe 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:
switch
statement, it sets tRow
and tCol
to the correct values to represent the forwards direction.isPlayerMoving
Boolean to true
and set the movingSteps
variable to 0
.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
.isPlayerRotating
Boolean to true
and set the rotationSpeed
variable to -5
.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 frameisPlayerMoving
to true
isPlayerRotating
to true
isPlayerMoving
is true
, then call the MovePlayer
method to move the player modelIfisPlayerRotating
is true
, then call the RotatePlayer
method to rotate the playerOnce 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:
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
.movingCrate
.transform
by 0.1
of a unit in the same direction as the playermovingSteps
variable, this is our step counter and we will stop executing this method when this has completed ten steps.movingSteps
, if this value is 10
then set the array entries and stop this method being called again.isPlayerMoving
to false
, this is what will stop the method being called in Update
again until a key press or Ouya controller press occurs.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.movingCrate
.movingCrate
to an empty string in case we don't move it next movepCol
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:
transform.Rotate
of the Vector3
rotation type by the rotationSpeed
variable defined earlier around the y axis%
operator is called moduloisPlayerRotating
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.
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.
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:
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:
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:
BuildLevel
method, we need to trigger the idle animationFirst 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.
3.143.203.96