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.x

29.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 safety

29.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.