3D Game Development
Other camera helpers: unproject_position(world_pos) (3D → screen, for health bars over enemies), is_position_behind(world_pos).
29.1 Node3D fundamentals
position, rotation, scale Local transform (rotation in radians, Euler YXZ by default)
global_position, global_rotation, global_transform basis, quaternion visible, top_level Visibility; ignore parent transform
extends Node3D
func _process(delta: float) -> void:
rotate_y(1.0 * delta) # radians around the local Y axis
translate(Vector3(0, 0, -2) * delta) # move in local space
position.y = sin(Time.get_ticks_msec() / 500.0)
rotation_degrees.x = 15.0
look_at(get_node("../Target").global_position, Vector3.UP)
var forward := -global_transform.basis.z # -Z is forward
var right := global_transform.basis.x29.2 Meshes, materials and lights
Primitive meshes: BoxMesh, SphereMesh, CapsuleMesh, CylinderMesh, PlaneMesh, QuadMesh, PrismMesh, TorusMesh, TextMesh.
Import models as glTF 2.0 (.glb/.gltf, recommended), .blend (needs Blender installed), FBX (4.3+ uses the built-in ufbx importer), OBJ.
WorldEnvironment + Environment resource: sky, ambient light, tonemapping, glow, SSAO, SSR, SDFGI (Forward+), fog, volumetric fog.
CSG nodes (CSGBox3D, CSGCombiner3D) for prototyping levels; GridMap for tile-based 3D.
func make_box() -> MeshInstance3D:
var mi := MeshInstance3D.new()
var box := BoxMesh.new()
box.size = Vector3(1, 2, 1)
mi.mesh = box
var mat := StandardMaterial3D.new()
mat.albedo_color = Color.TOMATO
mat.metallic = 0.2
mat.roughness = 0.6
mat.emission_enabled = true
mat.emission = Color(1, 0.3, 0.1)
mi.material_override = mat # or mi.set_surface_override_material(0, mat)
add_child(mi)
return mi
func add_lights() -> void:
var sun := DirectionalLight3D.new()
sun.rotation_degrees = Vector3(-45, 30, 0)
sun.shadow_enabled = true
add_child(sun)
var lamp := OmniLight3D.new()
lamp.omni_range = 8.0
lamp.light_energy = 2.0
lamp.position = Vector3(0, 3, 0)
add_child(lamp)29.3 First-person controller
extends CharacterBody3D
@export var speed := 5.0
@export var sprint_speed := 8.0
@export var jump_velocity := 4.5
@export var mouse_sensitivity := 0.002
var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity")
@onready var head: Node3D = $Head
@onready var camera: Camera3D = $Head/Camera3D
func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
rotate_y(-event.relative.x * mouse_sensitivity)
head.rotate_x(-event.relative.y * mouse_sensitivity)
head.rotation.x = clampf(head.rotation.x, deg_to_rad(-89), deg_to_rad(89))
if event.is_action_pressed("ui_cancel"):
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y -= gravity * delta
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
var input := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
var direction := (transform.basis * Vector3(input.x, 0, input.y)).normalized()
var current_speed := sprint_speed if Input.is_ac
…29.4 Third-person camera and mouse picking
Other camera helpers: unproject_position(world_pos) (3D → screen, for health bars over enemies), is_position_behind(world_pos).
# SpringArm3D (child of player) with a Camera3D child prevents clipping through walls
@onready var spring_arm: SpringArm3D = $SpringArm3D
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
spring_arm.rotation.y -= event.relative.x * 0.003
spring_arm.rotation.x = clampf(spring_arm.rotation.x - event.relative.y * 0.003, -1.2, 0.4)
# Clicking on 3D objects
func pick(screen_pos: Vector2) -> Dictionary:
var cam := get_viewport().get_camera_3d()
var from := cam.project_ray_origin(screen_pos)
var to := from + cam.project_ray_normal(screen_pos) * 1000.0
var query := PhysicsRayQueryParameters3D.create(from, to)
return get_world_3d().direct_space_state.intersect_ray(query) # call from _physics_process for safety29.5 Procedural geometry
ArrayMesh.add_surface_from_arrays(), ImmediateMesh (debug lines), and MeshDataTool (edit existing meshes) are alternatives.
func build_triangle() -> ArrayMesh:
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
st.set_uv(Vector2(0, 0)); st.add_vertex(Vector3(0, 1, 0))
st.set_uv(Vector2(1, 1)); st.add_vertex(Vector3(1, 0, 0))
st.set_uv(Vector2(0, 1)); st.add_vertex(Vector3(-1, 0, 0))
st.generate_normals()
return st.commit()29.6 3D animation and characters
Imported characters come with an AnimationPlayer; drive them with an AnimationTree (blend spaces for locomotion). Use Skeleton3D, BoneAttachment3D (attach weapons to hands), SkeletonIK3D/SkeletonModifier3D (4.3+) for IK and procedural adjustments, and root motion via AnimationMixer.root_motion_track.