Refine movement, water recovery, and UI baseline

This commit is contained in:
Alexander Sellite 2026-07-25 17:04:39 -04:00
parent d2fe367891
commit e2067e3bf4
23 changed files with 905 additions and 146 deletions

View file

@ -0,0 +1,46 @@
class_name PlayerWaterTrigger
extends Area3D
signal recovery_requested(player: Player, surface_height: float)
@export var surface_height: float = 0.2
@export_range(0.0, 2.0, 0.05) var entry_depth_threshold: float = 0.35
var _tracked_players: Array[Player] = []
var _triggered_players: Dictionary[StringName, bool] = {}
func _ready() -> void:
body_entered.connect(_on_body_entered)
body_exited.connect(_on_body_exited)
func _physics_process(_delta: float) -> void:
for player: Player in _tracked_players.duplicate():
if not is_instance_valid(player):
_tracked_players.erase(player)
continue
var player_key: StringName = StringName(str(player.get_instance_id()))
if _triggered_players.has(player_key):
continue
if (
player.get_body_center_position().y
<= surface_height - entry_depth_threshold
):
_triggered_players[player_key] = true
recovery_requested.emit(player, surface_height)
func _on_body_entered(body: Node3D) -> void:
var player: Player = body as Player
if player == null or player in _tracked_players:
return
_tracked_players.append(player)
func _on_body_exited(body: Node3D) -> void:
var player: Player = body as Player
if player == null:
return
_tracked_players.erase(player)
_triggered_players.erase(StringName(str(player.get_instance_id())))

View file

@ -0,0 +1 @@
uid://dnlbnlimgxoc2

View file

@ -0,0 +1,9 @@
class_name SafeRespawnPoint
extends Marker3D
@export var enabled: bool = true
func get_horizontal_distance_squared(world_position: Vector3) -> float:
var offset: Vector3 = global_position - world_position
return Vector2(offset.x, offset.z).length_squared()

View file

@ -0,0 +1 @@
uid://df2lsduguegxh

18
world/test_world.gd Normal file
View file

@ -0,0 +1,18 @@
class_name TestWorld
extends Node3D
@onready var _water_trigger: PlayerWaterTrigger = %PlayerWaterTrigger
@onready var _safe_respawn_points: Node3D = %SafeRespawnPoints
func get_player_water_trigger() -> PlayerWaterTrigger:
return _water_trigger
func get_safe_respawn_points() -> Array[SafeRespawnPoint]:
var points: Array[SafeRespawnPoint] = []
for child: Node in _safe_respawn_points.get_children():
var point: SafeRespawnPoint = child as SafeRespawnPoint
if point != null:
points.append(point)
return points

1
world/test_world.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://bclc786v5xw8c

View file

@ -1,7 +1,10 @@
[gd_scene load_steps=14 format=3]
[gd_scene load_steps=18 format=3]
[ext_resource type="Script" path="res://world/fishable_water_region.gd" id="1_water_region"]
[ext_resource type="Resource" path="res://fish/pools/test_water_pool.tres" id="2_fish_pool"]
[ext_resource type="Script" path="res://world/test_world.gd" id="3_world"]
[ext_resource type="Script" path="res://world/player_water_trigger.gd" id="4_water_trigger"]
[ext_resource type="Script" path="res://world/safe_respawn_point.gd" id="5_safe_point"]
[sub_resource type="BoxShape3D" id="GroundShape"]
size = Vector3(30, 1, 30)
@ -30,6 +33,12 @@ size = Vector3(40, 0.16, 19.5)
[sub_resource type="BoxShape3D" id="FishableShoreShape"]
size = Vector3(17.5, 0.16, 5.5)
[sub_resource type="BoxShape3D" id="PlayerWaterShape"]
size = Vector3(40, 5, 19.5)
[sub_resource type="BoxShape3D" id="PlayerWaterShoreShape"]
size = Vector3(17.5, 5, 5.5)
[sub_resource type="StandardMaterial3D" id="WaterMaterial"]
transparency = 1
albedo_color = Color(0.1, 0.45, 0.7, 0.72)
@ -43,6 +52,7 @@ ambient_light_color = Color(0.65, 0.72, 0.8, 1)
ambient_light_energy = 0.65
[node name="TestWorld" type="Node3D"]
script = ExtResource("3_world")
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment")
@ -94,3 +104,41 @@ shape = SubResource("FishableShoreShape")
[node name="ShoreRight" type="CollisionShape3D" parent="FishableWater"]
position = Vector3(11.25, 0, 12.5)
shape = SubResource("FishableShoreShape")
[node name="PlayerWaterTrigger" type="Area3D" parent="."]
unique_name_in_owner = true
position = Vector3(0, -2.3, -29.75)
collision_layer = 8
collision_mask = 2
script = ExtResource("4_water_trigger")
surface_height = 0.2
[node name="DeepWater" type="CollisionShape3D" parent="PlayerWaterTrigger"]
shape = SubResource("PlayerWaterShape")
[node name="ShoreLeft" type="CollisionShape3D" parent="PlayerWaterTrigger"]
position = Vector3(-11.25, 0, 12.5)
shape = SubResource("PlayerWaterShoreShape")
[node name="ShoreRight" type="CollisionShape3D" parent="PlayerWaterTrigger"]
position = Vector3(11.25, 0, 12.5)
shape = SubResource("PlayerWaterShoreShape")
[node name="SafeRespawnPoints" type="Node3D" parent="."]
unique_name_in_owner = true
[node name="DockApproach" type="Marker3D" parent="SafeRespawnPoints"]
position = Vector3(0, 0.58, -9)
script = ExtResource("5_safe_point")
[node name="WestShore" type="Marker3D" parent="SafeRespawnPoints"]
position = Vector3(-11.5, 0.58, -11.5)
script = ExtResource("5_safe_point")
[node name="EastShore" type="Marker3D" parent="SafeRespawnPoints"]
position = Vector3(11.5, 0.58, -11.5)
script = ExtResource("5_safe_point")
[node name="DockEnd" type="Marker3D" parent="SafeRespawnPoints"]
position = Vector3(0, 1.08, -17)
script = ExtResource("5_safe_point")

View file

@ -0,0 +1,149 @@
class_name WaterRecoveryController
extends Node
enum RecoveryState {
IDLE,
BOBBING,
FADING_OUT,
FADING_IN,
}
@export_range(-1.0, 1.0, 0.01) var water_center_offset: float = 0.0
@export_range(0.0, 0.5, 0.005) var bob_amplitude: float = 0.08
@export_range(0.1, 10.0, 0.1) var bob_speed: float = 2.4
@export_range(0.0, 5.0, 0.05) var pre_fade_bob_duration: float = 1.0
@export_range(0.0, 1.0, 0.01) var respawn_height_offset: float = 0.08
var state: RecoveryState = RecoveryState.IDLE
var _player: Player
var _fishing_spot: FishingSpot
var _game_ui: GameUI
var _screen_fade: ScreenFade
var _water_trigger: PlayerWaterTrigger
var _safe_points: Array[SafeRespawnPoint] = []
var _initial_spawn_transform: Transform3D
var _entry_position: Vector3
var _bob_base_position: Vector3
var _bob_elapsed: float = 0.0
var _prior_movement_enabled: bool = true
var _prior_camera_input_enabled: bool = true
var _generation: int = 0
func setup(
player: Player,
fishing_spot: FishingSpot,
game_ui: GameUI,
screen_fade: ScreenFade,
water_trigger: PlayerWaterTrigger,
safe_points: Array[SafeRespawnPoint],
) -> void:
_player = player
_fishing_spot = fishing_spot
_game_ui = game_ui
_screen_fade = screen_fade
_water_trigger = water_trigger
_safe_points = safe_points
_initial_spawn_transform = player.global_transform
if not _water_trigger.recovery_requested.is_connected(
_on_recovery_requested
):
_water_trigger.recovery_requested.connect(_on_recovery_requested)
if not _screen_fade.transition_completed.is_connected(
_on_fade_transition_completed
):
_screen_fade.transition_completed.connect(
_on_fade_transition_completed
)
func _process(delta: float) -> void:
if state != RecoveryState.BOBBING or _player == null:
return
_bob_elapsed += delta
var bob_offset: float = sin(_bob_elapsed * bob_speed * TAU) * bob_amplitude
_player.global_position = _bob_base_position + Vector3.UP * bob_offset
if _bob_elapsed >= pre_fade_bob_duration:
state = RecoveryState.FADING_OUT
_screen_fade.fade_to_black(_generation)
func _exit_tree() -> void:
_generation += 1
if _screen_fade != null and is_instance_valid(_screen_fade):
_screen_fade.reset_immediately()
if _player != null and is_instance_valid(_player):
_player.set_water_recovery_active(false)
_player.set_movement_enabled(_prior_movement_enabled)
_player.set_camera_input_enabled(_prior_camera_input_enabled)
state = RecoveryState.IDLE
func _on_recovery_requested(
triggered_player: Player,
surface_height: float,
) -> void:
if (
state != RecoveryState.IDLE
or triggered_player != _player
or not is_instance_valid(triggered_player)
):
return
_generation += 1
_entry_position = _player.global_position
_game_ui.close_player_menu()
_fishing_spot.begin_water_recovery()
_prior_movement_enabled = _player.is_movement_enabled()
_prior_camera_input_enabled = _player.is_camera_input_enabled()
_player.set_movement_enabled(false)
_player.set_camera_input_enabled(false)
_player.set_water_recovery_active(true)
_bob_base_position = _player.global_position
_bob_base_position.y = (
surface_height
+ water_center_offset
- _player.get_body_center_height()
)
_player.global_position = _bob_base_position
_bob_elapsed = 0.0
state = RecoveryState.BOBBING
func _on_fade_transition_completed(
generation: int,
faded_to_black: bool,
) -> void:
if generation != _generation:
return
if faded_to_black and state == RecoveryState.FADING_OUT:
_respawn_player()
state = RecoveryState.FADING_IN
_screen_fade.fade_from_black(_generation)
elif not faded_to_black and state == RecoveryState.FADING_IN:
_finish_recovery()
func _respawn_player() -> void:
var target_transform: Transform3D = _initial_spawn_transform
var nearest_distance: float = INF
for point: SafeRespawnPoint in _safe_points:
if point == null or not is_instance_valid(point) or not point.enabled:
continue
var distance: float = point.get_horizontal_distance_squared(
_entry_position
)
if distance < nearest_distance:
nearest_distance = distance
target_transform = point.global_transform
target_transform.origin.y += respawn_height_offset
_player.global_transform = target_transform
_player.velocity = Vector3.ZERO
func _finish_recovery() -> void:
_player.set_water_recovery_active(false)
_player.set_movement_enabled(_prior_movement_enabled)
_player.set_camera_input_enabled(_prior_camera_input_enabled)
_fishing_spot.end_water_recovery()
state = RecoveryState.IDLE
_bob_elapsed = 0.0

View file

@ -0,0 +1 @@
uid://dvv0ublaoj4gr