Scenery (all procedural, seeded, no imported assets): - Terrain shader: grass/dry grass/dirt/sand/rock strata/snow + detail normals - 5 tree types, faceted mossy rocks, cliff outcrops, sea stacks, bushes, logs and stumps, batched as MultiMesh per 48 m cell - Wind-animated grass and flowers streamed in tiles around the player - Lake (depth-shaded water with shore foam), ocean to the horizon, cloud sky shader, volumetric fog, glow, softer shadows - Swimming: float at the surface, swim-jump to climb out World bounds: - Steep mountain rim with an invisible wall partway up; one side opens to a sheer sea cliff with the wall 1 m inside the edge; whole chunk is an island so the mesh edge is never visible - Respawn safety net if the player leaves the walkable area or the map - Fix: the cliff edge now tracks the wall in the headlands (it used to drop away inside the wall, leaving a pit with lake water hanging over it) Tests: tests/test_boundary.gd (24-direction escape attempt) and tests/test_terrain_edges.gd (no ground below waterline near the wall). Upgrades the project to Godot 4.7 (project.godot rewritten by the 4.7 importer, .uid files added) and documents it in the setup guide.
66 lines
2.7 KiB
GDScript
66 lines
2.7 KiB
GDScript
extends CharacterBody3D
|
|
## First-person player controller.
|
|
##
|
|
## Move (WASD), look (mouse), jump (space), sprint (shift). Gravity + ground
|
|
## collision via CharacterBody3D. In deep water the player floats with their
|
|
## head above the surface and moves at swimming speed. Deliberately minimal: no
|
|
## stamina/health yet (M1), no network ownership yet (M2). Press ESC to free the
|
|
## mouse, click to recapture.
|
|
|
|
@export var speed: float = 5.0
|
|
@export var sprint_speed: float = 8.0
|
|
@export var swim_speed: float = 2.6
|
|
@export var jump_velocity: float = 5.5
|
|
@export var mouse_sensitivity: float = 0.0025
|
|
## Set by world.gd from the terrain. Feet this far below the surface = swimming.
|
|
@export var water_level: float = -INF
|
|
const FLOAT_DEPTH := 1.3
|
|
|
|
@onready var camera: Camera3D = $Camera3D
|
|
|
|
var _gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity", 9.8)
|
|
|
|
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)
|
|
camera.rotate_x(-event.relative.y * mouse_sensitivity)
|
|
camera.rotation.x = clamp(camera.rotation.x, -1.4, 1.4)
|
|
elif event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
elif event is InputEventMouseButton and event.pressed:
|
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
var float_y := water_level - FLOAT_DEPTH
|
|
var swimming := global_position.y < float_y + 0.05 and not is_on_floor()
|
|
|
|
if global_position.y < float_y:
|
|
# Buoyancy: ease back up to floating height.
|
|
velocity.y = move_toward(velocity.y, clampf((float_y - global_position.y) * 4.0, -3.0, 3.0), 20.0 * delta)
|
|
elif not is_on_floor():
|
|
velocity.y -= _gravity * delta
|
|
|
|
if Input.is_action_just_pressed("jump") and is_on_floor():
|
|
velocity.y = jump_velocity
|
|
elif Input.is_action_just_pressed("jump") and global_position.y > float_y - 0.4 and global_position.y < float_y + 0.3:
|
|
# Swim-jump: kick up out of the water to climb onto a bank.
|
|
velocity.y = jump_velocity
|
|
|
|
var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
|
var direction := (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
|
|
|
|
var current_speed := sprint_speed if Input.is_key_pressed(KEY_SHIFT) else speed
|
|
if swimming:
|
|
current_speed = swim_speed
|
|
if direction:
|
|
velocity.x = direction.x * current_speed
|
|
velocity.z = direction.z * current_speed
|
|
else:
|
|
velocity.x = move_toward(velocity.x, 0, current_speed)
|
|
velocity.z = move_toward(velocity.z, 0, current_speed)
|
|
|
|
move_and_slide()
|