Creating our player

Having the basis of our world is great, but if we don't have a player, it doesn't matter how nice the level looks. In this section, we will create the actual player that will walk around and move in the world:

  1. Let's first create a Capsule by selecting GameObject | 3D Object | Capsule.
  2. Right now, the capsule is too big to fit our world due to being larger than our blocks. To easily fix this, we will set the Scale of our Capsule to be (0.4, 0.4, and 0.4). Also, set its Position to (1, 2, 0):
    Creating our player
  3. Now, we want our player to use gravity and forces, so we will need to add a Rigidbody component by going to Component | Physics | Rigidbody.

    Note

    The 2D and 3D Physics systems are not interchangeable as in either can be used but they cannot interact with each other. You'll need to choose one or the other when working on a project. We're using 3D right now, so you can have a good idea of the differences between 2D and 3D and what to look out for.

  4. Next, because we are doing a 2D game, we don't want our player to be moving in the Z axis, so under the Rigidbody, open up the Constraints box and check Z in the Freeze Position variable. After this, check each axis for the Freeze Rotation as we do not want our character to change its rotation via Rigidbody (we'll rotate it via code):
    Creating our player
  5. After this, all that's left is to create some custom functionality, which means that you guessed it, another script. Create a new C# Script file named PlayerBehaviour and open it up in your IDE of choice.

    With the PlayerBehaviour script opened, let's first write down each of the issues we need to solve and make them functions. As programmers, it's our job to solve problems, and separating problems into smaller pieces will make it easier to solve each part rather than try to solve the entire thing all at once.

  6. Add in the following code:
    void FixedUpdate()
    {
        // Move the player left and right
        Movement();
    
        // Sets the camera to center on the player's position.
        // Keeping the camera's original depth
        Camera.main.transform.position = new Vector3(transform.position.x, transform.position.y, Camera.main.transform.position.z);
    }
  7. Next, we write the following in the Update function:
    void Update()
    {
    // Have the player jump if they press the jump button
       Jumping();
    }

    Note

    Update() is great and is called every frame, but it's called at random times, leading to more instant but less constant things, such as input. Instead of that, FixedUpdate() is a great function to use for things that need to happen consistently and for things like physics due to its fixed delta time (the Time.deltaTime value we've been using previously changes depending on the frame rate). However, in a platformer, the player needs to feel a jump instantly, so that's why I put the Jumping function inside of Update.

    So at this point, we have broken the player's behavior into two sections—their movement and their jumping.

  8. Next, we're going to need to declare some variables for us to use:
        // A reference to our player's rigidbody component
        private Rigidbody rigidBody;
        // Force to apply when player jumps
        public Vector2 jumpForce = new Vector2(0, 450);
    
        // How fast we'll let the player move in the x axis
        public float maxSpeed = 3.0f;
    
        // A modifier to the force applied
        public float speed = 50.0f;
    
        // The force to apply that we will get for the player's 
        // movement
        private float xMove;
    
        // Set to true when the player can jump
        private bool shouldJump;
  9. I've initialized the public data here, but the user can modify the numbers in the Inspector. However, we still need to initialize the private variables in the Start function:
        void Start () 
        {
            rigidBody = GetComponent<Rigidbody>();
            shouldJump = false;
            xMove = 0.0f;
        }
  10. Now that we have the variables, we think that we need to fill in the implementation for the Movement function now:
    void Movement()
    {
        // Get the player's movement (-1 for left, 1 for right, 
        // 0 for none)
        xMove = Input.GetAxis("Horizontal");
    
        if (xMove != 0)
        {
            // Setting player horizontal movement
            float xSpeed = Mathf.Abs(xMove * rigidBody.velocity.x);
    
            if (xSpeed < maxSpeed)
            {
                Vector3 movementForce = new Vector3(1, 0, 0);
                movementForce *= xMove * speed;
                rigidBody.AddForce(movementForce);
            }
    
            // Check speed limit
            if (Mathf.Abs(rigidBody.velocity.x) > maxSpeed)
            {
                Vector2 newVelocity;
    
                newVelocity.x = Mathf.Sign(rigidBody.velocity.x) *
                                maxSpeed;
                newVelocity.y = rigidBody.velocity.y;
    
                rigidBody.velocity = newVelocity;
            }
        }
        else
        {
            // If we're not moving, get slightly slower
            Vector2 newVelocity = rigidBody.velocity;
    
            // Reduce the current speed by 10%
            newVelocity.x *= 0.9f;
            rigidBody.velocity = newVelocity;
        }
    }

In this section of code, we use a different way to get input from the player, the GetAxis function. When called, GetAxis will return a value for the direction that you are moving in a particular axis. In this instance, -1 for going all the way to the left, 0 for stationary, and 1 for all the way to the right. GetAxis can return any number between -1 and 1 as using a game controller you may only slightly move the analog stick. This would allow you to sneak in an area rather than always be running. In addition to the Horizontal Axis, there are a number of others included in Unity by default, but we can also customize or create our own.

Adding Jump functionality

At this point, we can move left and right in the game, but we're unable to jump. Let's fix this now:

  1. Access the Input properties by going to Edit | Project Settings | Input. Once there extend the Jump tab. In the Alt Positive Button put in up:
    Adding Jump functionality
  2. Next, let's implement the Jumping function:
        void Jumping()
        {
            if(Input.GetButtonDown("Jump")) 
            {
                shouldJump = true;
            }
            
            // If the player should jump
            if(shouldJump) 
            {
                rigidBody.AddForce(jumpForce);
                shouldJump = false;
            }
        }

    Now, if we press Spacebar or Up, we will change the shouldJump Boolean value to true. If it's true, then we'll apply the jumpForce to our character.

  3. With this completed, let's save our script and jump back into the Unity Editor. From there, go to the Hierarchy tab and select the Capsule object. Rename it to Player and then attach the newly created behavior to our player if you haven't done so already:
    Adding Jump functionality

Great start! We now have a player in our world, and we're able to move around and jump. However, if you keep playing with it, you'll note some of the issues that this has, namely the fact that you can always jump up as many times as you want and if you hold a direction key hitting a wall, you'll stay stuck in the air. This could make for interesting game mechanics, but I'm going to assume that this is not what you're looking for.

Working with Gizmos

Next, before we solve our movement issues, I wanted to show you a tool that you can use as a developer to help you when working on your own projects. Add the following function to your script:

void OnDrawGizmos()
{
    Debug.DrawLine(transform.position, transform.position + rigidBody.velocity, Color.red);
}

OnDrawGizmos is a function inherited by the MonoBehaviour class that will allow us to draw things that will appear in the Scene view. Sure enough, if you play the game, you will not see anything in the Game view, but if you look at the Scene tab, you'll be able to see the velocity that our object is traveling by. To make it easier to see, feel free to hit the 2D button in the top toolbar on the Scene tab to get a side view:

Working with Gizmos

Note

Note that the Game tab needs to be active before Input will register any pressed keys so I have both tabs open. Alternatively, you can add the gizmo to the Game view as well by going to the top right of the Game tab and toggling the Gizmos button.

Smoothing out player movement

In this example here, the red line is showing that I'm jumping up and moving to the left. If you'll look at the Scene view when the player is walking, you'll see little bumps occuring. This isn't something we'd like to see as we expect the collision to flow together. These bumps occur because the moment that we hit the edges of two separate boxes, the collision engine will try to push the player in different directions to prevent the collision from happening. After the collisions occur, the physics engine will try to combine both of those forces into one that causes these hickups. We can fix this by telling Unity to spend some extra time doing the calcuations.

Go into Unity's Physics properties by going to Edit | Project Settings | Physics. Change the Default Contact Offset property to 0.0001:

Smoothing out player movement

The Contact Offset property allows the collision detection system to predicatively enforce the contact constraint even when the objects are slightly separated, we decrease that number as we don't want the collisions to happen.

Restricting Jumping

Now there's the matter of being able to jump anytime we want. What we want to have happen is to have the player not be able to jump unless they're on the ground. This is to prevent the case of being able to jump while falling:

  1. So to do this, we will need to introduce some new variables:
    private bool onGround;
    private float yPrevious;
  2. Just like any private variables, we will need to initialize them in our Start function:
    onGround = false;
    yPrevious = Mathf.Floor(transform.position.y);
  3. Now in our Jumping function, we just need to add the following code in bold:
        void Jumping()
        {
            if(Input.GetButtonDown("Jump")) 
            {
                shouldJump = true;
            }
            
            // If the player should jump
            if(shouldJump && onGround) 
            {
                rigidBody.AddForce(jumpForce);	
                shouldJump = false;
            }
        }
  4. And in our Update function, we will add in a new function for us to check whether we are grounded:
    // Update is called once per frame
    void Update () 
    {
        // Check if we are on the ground
        CheckGrounded();
        
        // Have the player jump if they press the jump button
        Jumping();
    }
  5. Now we just need to add in the code for CheckGrounded. This isn't exactly a simple issue to solve without math, so we will actually need to use some linear algebra to solve the issue for us as follows:
    void CheckGrounded()
    {
        // Check if the player is hitting something from 
        // the center of the object (origin) to slightly below
        // the bottom of it (distance)
        float distance = (GetComponent<CapsuleCollider>().height / 2 * this.transform.localScale.y) + .01f; Vector3 floorDirection = transform.TransformDirection(-Vector3.up);
        Vector3 origin = transform.position;
    
        if (!onGround)
        {
            // Check if there is something directly below us
            if (Physics.Raycast(origin, floorDirection, distance))
            {
                onGround = true;
            }
        }
        // If we are currently grounded, are we falling down or 
        // jumping?
        else if ((Mathf.Floor(transform.position.y) != yPrevious))
        {
            onGround = false;
        }
    
        // Our current position will be our previous next frame
        yPrevious = Mathf.Floor(transform.position.y);
    }

This function uses a Raycast to cast an invisible line (ray) from origin in the direction of the floor for a certain distance, which is just slightly further than our player. If it finds an object colliding with this, it will return true, which will tell us that we are indeed on the ground.

Preventing the player getting stuck

In the game, we can leave the ground in two ways—by jumping or by falling down a platform. Either way, we will be changing our y position; if that's the case, we are no longer on the ground, so onGround will be set to false. The Floor function will remove the decimal from a number to allow for some leeway for a floating point error.

  1. Now our only issue resides in the fact that the player sticks to walls if they press into it. To solve this, we will just simply not allow the player to move into a wall by not adding a force if we're right next to a wall. Add the following bolded code to this section of code in the Movement function:
    // Movement()
    // if xMove != 0...
    if (xSpeed < maxSpeed) 
    {
    Vector3 movementForce = new Vector3(1,0,0);
    movementForce *= xMove * speed;
    
    RaycastHit hit;
    if(!rigidBody.SweepTest(movementForce, out hit, 0.05f))
    {
    rigidBody.AddForce(movementForce);
    }
    }
    // Etc.

    The SweepTest function will check in the direction the rigid body is traveling, and if it sees something within a certain direction, it will fill hit with the object that it touched and return true. We want to stop the player from being able to move into the wall, so we will not add force if that's the case.

  2. Now, this works for the most part except for when we are already along the wall and jump up and other fringe cases. To fix these issues when we are touching a wall, we will add a variable that will keep track if we are touching a wall:
    private bool collidingWall;
  3. After this, we need to initialize it in Start:
    collidingWall = false;
  4. After this, we will use the 3D collision detection functions to determine if we're touching a wall:
    // If we hit something and we're not grounded, it must be a wall
    // or a ceiling. 
        void OnCollisionStay(Collision collision)
        { 
            if (!onGround)
            { 
                collidingWall = true;  
            }
        }
        
        void OnCollisionExit(Collision collision)
        {
            collidingWall = false;
        }

    You'll note that the functions look quite similar to the 2D functions aside from not having the word 2D.

  5. Next, inside of your Movement function, add the following bolded lines:
        void Movement()
        {
            //Get the player's movement (-1 for left, 1 for
            //right, 0 for none)
            xMove = Input.GetAxis("Horizontal");
            
            if(collidingWall && !onGround)
            {
                xMove = 0;
            }
            // Etc.

    Now if we are colliding against a wall, we will stop the player from applying force.

  6. Save the script and go back into Unity, refreshing the scripts if needed and hit the Play button:
    Preventing the player getting stuck

Now our player can jump along walls and fall like normal, and the player can jump only when he is on the ground! We now have the basis to make a platformer game completed!

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

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