Action-Adventure Player Controls

Introduction

The right player controls are a key-element for your game. If the controls are bad your game cannot be good either. In this blog post, I will introduce the player controls to you we used in our action-adventure game Kordex. This blog is the part of a bigger tutorial series concerning Player Controls, Camera Controls and Camera Collision. In this first part of the series we will cover Action-Adventure like Player Controls.

Player Controls Concept

The concept we present here is derived from previous action-adventure games like “The Legend of Zelda: The Wind Waker” or “Super Mario 64”.

The basic idea is that if the player moves forward (The “W” key in a WASD controls setup) he always runs away from the camera. So the forward vector of the player is the vector from the camera to the player. The other three directions (left, right and back) are defined according to the forward direction.

Player Controller

Movement

The forward direction of the player is always facing away from the camera. Consequently when moving right the player circles the camera clockwise.

Grounding

In order to place the player on the ground we shoot a ray downwards (negative y) from the center of the player and check if it hits the ground. If the ray does not hit or the distance between the hit and the center of the player is greater than half the height of the character, the player is mid-air and a gravity vector is applied to the character’s velocity. Otherwise the player is counted as grounded and the velocity in y-direction is set to zero. Additionally, to prevent the player going to mid-air mode even if he just falls from a little step or walks down a slope we additionally add a little epsilon to the ray. In this region the player is theoretically not touching the ground but counted as grounded.

Player Controls Implementation

In this section we will show you how to implement the concepts explained above into your own Unity project. You can also download the project using the download button above.

First of all, start a new Unity project and setup a basic scene with a level where the player should walk around later.

The next step is to import the Unity standard package “Characters”. To do so, go to Assets > Import Package > Characters. On the opening window click Import.

After importing drag the character model “Ethan” from the imported package into the Hierarchy and rename it to “Player”.

Add a Rigidbody and a Capsule Collider component to the player. Afterwards in the Rigidbodysettings, untick Use Gravity and Freeze Rotation in all three axes. Adjust the Capsule Collider size to match the player body but leave some space at the bottom so the collider is not touching the floor (also not when standing on a slope) so it does not interfere with our PlayerController-raycasting later on.

Animator Controller

Before we begin to implement the player, we setup a basic animator controller in order to get a better feeling over the controls of the player. First of all create a new animator controller by choosing Assets > Create > Animator Controller and name it “PlayerAnimatorController”. In order to assign this new animator controller to our character just drag and drop the “PlayerAnimatorController” on the “Player” in the Hierarchy. If you double-click on the “PlayerAnimatorController” now, the animator window should pop up. Here you can create animation states for each animation our player should perform.

Animator States

This tutorial just covers the idle and the run animation provided from the Standard Assets. Drag both the “HumanoidIdle” and the “HumanoidRun” into the animator.

Make sure that “HumanoidIdle” is the default state (marked as orange and “Entry” has a transition to “HumanoidIdle”). If not, right click on “HumanoidIdle” and click Set as Layer Default State. After that, rename “HumanoidIdle” to “Idle” and “HumanoidRun” to “Run” for simplification.

Animator Transisitons

Then right click on “Idle” and select Make Transition. Next, click on “Run” to make a transition from “Idle to “Run”. Select this newly created transition and uncheck Has Exit Time in the Inspector to enable instant transition between those two states. Additionally create a transition from “Run” to “Idle” with Has Exit Time unchecked.

The next step is to define when the transition between “Idle” and “Run” should occur. For this, we create a new parameter of the type Float in the Animator.

Name this one “MoveSpeed”. Afterwards select the transition from “Idle” to “Run” and add the condition “MoveSpeed Greater 0.1” in the Inspector.

Then select the other transition and add the condition “MoveSpeed Less 0.1”

That’s it so far for the Animator Controller. If you hit play now you should see the player doing its idle animation. Note that this is just very basic. Only the basic animations are setup here to get a general feeling of the player movement. Animations like a “Jump” can be added of course.

Player Controller

After completing the animation controller, it’s now time to implement the script responsible for player movement. Additionally we will also create a simple script for camera control. First of all, create two C# scripts “PlayerController” and “SimpleCameraController” and drag them onto the “Player” and the “Main Camera” respectively.

We will begin with writing the PlayerController. Open this script in your favorite editor and delete the Start() and the Update() method to get the following empty class:

using UnityEngine;
// Script for controlling the main character
public class PlayerController : MonoBehaviour
{

Attributes

Next we will add two nested classes MoveSettings and CharacterInput. MoveSettings contains different parameters concerning speed and falling which are configurable in the inspector later, while CharacterInput stores the keyboard and mouse input concerning the player:

// Class wrapping up settings concerning the character movement
[System.Serializable]
public class MoveSettings
{
// How fast the player moves
public float forwardVel = 4;
// How fast the player turns
public float rotateVel = 1000;
// How high the player jumps
public float jumpVel = 6;
// The distance to the ground to count the player as fully grounded
public float distToGround = 0.0f;
// The minimal distance to the ground to count the player falling (distToGround + eps)
public float minFallingDistToGround = 0.1f;
// The offset from the transform position to cast the down ray from
public Vector3 pivotOffset = new Vector3(0.0f, 0.5f, 0.0f);
// The mask defines what objects are counted as ground
public LayerMask ground;
}
// Class containing all input fields
class CharacterInput
{
// The input in the forward direction
public float forward = 0;
// The input in the sideward direction
public float sideward = 0;
// The input for jumping
public float jump = 0;
}

The first three attributes of the MoveSettings class define the respective velocities of the player. The distance to the ground defines how high the player should stand above the ground measured from the player’s pivot to the ground object. The minFallingDistToGround realizes the concept explained above so that the player does not always fall instantly when walking down slopes. The raycast in the downwards direction for ground detection will be cast from the 3D model pivot + the pivotOffset. The purpose for the offset is not to miss the ground if the player extends a bit into the 3D model geometry of the floor. The LayerMask ground allows you to specify what’s ground and what is not.

The CharacterInput class just stores the input values for forward-/sideward-walking and jumping.

We have an instance of MoveSettings and a down acceleration which defines how fast the player falls. Both can be configured in the Unity Inspector later. Furthermore there are several private fields. We will always store the velocity of the player which is applied each frame. Additionally, we will check every frame if the player is grounded and set the grounded attribute appropriately. The potential hit with the ground will be stored in groundHit. Another possible state of the player is jumping, for which the onJump attribute stands. The rest of the attributes are just references to the player components and the camera.

Methods

The Unity methods Start(), Update() and FixedUpdate() are defined as followed:

In the Start() function, all the necessary components are collected. The Update() function checks for relevant key or mouse input and calls the Turn() function for player turning. For all physically based operations the FixedUpdate() is used. Because the CheckGrounded() method will use ray casting and the Run() and Jump() methods are working with velocities they are called here. Finally the forward velocity of the player is applied based on the player rotation. If the player is standing on the ground the move direction is additionally directed along the tangent of the surface. The gravity is always applied in the same direction regardless of the player rotation.

The predefined axis "Vertical" and "Horizontal" are used for player input. The jump button is held to 1.0 if recognized because the Jump() function call happens in the FixedUpdate() and not in the normal Update().

Running is only possible in the viewing direction. Turning is done based on the input vector and the camera vector. Jumping adjusts the y-velocity and sets the onJump attribute of the class.

Finally the ground check looks like the following code:

// Checks if the player is standing on a ground
void CheckGrounded()
{
bool lastGrounded = grounded;
// Cast a ray downwards in y-axis with a max distance of pivotOffset.y + minFallingDistToGround to see if it hit the ground layer
bool hit = Physics.Raycast(transform.position + moveSettings.pivotOffset,
Vector3.down, out groundHit, moveSettings.pivotOffset.y + moveSettings.minFallingDistToGround, moveSettings.ground);
// Subtract the pivotOffset.y from the distance passed
groundHit.distance -= moveSettings.pivotOffset.y;
// The player is counted as grounded if
// his distance to the ground is smaller than the set distance to ground
// or
// his distance to the ground smaller is smaller than the minimum falling distance set and
// he was standing on the ground the last time checked and he is not during a jump
if (hit && (groundHit.distance < moveSettings.distToGround || (lastGrounded && !onJump))) { // Player is grounded -> place him exactly on the ground with a distance of distToGround
transform.position = new Vector3(transform.position.x, groundHit.point.y + moveSettings.distToGround, transform.position.z);
grounded = true;
// Set jump as finished
onJump = false;
}
else
{
// Not grounded
grounded = false;
}
}
}

First a ray is cast down from the player pivot + offset. If this ray hits, the player has a distance of minFallingDistToGround or less to the ground. The next if-branch serves the purpose of not cancelling an open jump by placing the player exactly over the ground.

Simple Follow Camera

For testing purposes we will introduce a very simple follow camera. The camera just keeps a certain distance in the xz-plane to the player and always looks towards him. There is no controlling, smoothing or collision detection yet. These topics will be discussed in the following blogposts.

Final Steps

Now it’s time to setup our PlayerController and SimpleCameraController we just wrote.

Camera Setup

First of all, we have to select a target for our SimpleCameraController. You could just drag the Player directly into the Target field but then the camera would look exactly at the pivot of the Player which are usually the feet. A better way is to create an empty GameObject as child of the player and name it “CameraLookAt” for example, so you can adjust its position directly in the Unity Scene view.

Drag this one into the the target field of the CameraController.

Player Collision

Next we will define a collision layer which will be used for the player ground. Go to Edit > Project Settings >Tags and Layers. Here you can see all defined layers for now and also have the possibility to add your own layers. Just select the first free User Layer and enter “Ground” in the text field next to it.

Now we have a new layer which we can assign to our world.

When asked if you want to set the layer for all child objects click Yes, change children. Now set the layer in the PlayerController for “Ground”.

Now you can configure the PlayerController as desired.

Currently if walking against a wall, the sphere collider of the Player hits the wall and sticks to it because the Unity physics system applies friction to colliding objects. Hence we create a custom physics material with zero friction and assign this to the Player collider. Navigate to Assets > Create > Physic Material. Name it “ZeroFrictionPhysicMaterial” and change the values in the inspector to following:

Then assign this material to the player collider:

Concluding

That’s it so far for the action-adventure player controls. Hopefully we could give you a basic overview of some character movement concepts. The provided scripts can of course be extended by many ways like the ability to focus targets and a better jump with restricted player movement which will be explained in following tutorials. You can also add your own ideas to the controllers to suit your needs. In further tutorials we will also discuss the implementation of a controllable camera and a suitable camera collision in order to prevent the camera from moving through objects.

We use cookies to ensure that we give you the best experience on our website. If you continue without changing your settings, we'll assume that you are happy to receive all cookies from this website.OKMore