Implement interactive prop handling for StickmanRig

- Added PropTrajectory class for deterministic ballistic calculations.
- Introduced StickmanPropInteraction component to manage prop interactions (pickup, drop, throw).
- Implemented grip resolution and attachment logic for props.
- Added signals for interaction events (prop_picked_up, prop_dropped, prop_thrown).
- Defined specifications for prop interaction lifecycle and event rules.
- Ensured compatibility with existing prop systems and added new capabilities for prop pickup.
This commit is contained in:
2026-09-23 21:26:13 -04:00
parent 0a7f97cec7
commit 043f114083
22 changed files with 1444 additions and 37 deletions
+19 -2
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 }
signal action_started(action: Dictionary, index: int)
signal action_finished(action: Dictionary, index: int)
@@ -22,6 +22,7 @@ signal recovery_requested
var action_queue: Array[Dictionary] = []
var _rig: StickmanRig = null
var _locomotion: StickmanLocomotion = null
var _prop_interaction: StickmanPropInteraction = null
var _runner_state: RunnerState = RunnerState.IDLE
var _action_phase: ActionPhase = ActionPhase.NONE
var _current_index: int = -1
@@ -34,9 +35,12 @@ var _overlay_speech: Dictionary = {}
var _speech_bubble: Node2D = null
func configure(rig: StickmanRig, locomotion: StickmanLocomotion) -> void:
func configure(rig: StickmanRig, locomotion: StickmanLocomotion, prop_interaction: StickmanPropInteraction = null) -> void:
_rig = rig
_locomotion = locomotion
_prop_interaction = prop_interaction
if _prop_interaction != null and not _prop_interaction.interaction_finished.is_connected(_on_interaction_finished):
_prop_interaction.interaction_finished.connect(_on_interaction_finished)
func tick(delta: float, rig_state: int, ragdoll_at_rest: bool) -> void:
@@ -62,6 +66,9 @@ func tick(delta: float, rig_state: int, ragdoll_at_rest: bool) -> void:
ActionPhase.RECOVERING:
if rig_state == StickmanRig.RigState.ANIMATED:
_finish_action()
ActionPhase.PROP:
if _prop_interaction != null:
_prop_interaction.tick(delta)
func speak(text: String, duration: float) -> void:
@@ -241,6 +248,10 @@ func _begin_action(action: Dictionary) -> void:
"recover":
_action_phase = ActionPhase.RECOVERING
recovery_requested.emit()
"pick_up_prop", "drop_prop", "throw_prop":
_action_phase = ActionPhase.PROP
if _prop_interaction == null or not _prop_interaction.begin_action(action):
_finish_action()
_:
push_warning("StickmanActionRunner: unknown action type '%s'; skipped." % String(action.get("type", "")))
_finish_action()
@@ -257,6 +268,8 @@ func _finish_action() -> void:
func _cancel_current_primary() -> void:
if _prop_interaction != null:
_prop_interaction.cancel()
if _locomotion != null:
_locomotion.cancel_walk(not _is_blocked())
_primary_speech.clear()
@@ -267,6 +280,10 @@ func _cancel_current_primary() -> void:
if _overlay_speech.is_empty() and _speech_bubble != null and is_instance_valid(_speech_bubble):
_speech_bubble.hide_bubble()
func _on_interaction_finished(_succeeded: bool) -> void:
if _action_phase == ActionPhase.PROP:
_finish_action()
func _is_blocked() -> bool:
return _locomotion != null and _locomotion.is_action_blocked()