WIP: add Camera3D, fix jump/forward-direction bugs, add mouse capture, build game.tscn composition

This commit is contained in:
claude-checkpoint-tooling
2026-08-21 00:39:13 +00:00
parent e2a77b055f
commit 7790670559
4 changed files with 76 additions and 25 deletions
+41 -12
View File
@@ -2,9 +2,9 @@ class_name Player
extends CharacterBody3D
## The player character controller.
##
## A simple CharacterBody3D with WASD/arrow locomotion, gravity, and a
## first-person Camera3D. Reads the spawn point from the current scene's
## `PlayerSpawn` Marker3D (see docs/architecture/pc2_test_level.md §3).
## A simple CharacterBody3D with WASD/arrow locomotion, gravity, jump, and a
## first-person Camera3D with mouse-look. Reads the spawn point from the
## current scene's `PlayerSpawn` Marker3D (see docs/architecture/pc2_test_level.md §3).
##
## No networking, no chunk code, no avatar UI — PC2 is local-only.
@@ -14,19 +14,29 @@ extends CharacterBody3D
# ---------------------------------------------------------------------------
const WALK_SPEED: float = 5.0
const JUMP_VELOCITY: float = -4.5
const JUMP_VELOCITY: float = 4.5
const GRAVITY: float = 9.8
## Mouse-look sensitivity (radians per pixel of relative motion).
const MOUSE_SENSITIVITY: float = 0.003
## Camera pitch is clamped so the player can't flip past looking straight
## up/down (which would otherwise let mouse-look invert on itself).
const PITCH_LIMIT: float = deg_to_rad(89.0)
# ---------------------------------------------------------------------------
# Input state
# ---------------------------------------------------------------------------
var _input_direction := Vector2.ZERO
var _camera_pitch: float = 0.0
@onready var _camera: Camera3D = $Camera3D
# ---------------------------------------------------------------------------
# _ready — spawn at PlayerSpawn marker
# _ready — spawn at PlayerSpawn marker, capture the mouse for look
# ---------------------------------------------------------------------------
func _ready() -> void:
@@ -52,6 +62,11 @@ func _ready() -> void:
push_warning("player.gd: no PlayerSpawn marker found; spawning at origin")
global_position = Vector3(0, 1.0, 0)
# Capture the mouse for FPS-style look. Headless/test runs have no real
# window to capture input for, so guard against that rather than error.
if DisplayServer.get_name() != "headless":
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
# ---------------------------------------------------------------------------
# _unhandled_input — capture keyboard / mouse for movement
@@ -60,11 +75,18 @@ func _ready() -> void:
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventKey:
var key := event as InputEventKey
if key.pressed:
if key.pressed and not key.echo:
_update_input_direction(key)
else:
elif not key.pressed:
_clear_input_direction(key)
elif event is InputEventMouseMotion:
# Release the mouse so the player can get back to a menu/cursor.
if key.pressed and key.keycode == KEY_ESCAPE:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
elif event is InputEventMouseButton and event.pressed:
# Clicking back into the viewport re-captures the mouse.
if Input.mouse_mode == Input.MOUSE_MODE_VISIBLE and DisplayServer.get_name() != "headless":
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
elif event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
_handle_mouse_look(event as InputEventMouseMotion)
@@ -93,7 +115,12 @@ func _clear_input_direction(key: InputEventKey) -> void:
func _handle_mouse_look(event: InputEventMouseMotion) -> void:
rotation.y -= event.relative.x * 0.002
# Yaw rotates the whole body (so movement direction turns with the
# look direction); pitch only tilts the camera, so the capsule itself
# never tips over.
rotation.y -= event.relative.x * MOUSE_SENSITIVITY
_camera_pitch = clampf(_camera_pitch - event.relative.y * MOUSE_SENSITIVITY, -PITCH_LIMIT, PITCH_LIMIT)
_camera.rotation.x = _camera_pitch
# ---------------------------------------------------------------------------
@@ -108,10 +135,12 @@ func _physics_process(delta: float) -> void:
# Jump.
if is_on_floor() and Input.is_action_just_pressed("ui_accept"):
velocity.y = JUMP_VELOCITY
return
# Build movement direction from input.
var direction := (_input_direction.x * transform.basis.x +
# Build movement direction from input. Godot's local "forward" is -Z, not
# +Z, so W (input.y = +1) must use -basis.z -- using +basis.z here would
# move the player backward when pressing forward (confirmed by reading
# Camera3D/Node3D's own convention: a node looks down its local -Z).
var direction := (_input_direction.x * transform.basis.x -
_input_direction.y * transform.basis.z).normalized()
velocity.x = direction.x * WALK_SPEED
velocity.z = direction.z * WALK_SPEED