Implement production vertical slice foundation
This commit is contained in:
1 parent
9d4a89f355
commit
a18e82eb44
104 files changed
+10595
-268
No files matched your search
@@ -0,0 +1,279 @@
|
||||
class_name ProductionInputAdapter
|
||||
extends Node
|
||||
|
||||
signal pause_requested
|
||||
signal rebind_result_available(result: RebindResult)
|
||||
|
||||
const VehicleCommand = preload("res://game/application/vehicle/vehicle_command.gd")
|
||||
const CameraLookCommand = preload("res://game/application/vehicle/camera_look_command.gd")
|
||||
const InputBindingSnapshot = preload(
|
||||
"res://game/application/input/input_binding_snapshot.gd"
|
||||
)
|
||||
const RebindResult = preload("res://game/application/input/rebind_result.gd")
|
||||
|
||||
const ACTION_FORWARD := &"vehicle_forward"
|
||||
const ACTION_BACKWARD := &"vehicle_backward"
|
||||
const ACTION_LEFT := &"vehicle_left"
|
||||
const ACTION_RIGHT := &"vehicle_right"
|
||||
const ACTION_ASCEND := &"vehicle_ascend"
|
||||
const ACTION_DESCEND := &"vehicle_descend"
|
||||
const ACTION_HIGH_THRUST := &"vehicle_high_thrust"
|
||||
const ACTION_EJECT := &"dive_eject"
|
||||
const ACTION_RECENTER_CAMERA := &"camera_recenter"
|
||||
const ACTION_PAUSE := &"pause_game"
|
||||
|
||||
const BINDABLE_ACTIONS: Array[StringName] = [
|
||||
ACTION_FORWARD,
|
||||
ACTION_BACKWARD,
|
||||
ACTION_LEFT,
|
||||
ACTION_RIGHT,
|
||||
ACTION_ASCEND,
|
||||
ACTION_DESCEND,
|
||||
ACTION_HIGH_THRUST,
|
||||
ACTION_EJECT,
|
||||
ACTION_RECENTER_CAMERA,
|
||||
]
|
||||
|
||||
const ACTION_LABELS := {
|
||||
ACTION_FORWARD: "Avancer",
|
||||
ACTION_BACKWARD: "Reculer",
|
||||
ACTION_LEFT: "Gouverner à gauche",
|
||||
ACTION_RIGHT: "Gouverner à droite",
|
||||
ACTION_ASCEND: "Monter",
|
||||
ACTION_DESCEND: "Descendre",
|
||||
ACTION_HIGH_THRUST: "Forte poussée",
|
||||
ACTION_EJECT: "Éjection maintenue",
|
||||
ACTION_RECENTER_CAMERA: "Recentrer le regard",
|
||||
}
|
||||
|
||||
const DEFAULT_KEYS := {
|
||||
ACTION_FORWARD: KEY_W,
|
||||
ACTION_BACKWARD: KEY_S,
|
||||
ACTION_LEFT: KEY_A,
|
||||
ACTION_RIGHT: KEY_D,
|
||||
ACTION_ASCEND: KEY_SPACE,
|
||||
ACTION_DESCEND: KEY_CTRL,
|
||||
ACTION_HIGH_THRUST: KEY_SHIFT,
|
||||
ACTION_EJECT: KEY_X,
|
||||
ACTION_RECENTER_CAMERA: KEY_R,
|
||||
ACTION_PAUSE: KEY_ESCAPE,
|
||||
}
|
||||
|
||||
@export var mouse_sensitivity_radians_per_pixel := 0.0025
|
||||
|
||||
var _gameplay_enabled := false
|
||||
var _look_delta := Vector2.ZERO
|
||||
var _rebind_action_id: StringName = &""
|
||||
var _pending_key_event: InputEventKey
|
||||
var _pending_conflicting_action_id: StringName = &""
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
_ensure_default_actions()
|
||||
|
||||
|
||||
func set_gameplay_enabled(enabled: bool) -> void:
|
||||
_gameplay_enabled = enabled
|
||||
_look_delta = Vector2.ZERO
|
||||
if enabled:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
else:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
|
||||
func sample_vehicle_command(sequence: int) -> VehicleCommand:
|
||||
if not _gameplay_enabled or get_tree().paused:
|
||||
return VehicleCommand.neutral(sequence)
|
||||
return VehicleCommand.new(
|
||||
sequence,
|
||||
Input.get_axis(ACTION_BACKWARD, ACTION_FORWARD),
|
||||
Input.get_axis(ACTION_LEFT, ACTION_RIGHT),
|
||||
Input.get_axis(ACTION_DESCEND, ACTION_ASCEND),
|
||||
Input.is_action_pressed(ACTION_HIGH_THRUST)
|
||||
)
|
||||
|
||||
|
||||
func take_camera_look_command() -> CameraLookCommand:
|
||||
var command := CameraLookCommand.new(
|
||||
-_look_delta.x * mouse_sensitivity_radians_per_pixel,
|
||||
-_look_delta.y * mouse_sensitivity_radians_per_pixel,
|
||||
_gameplay_enabled and Input.is_action_just_pressed(ACTION_RECENTER_CAMERA)
|
||||
)
|
||||
_look_delta = Vector2.ZERO
|
||||
return command
|
||||
|
||||
|
||||
func ejection_active() -> bool:
|
||||
return (
|
||||
_gameplay_enabled
|
||||
and not get_tree().paused
|
||||
and Input.is_action_pressed(ACTION_EJECT)
|
||||
)
|
||||
|
||||
|
||||
func binding_snapshots() -> Array[InputBindingSnapshot]:
|
||||
var snapshots: Array[InputBindingSnapshot] = []
|
||||
for action_id: StringName in BINDABLE_ACTIONS:
|
||||
snapshots.append(
|
||||
InputBindingSnapshot.new(
|
||||
action_id,
|
||||
String(ACTION_LABELS[action_id]),
|
||||
_binding_text(action_id)
|
||||
)
|
||||
)
|
||||
return snapshots
|
||||
|
||||
|
||||
func begin_rebind(action_id: StringName) -> RebindResult:
|
||||
if action_id not in BINDABLE_ACTIONS:
|
||||
return RebindResult.new(false, action_id, "", &"", "Action inconnue")
|
||||
_rebind_action_id = action_id
|
||||
_pending_key_event = null
|
||||
_pending_conflicting_action_id = &""
|
||||
return RebindResult.new(false, action_id, "", &"", "Appuyez sur une touche")
|
||||
|
||||
|
||||
func resolve_pending_conflict(swap_bindings: bool) -> RebindResult:
|
||||
if _pending_key_event == null or _rebind_action_id.is_empty():
|
||||
return RebindResult.new(false, &"", "", &"", "Aucun conflit en attente")
|
||||
if not swap_bindings:
|
||||
var cancelled_action := _rebind_action_id
|
||||
_clear_pending_rebind()
|
||||
return RebindResult.new(false, cancelled_action, "", &"", "Remappage annulé")
|
||||
|
||||
var target_action := _rebind_action_id
|
||||
var conflicting_action := _pending_conflicting_action_id
|
||||
var previous_target_event := _first_key_event(target_action)
|
||||
_set_single_key_binding(target_action, _pending_key_event)
|
||||
if not conflicting_action.is_empty():
|
||||
if previous_target_event != null:
|
||||
_set_single_key_binding(conflicting_action, previous_target_event)
|
||||
else:
|
||||
InputMap.action_erase_events(conflicting_action)
|
||||
var result := RebindResult.new(
|
||||
true,
|
||||
target_action,
|
||||
_binding_text(target_action),
|
||||
conflicting_action,
|
||||
"Touches permutées"
|
||||
)
|
||||
_clear_pending_rebind()
|
||||
return result
|
||||
|
||||
|
||||
func restore_default_bindings() -> void:
|
||||
for action_id: StringName in BINDABLE_ACTIONS:
|
||||
_set_single_key_binding(action_id, _key_event(int(DEFAULT_KEYS[action_id])))
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not _rebind_action_id.is_empty():
|
||||
_handle_rebind_input(event)
|
||||
return
|
||||
if event.is_action_pressed(ACTION_PAUSE):
|
||||
pause_requested.emit()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if (
|
||||
_gameplay_enabled
|
||||
and not get_tree().paused
|
||||
and event is InputEventMouseMotion
|
||||
):
|
||||
_look_delta += (event as InputEventMouseMotion).relative
|
||||
|
||||
|
||||
func _handle_rebind_input(event: InputEvent) -> void:
|
||||
if not (event is InputEventKey):
|
||||
return
|
||||
var key_event := event as InputEventKey
|
||||
if not key_event.pressed or key_event.echo:
|
||||
return
|
||||
get_viewport().set_input_as_handled()
|
||||
if key_event.physical_keycode == KEY_ESCAPE:
|
||||
var cancelled_action := _rebind_action_id
|
||||
_clear_pending_rebind()
|
||||
rebind_result_available.emit(
|
||||
RebindResult.new(false, cancelled_action, "", &"", "Remappage annulé")
|
||||
)
|
||||
return
|
||||
|
||||
var normalized_event := _key_event(key_event.physical_keycode)
|
||||
var conflicting_action := _find_conflicting_action(
|
||||
_rebind_action_id,
|
||||
normalized_event.physical_keycode
|
||||
)
|
||||
if not conflicting_action.is_empty():
|
||||
_pending_key_event = normalized_event
|
||||
_pending_conflicting_action_id = conflicting_action
|
||||
rebind_result_available.emit(
|
||||
RebindResult.new(
|
||||
false,
|
||||
_rebind_action_id,
|
||||
normalized_event.as_text_physical_keycode(),
|
||||
conflicting_action,
|
||||
"Cette touche est déjà utilisée"
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
_set_single_key_binding(_rebind_action_id, normalized_event)
|
||||
var result := RebindResult.new(
|
||||
true,
|
||||
_rebind_action_id,
|
||||
_binding_text(_rebind_action_id),
|
||||
&"",
|
||||
"Commande mise à jour"
|
||||
)
|
||||
_clear_pending_rebind()
|
||||
rebind_result_available.emit(result)
|
||||
|
||||
|
||||
func _ensure_default_actions() -> void:
|
||||
for action_id: StringName in DEFAULT_KEYS:
|
||||
if not InputMap.has_action(action_id):
|
||||
InputMap.add_action(action_id)
|
||||
if InputMap.action_get_events(action_id).is_empty():
|
||||
InputMap.action_add_event(action_id, _key_event(int(DEFAULT_KEYS[action_id])))
|
||||
|
||||
|
||||
func _find_conflicting_action(
|
||||
target_action: StringName,
|
||||
physical_keycode: Key
|
||||
) -> StringName:
|
||||
for action_id: StringName in BINDABLE_ACTIONS:
|
||||
if action_id == target_action:
|
||||
continue
|
||||
var event := _first_key_event(action_id)
|
||||
if event != null and event.physical_keycode == physical_keycode:
|
||||
return action_id
|
||||
return &""
|
||||
|
||||
|
||||
func _binding_text(action_id: StringName) -> String:
|
||||
var event := _first_key_event(action_id)
|
||||
return event.as_text_physical_keycode() if event != null else "Non assignée"
|
||||
|
||||
|
||||
func _first_key_event(action_id: StringName) -> InputEventKey:
|
||||
for event: InputEvent in InputMap.action_get_events(action_id):
|
||||
if event is InputEventKey:
|
||||
return (event as InputEventKey).duplicate()
|
||||
return null
|
||||
|
||||
|
||||
func _set_single_key_binding(action_id: StringName, event: InputEventKey) -> void:
|
||||
InputMap.action_erase_events(action_id)
|
||||
InputMap.action_add_event(action_id, event)
|
||||
|
||||
|
||||
func _key_event(physical_keycode: Key) -> InputEventKey:
|
||||
var event := InputEventKey.new()
|
||||
event.physical_keycode = physical_keycode
|
||||
return event
|
||||
|
||||
|
||||
func _clear_pending_rebind() -> void:
|
||||
_rebind_action_id = &""
|
||||
_pending_key_event = null
|
||||
_pending_conflicting_action_id = &""
|
||||
Reference in new issue
Block a user