Added gameplay_input manager.

Refactored menus, player, and escape_capsule to utilize new system.
Actions defined in Godot project file with default bindings.
Player/Vehicles now react to actions and GameplayInput states instead of polling Input directly.
They respond to commands via `_handle_gameplay_command` and should not implement _unhandled_input directly.
Kept some debugging input handling code in `player.gd` for things like F3/F5/F4/PageUp/PageDown.
We may want to move much of this out of `player.gd` into something that handles the gamestate more generally.
This commit is contained in:
BadQuanta
2026-10-06 02:19:50 +00:00
parent ed63221996
commit 1e8ebb7567
9 changed files with 751 additions and 257 deletions
+89 -133
View File
@@ -15,7 +15,6 @@ extends CharacterBody3D
var player_inside: bool = false
var player_ref: CharacterBody3D = null
var using_third_person: bool = false
var f_was_down: bool = false
var mass: float = 6500.0
var thrust_force: float = 180000.0
@@ -64,11 +63,12 @@ var _hatch_crew_open: bool = false
var _hatch_dock_open: bool = false
var _ladder_out: bool = false
var _cabin_dim: bool = false
var _v_was: bool = false
var _thrust_input: float = 0.0
func _ready() -> void:
_ensure_actions()
GameplayInput.command_requested.connect(_handle_vehicle_command)
GameplayInput.transient_input_cleared.connect(_clear_vehicle_input)
if interaction_area:
interaction_area.monitoring = true
interaction_area.monitorable = true
@@ -193,7 +193,7 @@ func _gravity_accel() -> Vector3:
func _update_telemetry() -> void:
telem_speed = velocity.length()
telem_thrust = Input.is_key_pressed(KEY_SPACE)
telem_thrust = _thrust_input > 0.0
var earth := _earth()
if earth:
telem_earth_rng = maxf(0.0, global_position.distance_to(earth.global_position) - EARTH_RADIUS)
@@ -262,7 +262,7 @@ func _on_body_entered(body: Node3D) -> void:
if body.is_in_group("player"):
player_ref = body
if interact_prompt and not player_inside:
interact_prompt.text = "Press F to Enter"
interact_prompt.text = "[%s] Enter" % GameplayInput.binding_label(&"interact")
interact_prompt.visible = true
@@ -273,103 +273,61 @@ func _on_body_exited(body: Node3D) -> void:
interact_prompt.visible = false
func _unhandled_input(event: InputEvent) -> void:
if not player_inside:
## Handles semantic vehicle commands; proximity permits entry only on foot.
func _handle_vehicle_command(action: StringName, command_context: int) -> void:
if command_context != GameplayInput.context:
return
if event.is_action_pressed("ui_cancel"):
if pause_menu and pause_menu.has_method("open_pause") and player_ref:
pause_menu.open_pause(player_ref)
get_viewport().set_input_as_handled()
if action == &"interact":
if player_inside:
exit_capsule()
elif player_ref and interaction_area.overlaps_body(player_ref) and GameplayInput.context == GameplayInput.Context.ON_FOOT:
enter_capsule()
return
if _event_toggles(event, "capsule_gear", KEY_G):
_gear_down = not _gear_down
_set_hull("gear_deploy", 1.0 if _gear_down else 0.0)
get_viewport().set_input_as_handled()
elif _event_toggles(event, "capsule_hatch_crew", KEY_H):
_hatch_crew_open = not _hatch_crew_open
_set_hull("hatch_crew_open", 1.0 if _hatch_crew_open else 0.0)
get_viewport().set_input_as_handled()
elif _event_toggles(event, "capsule_hatch_dock", KEY_J):
_hatch_dock_open = not _hatch_dock_open
_set_hull("hatch_dock_open", 1.0 if _hatch_dock_open else 0.0)
get_viewport().set_input_as_handled()
elif _event_toggles(event, "capsule_ladder", KEY_L):
_ladder_out = not _ladder_out
_set_hull("hatch_ladder_deploy", 1.0 if _ladder_out else 0.0)
get_viewport().set_input_as_handled()
elif _event_toggles(event, "capsule_instruments", KEY_I):
_set_hull("instruments_demo", 1.0)
get_viewport().set_input_as_handled()
elif _event_toggles(event, "capsule_cabin_light", KEY_K):
_cabin_dim = not _cabin_dim
_set_hull("cabin_dim", _cabin_dim)
get_viewport().set_input_as_handled()
if not player_inside or GameplayInput.context != GameplayInput.Context.VEHICLE:
return
match action:
&"toggle_view":
toggle_camera()
&"vehicle_toggle_gear":
_gear_down = not _gear_down
_set_hull("gear_deploy", 1.0 if _gear_down else 0.0)
&"vehicle_toggle_crew_hatch":
_hatch_crew_open = not _hatch_crew_open
_set_hull("hatch_crew_open", 1.0 if _hatch_crew_open else 0.0)
&"vehicle_toggle_dock_hatch":
_hatch_dock_open = not _hatch_dock_open
_set_hull("hatch_dock_open", 1.0 if _hatch_dock_open else 0.0)
&"capsule_toggle_ladder":
_ladder_out = not _ladder_out
_set_hull("hatch_ladder_deploy", 1.0 if _ladder_out else 0.0)
&"vehicle_activate_instruments":
_set_hull("instruments_demo", 1.0)
&"vehicle_toggle_cabin_lights":
_cabin_dim = not _cabin_dim
_set_hull("cabin_dim", _cabin_dim)
func _ensure_actions() -> void:
var binds := {
"interact": KEY_F,
"toggle_first_person": KEY_V,
"capsule_gear": KEY_G,
"capsule_hatch_crew": KEY_H,
"capsule_hatch_dock": KEY_J,
"capsule_ladder": KEY_L,
"capsule_instruments": KEY_I,
"capsule_cabin_light": KEY_K,
}
for action in binds.keys():
if InputMap.has_action(action):
continue
InputMap.add_action(action)
var ev := InputEventKey.new()
ev.physical_keycode = binds[action]
InputMap.action_add_event(action, ev)
func _event_toggles(event: InputEvent, action: String, key: Key) -> bool:
if InputMap.has_action(action) and event.is_action_pressed(action):
return true
if event is InputEventKey and event.pressed and not event.echo and event.physical_keycode == key:
return true
return false
func _act_pressed(action: String) -> bool:
return InputMap.has_action(action) and Input.is_action_pressed(action)
func _act_just(action: String) -> bool:
return InputMap.has_action(action) and Input.is_action_just_pressed(action)
## Clears held cockpit look and visual thrust on menus, handoffs, or focus loss.
func _clear_vehicle_input() -> void:
free_look = false
cockpit_yaw = 0.0
cockpit_pitch = 0.0
_thrust_input = 0.0
if player_inside:
_set_hull("abort_burn", false)
_set_hull("att_pitch", 0.0)
_set_hull("att_yaw", 0.0)
_set_hull("att_roll", 0.0)
## Updates active vehicle input/cameras while retaining the original flight timing.
func _process(delta: float) -> void:
var interact_now := _act_just("interact") or _just_key(KEY_F)
if not player_inside and player_ref and interact_now:
f_was_down = true
enter_capsule()
return
if not player_inside:
f_was_down = Input.is_physical_key_pressed(KEY_F)
if player_ref and interact_prompt:
interact_prompt.text = "[%s] Enter" % GameplayInput.binding_label(&"interact")
_update_crust_exception()
return
if interact_now and not f_was_down:
f_was_down = true
exit_capsule()
return
f_was_down = _act_pressed("interact") or Input.is_physical_key_pressed(KEY_F)
if _act_just("toggle_first_person"):
toggle_camera()
elif not InputMap.has_action("toggle_first_person") and _just_key(KEY_V):
toggle_camera()
var was_free = free_look
free_look = Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)
if was_free and not free_look and not using_third_person:
cockpit_yaw = 0.0
cockpit_pitch = 0.0
if Input.is_key_pressed(KEY_ALT):
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
else:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
_update_vehicle_look(delta)
handle_flight(delta)
_update_telemetry()
_update_flight_hud()
@@ -383,30 +341,38 @@ func _process(delta: float) -> void:
capsule_camera.make_current()
func _just_key(key: Key) -> bool:
var down := Input.is_physical_key_pressed(key)
if key == KEY_V:
var edge := down and not _v_was
_v_was = down
return edge
return false
## Updates cockpit/orbit look without letting menu input reach the vehicle camera.
func _update_vehicle_look(delta: float) -> void:
var cursor_released := GameplayInput.is_held(&"vehicle_release_cursor")
if GameplayInput.gameplay_enabled():
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE if cursor_released else Input.MOUSE_MODE_CAPTURED
var previous_freelook := free_look
free_look = GameplayInput.is_held(&"vehicle_freelook")
if previous_freelook and not free_look and not using_third_person:
cockpit_yaw = 0.0
cockpit_pitch = 0.0
var mouse_displacement := GameplayInput.consume_mouse_displacement()
if cursor_released:
return
var mouse_turn := mouse_displacement * look_sensitivity
var controller_turn := GameplayInput.read_controller_look(delta)
if using_third_person:
_apply_vehicle_look(mouse_turn + controller_turn)
else:
_apply_vehicle_look((mouse_turn if free_look else Vector2.ZERO) + controller_turn)
func _input(event: InputEvent) -> void:
if not player_inside:
return
if Input.is_key_pressed(KEY_ALT):
return
if event is InputEventMouseMotion:
if using_third_person:
orbit_yaw -= event.relative.x * look_sensitivity
orbit_pitch += event.relative.y * look_sensitivity
orbit_pitch = clampf(orbit_pitch, orbit_pitch_min, orbit_pitch_max)
elif free_look:
cockpit_yaw -= event.relative.x * look_sensitivity
cockpit_pitch -= event.relative.y * look_sensitivity
cockpit_yaw = clamp(cockpit_yaw, -cockpit_look_limit_yaw, cockpit_look_limit_yaw)
cockpit_pitch = clamp(cockpit_pitch, -cockpit_look_limit_down, cockpit_look_limit_up)
## Applies angular displacement in radians while preserving existing camera limits.
func _apply_vehicle_look(look_delta: Vector2) -> void:
if using_third_person:
orbit_yaw -= look_delta.x
orbit_pitch += look_delta.y
orbit_pitch = clampf(orbit_pitch, orbit_pitch_min, orbit_pitch_max)
else:
cockpit_yaw -= look_delta.x
cockpit_pitch -= look_delta.y
cockpit_yaw = clampf(cockpit_yaw, -cockpit_look_limit_yaw, cockpit_look_limit_yaw)
cockpit_pitch = clampf(cockpit_pitch, -cockpit_look_limit_down, cockpit_look_limit_up)
func enter_capsule() -> void:
@@ -436,7 +402,6 @@ func enter_capsule() -> void:
orbit_yaw = 0.0
orbit_pitch = 0.4
using_third_person = false
f_was_down = true
free_look = false
if camera_pivot:
camera_pivot.top_level = false
@@ -454,6 +419,7 @@ func enter_capsule() -> void:
if flight_label:
flight_label.visible = true
_update_crust_exception()
GameplayInput.set_context(GameplayInput.Context.VEHICLE)
func exit_capsule() -> void:
@@ -463,7 +429,6 @@ func exit_capsule() -> void:
player_inside = false
velocity = Vector3.ZERO
free_look = false
f_was_down = false
using_third_person = false
_set_hull("abort_burn", false)
_set_hull("rcs_direction", 0)
@@ -504,6 +469,7 @@ func exit_capsule() -> void:
FloatingOrigin.set_focus(exiting_player)
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
player_ref = null
GameplayInput.set_context(GameplayInput.Context.ON_FOOT)
func toggle_camera() -> void:
@@ -569,27 +535,17 @@ func update_third_person_camera() -> void:
third_person_camera.global_basis = cam_basis
## Reads signed flight intent while preserving the existing flight equations/timing.
func handle_flight(delta: float) -> void:
_update_crust_exception()
var input_pitch := 0.0
var input_roll := 0.0
var input_yaw := 0.0
if Input.is_key_pressed(KEY_W):
input_pitch -= 1.0
if Input.is_key_pressed(KEY_S):
input_pitch += 1.0
if Input.is_key_pressed(KEY_A):
input_yaw += 1.0
if Input.is_key_pressed(KEY_D):
input_yaw -= 1.0
if Input.is_key_pressed(KEY_Q):
input_roll -= 1.0
if Input.is_key_pressed(KEY_E):
input_roll += 1.0
var input_pitch := GameplayInput.read_axis(&"vehicle_pitch_up", &"vehicle_pitch_down")
var input_roll := GameplayInput.read_axis(&"vehicle_roll_left", &"vehicle_roll_right")
var input_yaw := GameplayInput.read_axis(&"vehicle_yaw_right", &"vehicle_yaw_left")
rotate_object_local(Vector3.RIGHT, input_pitch * rotation_speed * delta)
rotate_object_local(Vector3.FORWARD, -input_yaw * rotation_speed * delta)
rotate_object_local(Vector3.UP, input_roll * rotation_speed * delta)
var thrusting := Input.is_key_pressed(KEY_SPACE)
_thrust_input = GameplayInput.read_strength(&"vehicle_thrust")
var thrusting := _thrust_input > 0.0
_set_hull("abort_burn", thrusting)
_set_hull("att_pitch", input_pitch)
_set_hull("att_yaw", input_yaw)
@@ -597,7 +553,7 @@ func handle_flight(delta: float) -> void:
_set_hull("rcs_direction", 0)
var acceleration: Vector3 = _gravity_accel()
if thrusting:
acceleration += global_transform.basis.y * (thrust_force / mass)
acceleration += global_transform.basis.y * (thrust_force / mass) * _thrust_input
velocity += acceleration * delta
var near_surface := is_on_floor() or _crust_exception
if not near_surface: