First-person Character
In this installment, we’ll look at how to make a first-person character. We’ll use the CSG-based level we designed in the previous part as a place to walk around and test our movement.
Character Scene
In an FPS or similar game, we want to give the player the illusion that they’re looking out of the character’s eyes. One nice aspect of this is that we don’t really need a model, at least to get started.
Start with a KinematicBody
. To this we’ll add two CollisionShape
nodes (“Body” and “Feet”). We also want to have a Camera
, but we need to be careful about how we handle rotation. The character should rotate in Y, but only the camera should rotate in X (for looking up and down). To make this work, add a Spatial
node, which we’ll call “Pivot”, and add the Camera
to that.
The “Body” collision shape is going to represent the player’s body. We can use a CapsuleShape
(rotated 90
degrees around X). Set its Radius to 0.5
and Height to 1
.
One issue with using a CapsuleShape
is that the bottom is rounded. This is good for moving over small bumps in a natural way, but it also means that when standing on the edge of a surface, the player will “roll” off in a strange way. We can prevent this by using a BoxShape
for the “Feet” collision. Make its extents (.4, .1, .4)
and place it so that its bottom is just above the bottom of the capsule. We’ll still have the round bottom to move over things, but the box will keep us from sliding off ledges.
Move the Pivot
up a bit, so that it’s not pointing the camera out of the “middle” of the body.
Moving around
Much of the code for movement is the same as we used for the third-person character earlier in this series. We’ll start with declaring our variables:
extends KinematicBody
onready var camera = $Pivot/Camera
var gravity = -30
var max_speed = 8
var mouse_sensitivity = 0.002 # radians/pixel
var velocity = Vector3()
The value of mouse_sensitivity
maps the movement of the mouse, which is in pixels, to rotation, in degrees. So for each pixel the mouse moves, we’ll rotate by 0.002
radians (or about 0.1
degrees).
func get_input():
var input_dir = Vector3()
# desired move in camera direction
if Input.is_action_pressed("move_forward"):
input_dir += -global_transform.basis.z
if Input.is_action_pressed("move_back"):
input_dir += global_transform.basis.z
if Input.is_action_pressed("strafe_left"):
input_dir += -global_transform.basis.x
if Input.is_action_pressed("strafe_right"):
input_dir += global_transform.basis.x
input_dir = input_dir.normalized()
return input_dir
When detecting input, we want to move in the direction the body is facing.
Next, we add up the inputs and return the resulting direction vector.
func _unhandled_input(event):
if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
rotate_y(-event.relative.x * mouse_sensitivity)
$Pivot.rotate_x(-event.relative.y * mouse_sensitivity)
$Pivot.rotation.x = clamp($Pivot.rotation.x, -1.2, 1.2)
We also need to capture mouse movement for camera rotation. As discussed above, horizontal mouse movement rotates the entire body in Y, while vertical motion rotates the helper node in X.
We also need to limit that vertical rotation to prevent the camera from flipping upside-down.
func _physics_process(delta):
velocity.y += gravity * delta
var desired_velocity = get_input() * max_speed
velocity.x = desired_velocity.x
velocity.z = desired_velocity.z
velocity = move_and_slide(velocity, Vector3.UP, true)
In _physics_process()
we get the desired_velocity
, the direction vector returned by get_input()
, and multiply it by max_speed
to set its length. We don’t want to alter our velocity.y
because that’s set by gravity, so we only set the x
and z
components based on the input.
We’re also setting true
for the stop_on_slope
parameter of move_and_slide()
. This keeps us from sliding down the ramp if we stand on it.
Test your movement and ensure everything is working as expected.
Adding a weapon
First person characters typically have some sort of item, or at least empty hands, visible in front of them. We’re going to use the shotgun model from the following art pack:
Addd a MeshInstance
to the Pivot
node and use the shotgun.obj
model from the art pack. You’ll notice that the model is too small for our player’s scale, so set Scale to (8, 8, 8)
.
We need to position the model so that it’s projecting out “through” the camera. Aligning it can be difficult, so click the “View” menu and choose “2 Viewports”. In the bottom one, select the Camera
and click “Preview”, and in the top you can move the gun until it looks right.
Wrapping up
We now have a basic first-person character controller. This could make a good foundation for a wide variety of game types by adding features and behaviors, as we’ll see in later lessons.
You can also find a video version of this lesson here: