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.

alt

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 += -camera.global_transform.basis.z
    if Input.is_action_pressed("move_back"):
        input_dir += camera.global_transform.basis.z
    if Input.is_action_pressed("strafe_left"):
        input_dir += -camera.global_transform.basis.x
    if Input.is_action_pressed("strafe_right"):
        input_dir += camera.global_transform.basis.x
    input_dir = input_dir.normalized()
    return input_dir

When detecting input, we want to move in the direction the camera is facing, which is not necessarily the current movement direction. Although we’re not going to do it yet, this will allow us to implement friction and acceleration for a more natural movement feel.

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:

Kenney Weapon 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.

alt

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:

Comments

comments powered by Disqus