Creating a 3D Character
In the last part, we covered how to import 3D objects and how to arrange them in a scene. In this installment, we’ll add more objects to the scene, including a user-controlled character.
Building the Scene
We’re going to continue using the Kenney Platformer Kit we downloaded in Part 2. Select all the
block*.glb files and in the Import tab set their Root Type to
StaticBody3D. Uncheck the Root Name property and click Reimport. Select
blockLarge.glb and make a new inherited scene. Use the Create Single Convex Collision Sibling option on the mesh using the menu as you did in the last tutorial. Now you can save the scene - I recommend making a separate folder for this, as soon you’re going to have a bunch of scenes representing the differently shaped platform parts.
Open the scene from the previous step with the “Ground” plane and the crates.
Delete the crates and add an instance of the large block. We want to be able
to place these blocks so that they line up. To do this, select “Configure Snap”
from the “Transform” menu at the top of the Viewport and set Translate Snap
0.5. Then click on the “Snap Mode” button (or press the
Y key). Now
duplicate the block a few times and drag them to arrange.
If you like, go ahead and add scenes for some of the other platform blocks and arrange them into a pleasing level. Be creative!
Adding a Character
Now we’re going to make a character so we can walk around on the platforms.
Open a new scene and start with a
CharacterBody3D named “Character”. This
PhysicsBody node behaves very much like its 2D equivalent (you’ve already done the 2D tutorials, right?). It has a
move_and_slide() method that we’ll use to perform the movement and collision detection.
Add a capsule-shaped
MeshInstance3D and a matching
CollionShape3D. Remember, you can add a
StandardMaterial3D to the mesh and set its Albedo/Color property to change the color.
The capsule is nice, but it’s going to be hard to tell what direction it’s
facing. Let’s add another mesh, this time with a
Set its Top Radius to
0.2, its Bottom Radius to
0.001 and its Height to 0.5, then its x rotation to
-90 degrees. Now you have a nice cone shape. Arrange it so it’s pointing out from the body along the negative z axis. (You can tell which way is negative because the gizmo arrows point in the positive direction).
In this picture, we’ve also added two sphere meshes for eyes to give a little more character. Feel free to add whatever details you like.
Let’s also add a
Camera3D to the scene, so it will follow the player around. Position the camera behind and above the character, angling it down a bit. Click the “Preview” button to check the camera’s view.
Before we add a script, open the “Project Settings” and add the following inputs on the “Input Map” tab:
Now let’s add a script to the body.
extends CharacterBody3D var gravity = ProjectSettings.get_setting("physics/3d/default_gravity") var speed = 4.0 # movement speed var jump_speed = 6.0 # determines jump height var mouse_sensitivity = 0.002 # turning speed func get_input(): var input = Input.get_vector("strafe_left", "strafe_right", "move_forward", "move_back") velocity.x = input.x * speed velocity.z = input.y * speed func _physics_process(delta): velocity.y += -gravity * delta get_input() move_and_slide()
The code in
_physics_process() is pretty straightforward: add gravity to
accelerate in the positive Y direction (downward), call
check for input, and then use
move_and_slide() to move in the direction
of the velocity vector.
get_input() we check to see which key is pressed and then move in that
direction. Run the program and test:
This is all good, but we need to be able to rotate using the mouse. Add the following code to the character’s script:
func _unhandled_input(event): if event is InputEventMouseMotion: rotate_y(-event.relative.x * mouse_sensitivity)
This will convert any mouse motion in the x direction into a rotation around the y axis.
Run the scene and confirm that moving the mouse rotates the character:
However, there’s a problem. No matter which way we’re facing, pressing W moves us along the Z axis of the world. Our movement is using global coordinates, but we need to move in the object’s forward direction.
The Power of Transforms
This is where transforms come in. A transform is a mathematical matrix that contains the object’s translation, rotation, and scale information all in one. In Godot it’s stored in the
Transform data type. The position information is
transform.origin and the orientation information is in the
Remember how the 3D gizmo can be set to “Local Space Mode”? When in this mode,
the gizmo’s X/Y/Z axes point along the object’s axes. This is the same as the
basis of the transform. The basis contains three
Vector3 objects called
z that represent these directions. We can use this to ensure that pressing the W key will always move us in the object’s forward direction.
get_input() function like so:
func get_input(): var input = Input.get_vector("strafe_left", "strafe_right", "move_forward", "move_back") var movement_dir = transform.basis * Vector3(input.x, 0, input.y) velocity.x = movement_dir.x * speed velocity.z = movement_dir.z * speed
By multiplying the input vector by the
transform.basis, we apply that transformation to the vector. Since the basis represents the object’s rotation, we’ve now converted forward and back to point along the object’s Z axis, and the strafe keys along its X.
Let’s add one more movement to the player: jumping.
Add these lines to the end of
if event.is_action_pressed("jump") and is_on_floor(): velocity.y = jump_speed
Improving the camera
You may have noticed that the if the character stands near an obstacle, the camera can “clip” inside the object, which doesn’t look nice. While coding a good 3D camera can be a complex topic on its own, we can use a built-in Godot node to get a pretty good solution.
Camera3D from the character scene and add a
SpringArm3D. This node can act as a moving arm that holds the camera while detecting collisions. It will move the camera closer if there’s an obstacle.
In its properties, set Spring Length to
5, and set its Position to
(0, 1, 0), which is at the character’s head. Note the yellow line indicating the Spring Length. The camera will move along this line - at its end whenever possible, but moving closer if an obstacle is there.
Add back a
Camera3D as a child of the
SpringArm3D, and try running the game again. You can experiment with rotating the spring arm (around its x axis to point down slightly, for example) until you find something you like.
What about first person?
If you’re curious how you would do this in first person, see the Basic FPS Character recipe. You’ll notice several similarities with the 3rd person script we wrote above.
In this tutorial you learned how to build a more complex scene, and how to write movement code for a user-controlled character. You also learned about transforms, which are a very important concept in 3D - you’re going to be using a lot in the future.