Unlike Unity, Godot doesn’t ship with any built-in character controllers, neither third-person nor first-person. So, in this chapter, we’ll create a reusable first-person controller rig from start to end in C# for Godot. Once created, we’ll be able to drag and drop first-person controls directly into any scene. The WASD keyboard keys will move the camera forward, left, backward, and right, respectively. Holding down the Shift key enables run mode and the space bar initiates a jump. Mouse movement will control head orientation, and the first-person controller overall will support many physical interactions, including gravity, collisions, ramp movement, and more. See Figure 6-1.
Getting Started – Creating a Camera Scene
Let’s begin the first-person character controller by creating a new scene to contain our objects. In Godot, a Scene behaves like a Unity Prefab. It allows us to create an asset in isolation, which can be added and reused across multiple scenes. To create a new Scene, select Scene ➤ New Scene from the application menu, and then choose 3D scene from the Create Root Node menu. See Figure 6-2.
Next, right-click the root node from the Scene Tree, and then choose Add Child Node to show the Create Node Dialog. From here, create a KinematicBody Node (similar to a character controller in Unity – or a Rigidbody set to Kinematic Mode). A Kinematic Body is useful for building player-controlled characters or AI-controlled characters that need to react with physics and collision bodies. This node will become the root node – or main node – of the player character. See Figure 6-3.
The newly created Kinematic Body node should become the root of the scene, that is, the topmost node. It’ll be the topmost node of the player character, and there’ll be several child nodes. Our current scene should contain only the player character – it’s not intended to be played stand-alone but should be embedded into another scene, instantiated as a node, wherever first-person controls are needed. To do this, right-click the KinematicBody node in the Scene Tree and then select Make Scene Root from the context menu.
As the Kinematic Body becomes the root, the previous root node (a 3D Spatial) will now become a child, and it may be safely deleted or left as is. We’ll need a spatial node for later anyway. Every KinematicBody needs a CollisionShape child node to express the character’s volume and size (equivalent to a Unity Collider). Let’s add one now, specifically a CollisionShape node. See Figure 6-5.
The CollisionShape node begins as an empty spatial object with no width, height, or depth. To assign it shape, volume, and form, simply select the CollisionShape node and then move to the Shape field in the Inspector. From here, choose New CapsuleShape. The Capsule best approximates the volume of a humanoid character. See Figure 6-6.
You may need to rotate the CollisionShape by 90 degrees on the X axis – or a different axis (depending on your object’s orientation) – and then tweak the CapsuleShape’s settings to adjust its Radius and Height. The idea is to resize the CollisionShape to approximate a humanoid character, such as the player or an NPC. See Figure 6-7.
Now let’s focus on building the character’s head, which will be the center of the camera’s view. To start, create an empty spatial node – a child of the root – or use an existing spatial if you have one. Rename the node to “Head” and position it toward the top of the capsule at the center of the head location. Remember, the blue forward arrow should always point in the direction of the character’s view. See Figure 6-8.
The blue arrow of the transform gizmo is the forward vector. It must represent the direction in which the character is facing. All nodes within the player hierarchy should share the same orientation, with the forward vector pointing forward.
Having now used a spatial node to mark the head position, we should add a camera as a child object. Simply add a camera node using the Node Creation Window, and then rotate its transform if needed by 180 degrees around the Y axis to face forward. See Figure 6-9. The Player Rig is now completed and ready to code. Excellent!
Player Movement and Key Bindings
One of the most important mechanics supported by any first-person controller is movement, both walking and running, specifically moving forward and backward using the W and S keys and strafing – or side stepping – left and right with A and D. To read input from these keys on the keyboard or to read input from buttons on a gamepad, we must first configure the game’s Input Actions. This is simple to achieve. To do this, select Project ➤ Project Settings from the application menu, and then switch to the Input Map tab. See Figure 6-10.
We’ll need to create four distinct Actions to read input from WASD: left, right, up, and down. In Godot, an Action is a form of key binding. It converts a button press or an analog control into numerical data that drives gameplay. To add Actions, simply type a name for each action into the Action field, and then click the Add button for each action to add four new actions in total. I’ve used the names move_forward, move_backward, move_left, and move_right, respectively. See Figure 6-11.
Each Input Action defines one or more key bindings or mappings, a conversion from a button press to axis data. Let’s explore how to create bindings for the move_forward Action; and the remaining Actions are a repeat of that process. Click the Add Event button (+ icon), located beside the Input Action name. This creates a new key binding. On clicking Add Event, select Key from the context menu. This will link a keyboard button to the Input Action. See Figure 6-12.
After clicking Key from the context menu, Godot prompts you to press the relevant keyboard key to form the key binding. For the move_forward Action, press the W key. See Figure 6-13.
You can also add multiple keys to the same Action. Simply click Add Event again, and then add a new key binding. For the move_forward action, the W and Up arrow keys are most appropriate. See Figure 6-14 for the complete move_forward binding, along with all other actions, move_backward, move_left, and move_right. See Figure 6-14.
Reading Input Actions for Movement
The previous section demonstrated how to configure four input actions for player movement and all its associated key bindings. This links forward, backward, left, and right movement to the WASD keys and to the directional arrows. In this section, we’ll create a new script file for the PlayerCharacter, which reads input through the Actions. To get started, create a new C# script file named FPSControl.cs and attach the script to the topmost KinematicBody node, as shown in Figure 6-15. The newly created script is shown in Listing 6-1.
public class FPSControl : KinematicBody
// Declare member variables here. Examples:
// private int a = 2;
// private string b = "text";
// Called when the node enters the scene tree for the first time.
public override void _Ready()
// // Called every frame. 'delta' is the elapsed time since the previous frame.
// public override void _Process(float delta)
Godot Auto-Generated FPSControl Script
Godot offers the GetActionStrengthfunction to read input data from the specified Action. This function returns a smoothed floating-point value in the 0 to 1 range; 0 means “not pressed” and 1 means “fully pressed.” These values, read from four Actions, can effectively be translated across two axes of movement. Specifically, move_forward and move_backward together represent the Vertical Axis. And move_left and move_right represent the Horizontal Axis. We can visualize both Horizontal and Vertical in the –1 to 1 range. –1 means down or left, and 1 means right or forward. 0 means neither Action is pressed, or both are pressed together (because –1 + 1 = 0). To read input across the four Actions and to map them into our two axes of character movement, let’s first add Export string variables to our class, allowing us to customize axis names from the inspector, which will be plugged into the function GetActionStrength. See the following code. Export is equivalent to the SerializedField attribute in Unity.
private string LeftAxis, RightAxis,
UpAxis, DownAxis = string.Empty;
Next, we’ll use the _process event to read input from the actions and map them to the two axes. See Listing 6-2.
Converting Input Actions to 2D Movement, Horizontal and Vertical
Establishing Move Direction
Input read from Actions using GetActionStrength can be converted easily into two dimensions of local motion: horizontal and vertical – left and right and forward and backward, respectively. This motion may be expressed in two floating-point values only. This motion is local insofar as horizontal and vertical movement always relate to the direction in which the player is currently facing. Forward always means forward for the player, as opposed to forward in the world, for example. For this reason, we’ll need to convert the local movement strength to a world space direction for moving the player based on input. To do that, consider the revised _process event in Listing 6-3.
The code in Listing 6-2 depends on two additional class variables. The first is a Vector3 variable, MoveDirection, which always represents the normalized velocity of the player character, that is, the direction of movement. This variable is normalized and has a unit length, allowing it to be scaled by any speed needed. The other is the HeadNode variable, which refers to the empty spatial node that is the parent of the camera. Chapter 3 explores how to retrieve node references from a NodePath using the GetNodefunction. The head always represents the direction of travel for the player. This is because when moving forward and backward or left and right, the character is always moving in the direction of stare. Here’s how you can read the Head Node and Camera Node objects at application startup, from NodePath variables defined in the Inspector.
public override void _Ready()
HeadNode = GetNode(HeadPath) as Spatial;
CamNode = GetNode(CamPath) as Spatial;
Notice the Input.SetMouseMode function is used to hide the system cursor, allowing for more intuitive head motion.
The MoveDirection variable is calculated each frame by using the basis member of the Head Node. The basis variable expresses three vectors, which are the local X, Y, and Z axes in world space. This means that the basis.z variable is the forward vector in world space and the basis.x is the right vector in world space.
The previous section detailed how to convert the player’s move direction into world space using a normalized vector. This vector can be multiplied by a speed to offset the player along its velocity smoothly and seamlessly, frame by frame. However, this direction doesn’t account for gravity, which is a force normally pulling everything downward toward the ground at an accelerated rate. To apply gravity, let’s create a representation of it using a Vector3 variable, expressing direction and strength.
public Vector3 Gravity = Vector3.Zero;
A gravity vector of type Vector3.Zero will have no strength. For the player character, a vector of (0, –30, 0) works well in most cases. This can be specified in the Inspector. Other interesting values include (0, 30, 0), which would push an object upward, like a perpetual jump, and (–30, 0, 0), which would continually pull an object sideways.
Completing Player Movement
In this section, we’ll complete the crucial functionality started in previous sections, namely, input, direction calculation, and gravity. Here, we’ll complete the movement controls for the first-person character, specifically WASD motion. This code will span two functions, namely, _Process and _PhysicsProcess. _Process will be used to read input directly from the keyboard, and _PhysicsProcess will convert that input to physics-based movement for the KinematicBody. Consider the following _Process and _PhysicsProcessfunctions in Listing 6-4.
In Listing 6-4, the _Process event uses the Input.GetActionStrengthfunction to read input data from the keyboard – on both the horizontal and vertical axes – and converts that to numerical data within the –1 to 1 range. This data is then constructed as a Vector3 structure, representing object velocity. The Transform.basis member expresses the three local axes of an object (Up, Forward, and Right) in world space. These are expressed in basis.y, basis.z, and basis.x, respectively.
Next, the _PhysicsProcess event uses the Vector3.LinearInterpolate function to smoothly transition the player’s current position to a new position, based on our input velocity. The MoveAndSlideWithSnapfunction will move an object along its velocity but consider important physical properties about the world. Specifically, MoveAndSlideWithSnap ensures that, firstly, the player moves around successfully without passing through solid objects, like walls and enemies; secondly, the player can move up or down ramps and inclines; and thirdly, the player will move cleanly when standing on movable surfaces, like conveyor belts or ascending platforms. The SlopeAngle and Floor Normal parameters determine these behaviors.
Notice also that our code calculates gravity. That is, for each _PhysicsProcess, our velocity is calculated and gravity applied to continually pull an object downward. This code uses the Gravity vector defined in the previous section.
Head Movement and Orientation
In the preceding section, the _PhysicsProcess function generated a velocity vector for the player character, based primarily on direct keyboard input. Pressing the W key or the Up arrow always moves the player forward, that is, forward in the direction in which the camera is facing. This section explores how to determine our look direction in code and how to control it using mouse movement. Let’s start by reading input from the mouse, specifically mouse movement on the horizontal and vertical axes. This is important because Horizontal mouse movement (sliding left or right) controls head rotation around the Y axis, while movement along the Vertical direction controls head rotation around the X axis. The following code can be added to our FPSControl class. See Listing 6-5.
public override void _Input(InputEvent motionUnknown)
InputEventMouseMotion motion = motionUnknown as InputEventMouseMotion;
if (motion != null)
MouseMove = motion.Relative;
Reading Mouse Movement Input
The _Input event is a native function of the Node class, which can be overridden by any script to handle input events, like key presses and mouse movement. This function will always fire when an input event changes. Here, this function is used simply to record the Relative mouse motion, that is, how much the mouse position has changed in screen space since the previous input event. The MouseMove variable is simply a Vector2 class variable used to express the current mouse movement.
The revised _Processfunction now supports head rotation based on mouse movement. The complete head rotation is calculated from the MouseResult Vector2 structure, where MouseResult.X expresses rotation around the camera’s local Y axis and MouseResult.Y expresses rotation around the camera’s local X axis. The MouseSensitivity floating-point variable is a multiplier to either strengthen or weaken the rotation effect. Notice the Spatial variable HeadNode references the Spatial parent of the camera. This node rotates according to mouse input.
Head rotation is a multistep process, iterating through Pitch (rotation around X) and Yaw (rotation around Y). First, the calculated Pitch is constrained by the Mathf.Clampfunction and a PitchLimit. The PitchLimit variable is a Vector2 object whose X and Y components define the minimum and maximum rotation in degrees, respectively, that the head may rotate from its starting orientation around the local X axis. This prevents the camera from rotating upside down, which would normally happen if the player continually moved the mouse up or down without stopping or reversing direction. Second, the Transform.Rotatedfunction is called to rotate the Camera (or more specifically, it’s empty parent) around its local Y and then its local X. Notice the utility function Mathf.Deg2Rad is called during this process, converting angles in degrees to radians, as expected by Godot’s Rotated function.
The final step is ensuring the Spatial.RotationDegrees variable (a Vector3 object) is clamped within our acceptable pitch range. RotationDegrees parallels Unity’s EulerAngles variable. It’s a Vector3 structure expressing Pitch, Yaw, and Roll – rotation around each local axis. So by rotating the camera based on mouse movement as we have done here, the CameraNode.basis.z variable always expresses our forward direction in world space – the direction we’re facing.
Jumping and Being Grounded
Our first-person player character must support a jumping behavior, that is, the ability to jump in the air and then to fall down again by gravity. Jumping introduces two important states for the player, namely, grounded and not grounded. Grounded is true whenever the player is in contact with the floor and false when not. Knowing when a character is grounded is important for preventing double jumps – for not being able to jump while you’re already jumping or falling and for enabling animations or character behaviors that change depending on whether the character is jumping, falling, or standing. For our character, a space bar press initiates a jump. To code this, let’s first configure the relevant Input Action, as with movement, by simply accessing the Project ➤ Project Settings menu and by adding a Jump action from the Input Map, as demonstrated earlier in this chapter. See Figure 6-16.
In addition, let’s add three new variables: JumpPower, a float describing the strength of our jump; Grounded, to describe whether we are currently touching the ground or not; and Snap, which will be plugged into the MoveAndSlideWithSnap function, inside _PhysicsProcess. When we’re jumping, Snap should be Vector3.Zero to ensure we can lift off without being connected to the floor, and when falling, it should be Vector3.Down, to ensure we fall back to ground.
private bool Grounded = false;
private float JumpPower = 15f;
private Vector3 Snap = Vector3.Down;
Now we’ve added our variables, let’s refine the _Process function to detect and handle a Jump situation. See Listing 6-7.
A jump works like inverted gravity. Gravity pulls you down with acceleration, and a jump pulls you up with deceleration. Listing 6-7 detects a jump press to initiate a jump, but it depends on the Grounded variable being accurate, representing our contact status with the floor. Godot provides a convenient function in the KinematicBody class to detect floor contact, namely, IsOnFloor. This function returns true if the KinematicBody is on the ground. The _PhysicsProcessfunction can therefore be written in full as in Listing 6-8.
Basically, sprinting can be coded as “fast walking.” Previously, our first-person controller had a walk speed determining how quickly it moved around the scene when horizontal and vertical input was given. Here, we’ll adjust the code to support two different speeds depending on whether the Shift key is being held down. A Shift key press should be configured as an Action from the Input Map – “move_sprint.” To support running, we’ll create a SpeedMultiplier float variable. A value of 1f is the “default speed.” As a result, run functionality can be added to the _Process function with the Listing 6-9.
SpeedMultiplier = RunMultiplier;
SpeedMultiplier = 1f;
Add Sprint Functionality
Head Bobs and Sine Waves
This section adds our final, polish feature to the first-person controller, specifically the Head Bob, that is, the smoothed, bobbing (up and down) motion of the head as it naturally adjusts to character locomotion, walking through the scene. A Head Bob is, simply put, a Y axis position adjustment, back and forth that applies only when the character is moving. To implement a Head Bob, we’ll use a Sine Wave. This is a smooth, repeated curve that we can programmatically move through to determine the Y position of the character’s head over time. This position can be generated from just two float variables, Amplitude and Frequency. Amplitude determines the maximum height of the curve and thereby the strength of the bob. Frequency determines the smoothness of the effect, the ratio of bobs to linear distance. Let’s get started with Head Bobbing by creating three new class variables, which we’ll use later.
public float HeadAmplitude, HeadFrequency = 1f;
//Current Y-Pos of the head
private float BobHeight = 0f;
Next, we’ll code a new and separate function to handle Head Bobbing independently, which will be called every frame, inside _Process. See Listing 6-10.
Excellent! You’ve now coded a great looking, and fully tweakable, head bob. Let’s break down the code here. Firstly, the HeadBobfunction accepts a delta value, which can be passed from the _Process event. This is simply a deltaTime value. Inside the _Process event, the HeadBob function begins by checking our Grounded status to ensure we’re on the ground. It then proceeds to wrap an incremental, numerical value between 0 and 360 into Mathf.Sin to retrieve a value along the since curve. This becomes the LerpedHeight and updates the camera origin on Y.
Completing the FPS Controller
So, we’re done. The first-person controller is now fully coded. Great job! Godot doesn’t ship with a C# first-person controller, so we’ve just made an important contribution. Check out the complete code for the controller in Listing 6-11.
public override void _Input(InputEvent motionUnknown)
InputEventMouseMotion motion = motionUnknown as InputEventMouseMotion;
if (motion != null)
MouseMove = motion.Relative;
The Complete First-Person Controller
Testing the Controller
You’ve built a first-person controller. Great! Now let’s test it. To do this, create a completely new empty scene. And then add a selection of mesh instances to it, boxes, cubes, cylinders, and others, to build some scenery. Ensure that you generate static bodies for each mesh to enable collisions. See Figure 6-17. Remember, the associated book companion files feature a basic collision scene for your testing.
Next, let’s add the player character. The first-person controller was created as a completely separate scene, which means it can be dragged and dropped into any other scene, just like a Unity Prefab or an Additive Scene. See Figure 6-18.
Once the player is added, you’ll need to set your starting properties for the first-person controller. By selecting the player object, you can adjust its fields from the inspector, customizing them for active scene, as opposed to the original. See Figure 6-19.
Now you’re ready to try out the new first-person controller. Simply save the scene, and then press the Play Scene button from the toolbar. See Figure 6-20. Great work!
Congratulations. In this chapter, you coded a first-person controller in C# for Godot, complete with run, jump, and head-control mechanics. In reaching this far, you’ve seen varied examples for reading input, both event-based and polling-based; ways to move objects during _PhysicsProcess to account for physical interactions; how to use scenes as Prefabs, nested inside other scenes; and how to create input actions and more.