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). ## ## No networking, no chunk code, no avatar UI — PC2 is local-only. # --------------------------------------------------------------------------- # Movement constants # --------------------------------------------------------------------------- const WALK_SPEED: float = 5.0 const JUMP_VELOCITY: float = -4.5 const GRAVITY: float = 9.8 # --------------------------------------------------------------------------- # Input state # --------------------------------------------------------------------------- var _input_direction := Vector2.ZERO # --------------------------------------------------------------------------- # _ready — spawn at PlayerSpawn marker # --------------------------------------------------------------------------- func _ready() -> void: # Per §3 of pc2_test_level.md: look up PlayerSpawn from the current # scene root. The lookup walks the full subtree (recursive=true) so # it finds the marker whether it is a direct child of the level or # nested deeper. # # current_scene is null in headless --script mode and in any scene # before the main scene is fully swapped in, so guard it rather than # blindly dereferencing. var current_scene: Node = get_tree().current_scene var spawn: Node = null if current_scene != null: spawn = current_scene.find_child("PlayerSpawn", true, false) if spawn is Node3D: global_position = (spawn as Node3D).global_position # Align player forward with spawn yaw (optional polish). var spawn_yaw := (spawn as Node3D).rotation.y rotation.y = spawn_yaw else: push_warning("player.gd: no PlayerSpawn marker found; spawning at origin") global_position = Vector3(0, 1.0, 0) # --------------------------------------------------------------------------- # _unhandled_input — capture keyboard / mouse for movement # --------------------------------------------------------------------------- func _unhandled_input(event: InputEvent) -> void: if event is InputEventKey: var key := event as InputEventKey if key.pressed: _update_input_direction(key) else: _clear_input_direction(key) elif event is InputEventMouseMotion: _handle_mouse_look(event as InputEventMouseMotion) func _update_input_direction(key: InputEventKey) -> void: match key.keycode: KEY_W, KEY_UP: _input_direction.y += 1.0 KEY_S, KEY_DOWN: _input_direction.y -= 1.0 KEY_A, KEY_LEFT: _input_direction.x -= 1.0 KEY_D, KEY_RIGHT: _input_direction.x += 1.0 func _clear_input_direction(key: InputEventKey) -> void: match key.keycode: KEY_W, KEY_UP: _input_direction.y -= 1.0 KEY_S, KEY_DOWN: _input_direction.y += 1.0 KEY_A, KEY_LEFT: _input_direction.x += 1.0 KEY_D, KEY_RIGHT: _input_direction.x -= 1.0 func _handle_mouse_look(event: InputEventMouseMotion) -> void: rotation.y -= event.relative.x * 0.002 # --------------------------------------------------------------------------- # Physics — gravity, jump, move_and_slide # --------------------------------------------------------------------------- func _physics_process(delta: float) -> void: # Apply gravity. if not is_on_floor(): velocity.y -= GRAVITY * delta # 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 + _input_direction.y * transform.basis.z).normalized() velocity.x = direction.x * WALK_SPEED velocity.z = direction.z * WALK_SPEED move_and_slide()