Basic · Stand the model up straight
30 minYou are building: correct values for model_scale, model_offset, head_height and model_yaw_offset_deg on every character — and the knowledge to diagnose a facing bug without guessing.
1 · The box the model must fit
Every player in this game stands inside the same collision shape: a capsule 1.8 m tall with a 0.4 m radius, whose bottom sits at local y = 0.
Your model's feet must land there too. The capsule is what the world collides with; the model is only what you see. If they disagree, the character appears to hover or wade.
2 · The symptom table
This is the whole troubleshooting guide. Keep it somewhere you can find it again.
Blender exports at 1 unit = 1 metre, so model_scale is usually 1.0. A model authored in centimetres needs 0.01.
3 · Measure your model
- Open the imported model scene on its own. Select its
MeshInstance3D. - In the Inspector, look at the mesh's size, or just read the grid in the viewport — each square is one metre.
- If the character is about 1.7 m tall,
model_scalestays 1.0. If it is 170 units tall, set 0.01. - Note where the feet are. If the model's origin is at the hips, you will need a negative
model_offset.y. - Estimate eye level in metres and put it in
head_height. About 1.55 for an adult-sized human.
Write your values into each character resource. You will confirm them for real in lesson 8, when there is a capsule to stand in.
4 · Two reasons a model faces backwards
They look identical on screen. Knowing which one you have saves an afternoon.
Problem A · The rotation maths
In Godot a node's forward axis is −Z. Rotate a node by yaw t around Y and its forward vector becomes (−sin t, 0, −cos t).
So to face a direction (x, 0, z) you need t = atan2(−x, −z). Write atan2(x, z) instead and the yaw is exactly 180° out:
# Wrong — the model looks the opposite way to its travel.
var facing := atan2(direction.x, direction.z)
# Right — model and movement agree.
var facing := atan2(-direction.x, -direction.z)The tell, and the trap
Because the error is a half-turn, it flips every axis at once. If forward, back, left and right all look mirrored, you have one rotation bug — not four input bugs. Do not “fix” it by reversing your input actions or negating WASD. Movement was never wrong. That breaks what worked and leaves the real bug in place.
Problem B · The model itself faces +Z
If the maths is right and your model is still backwards, the model was authored the other way round. Plenty of artists model characters looking down +Z; Godot expects −Z.
Fix it in the resource, not the code: set model_yaw_offset_deg = 180 on that character. It is per-character on purpose, because it varies per model. One asset pack faces +Z and the next faces −Z, and a fix in code would break every model that was already correct.
Here is where the value is used, in lesson 8's player script:
model_root.add_child(instance)
_model = instance
if _data != null:
instance.scale = Vector3.ONE * _data.model_scale
instance.position = _data.model_offset
instance.rotation.y = deg_to_rad(_data.model_yaw_offset_deg)
_head_height = _data.head_height5 · Telling them apart in ten seconds
- Press V to go first person, then back. First person locks the body to the camera through a different code path, so the two problems behave differently.
- Walk forward in third person and watch a character that is still a placeholder capsule — it has a small nose pointing along −Z.
- Capsule correct, your model backwards ⇒ Problem B. Set
model_yaw_offset_deg = 180. - Even the capsule backwards ⇒ Problem A. Check the
atan2line.
Done when: every character resource has values for all four fields, and you can state in one sentence how to tell Problem A from Problem B.
Challenge 1 · Name the fix 🟢 Easy
8 minFor each symptom, write the field you would change and which way you would move it.
- The character's ankles are buried in the floor.
- The character is the size of a lamp post.
- In first person you are looking at the inside of the character's chin.
- The character walks forward while looking over its own shoulder, and so does the placeholder capsule.
- The character walks forward while looking backwards, but the capsule is fine.
Done when: five answers, each naming a field or the atan2 line, with a direction.
Challenge 2 · Test the maths with no game 🟡 Medium
14 minYou can check the facing formula before a single character exists. Save this as facing_check.gd in the project root:
extends SceneTree
func _init() -> void:
var cases := {
"W (forward)": Vector3(0, 0, -1),
"S (back)": Vector3(0, 0, 1),
"A (left)": Vector3(-1, 0, 0),
"D (right)": Vector3(1, 0, 0),
}
for label in cases:
var direction: Vector3 = cases[label]
var yaw := atan2(-direction.x, -direction.z)
# -Z is the node's forward axis in Godot.
var facing := -Basis(Vector3.UP, yaw).z
var ok := facing.distance_to(direction) < 0.001
print("%-12s want %v faces %v %s" % [
label, direction, facing.snappedf(0.001),
"OK" if ok else "WRONG"
])
quit()Run it from a terminal:
Godot_v4.4.1-stable_win64.exe --headless --path . --script res://facing_check.gd- All four lines should say OK. Copy the output into your notes.
- Now change the yaw line to
atan2(direction.x, direction.z)and run again. How many lines say WRONG? - Put it back.
Use the local basis, not the global one
A first attempt at this script used node.global_transform.basis and printed nonsense. A node created during _init is not in the tree yet, so global_transform silently returns identity. Building a Basis directly needs no tree at all.
Done when: four OKs with the correct formula, and you have seen how many break with the wrong one.
Challenge 3 · A fitting bench 🟠 Harder
16 minSetting these numbers by guessing is slow. Build a scene that shows you the answer.
- New scene,
Node3Droot, saved asres://scenes/fitting_bench.tscn. - Add a
WorldEnvironment, aDirectionalLight3Dand aCamera3D. - Add a
MeshInstance3Dwith a CapsuleMesh: height 1.8, radius 0.4. Move it up 0.9 so its bottom is at y = 0. Make its material half transparent. - Add a
MeshInstance3Dwith a BoxMesh sized 0.1 × 0.1 × 0.4, placed at your character's eye height and pushed along −Z. That is your nose marker. - Attach a script that applies a character resource:
extends Node3D
@export var character: CharacterData
func _ready() -> void:
if character == null or character.model_scene == null:
return
var model := character.model_scene.instantiate()
add_child(model)
model.scale = Vector3.ONE * character.model_scale
model.position = character.model_offset
model.rotation.y = deg_to_rad(character.model_yaw_offset_deg)
print("head height: ", character.head_height)- Drag each character resource into the Character slot in turn and press F6.
- Adjust the resource until the feet touch the capsule's base and the head reaches near its top.
Done when: every character fits its capsule on the bench, and the model faces the same way as the nose marker.
Extra · Go deeper on 3D maths 🔴 Open-ended
no limit- Draw the −Z convention on paper: the axes, a node, and its forward arrow. Then draw what
atan2(x, z)would give you. Keep the drawing. - Extend
facing_check.gdwith the four diagonals. Should they all still be OK? - Turn the fitting bench into an
@toolscript so it updates in the editor as you change the resource. - Look up Basis in the Godot docs. Write one sentence on what the three columns of a basis actually are.