Add prop interaction system and throw mechanics specification

- Introduced `stickman_prop_interactor.gd` to manage prop interactions.
- Created `phase_11_prop_interactions.md` detailing the design and implementation of prop pickup, drop, and throw mechanics.
- Defined interaction states, action lifecycles, and event triggers for prop handling.
- Specified constraints for throw actions, including target positioning and velocity calculations.
- Established acceptance criteria for prop interactions and lifecycle management.
This commit is contained in:
2026-09-25 23:42:26 -04:00
parent 730a9a966b
commit 6a1359f244
22 changed files with 3433 additions and 34 deletions
+27 -3
View File
@@ -9,7 +9,7 @@ const SPEECH_BUBBLE_SCRIPT := preload("res://scripts/stickman_speech_bubble.gd")
const SPEECH_BUBBLE_OFFSET := Vector2(0.0, -640.0)
enum RunnerState { IDLE, EXECUTING }
enum ActionPhase { NONE, WALKING, SPEAKING, WAITING, RAGDOLLING, RECOVERING }
enum ActionPhase { NONE, WALKING, SPEAKING, WAITING, RAGDOLLING, RECOVERING, PROP_INTERACTION }
enum QueueStatus { NOT_STARTED, COMPLETED, INTERRUPTED, ACTIVE, PENDING }
signal action_started(action: Dictionary, index: int)
@@ -29,6 +29,7 @@ var action_queue: Array[Dictionary]:
set_authored_queue(value)
var _rig: StickmanRig = null
var _locomotion: StickmanLocomotion = null
var _prop_interactor: Node = null
var _runner_state: RunnerState = RunnerState.IDLE
var _action_phase: ActionPhase = ActionPhase.NONE
var _current_index: int = -1
@@ -46,11 +47,15 @@ var _completed_ids: Dictionary = {}
var _interrupted_ids: Dictionary = {}
var _paused: bool = false
var _session_started: bool = false
var _prop_action_finished: bool = false
func configure(rig: StickmanRig, locomotion: StickmanLocomotion) -> void:
func configure(rig: StickmanRig, locomotion: StickmanLocomotion, prop_interactor: Node = null) -> void:
_rig = rig
_locomotion = locomotion
_prop_interactor = prop_interactor
if _prop_interactor != null and not _prop_interactor.is_connected("action_completed", _on_prop_action_completed):
_prop_interactor.connect("action_completed", _on_prop_action_completed)
func tick(delta: float, rig_state: int, ragdoll_at_rest: bool) -> void:
@@ -78,6 +83,10 @@ func tick(delta: float, rig_state: int, ragdoll_at_rest: bool) -> void:
ActionPhase.RECOVERING:
if rig_state == StickmanRig.RigState.ANIMATED:
_finish_action()
ActionPhase.PROP_INTERACTION:
if _prop_action_finished:
_prop_action_finished = false
_finish_action()
func speak(text: String, duration: float) -> void:
@@ -358,7 +367,9 @@ func _advance_to_next_action() -> void:
_current_action_id = int(_current_run_item.get("action_id", -1))
_current_index = _index_for_action_id(_current_action_id) if _current_action_id >= 0 else -1
var action: Dictionary = _current_run_item.get("action", {})
if String(action.get("type", "")) == "walk_to" and _rig != null:
var action_type := String(action.get("type", ""))
var requires_animated := action_type == "walk_to" or action_type == "pick_up_prop" or action_type == "drop_prop" or action_type == "throw_prop"
if requires_animated and _rig != null:
if _rig.state == StickmanRig.RigState.RECOVERING:
_run_queue.insert(_run_cursor, _current_run_item.duplicate(true))
_current_run_item.clear()
@@ -394,6 +405,12 @@ func _begin_action(action: Dictionary) -> void:
"recover":
_action_phase = ActionPhase.RECOVERING
recovery_requested.emit()
"pick_up_prop", "drop_prop", "throw_prop":
if _rig == null or not _rig.start_prop_action(action):
_finish_action()
else:
_prop_action_finished = false
_action_phase = ActionPhase.PROP_INTERACTION
_:
push_warning("StickmanActionRunner: unknown action type '%s'; skipped." % String(action.get("type", "")))
_finish_action()
@@ -420,9 +437,12 @@ func _cancel_current_primary(notify: bool = false) -> void:
active_action_cancelled.emit(action.duplicate(true))
if _locomotion != null:
_locomotion.cancel_walk(not _is_blocked())
if _rig != null:
_rig.cancel_prop_action()
_primary_speech.clear()
_phase_timer = 0.0
_action_phase = ActionPhase.NONE
_prop_action_finished = false
_current_run_item.clear()
_current_index = -1
_current_action_id = -1
@@ -430,6 +450,10 @@ func _cancel_current_primary(notify: bool = false) -> void:
_speech_bubble.hide_bubble()
func _on_prop_action_completed() -> void:
_prop_action_finished = true
func _allocate_action_id() -> int:
var result: int = _next_action_id
_next_action_id += 1