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
+39
View File
@@ -11,6 +11,7 @@ extends RigidBody2D
## friction/bounce (PhysicsMaterial), and themed visuals.
const PropVisuals := preload("res://scripts/prop_visuals.gd")
const PropAnchorDataScript := preload("res://scripts/prop_anchor_data.gd")
# ---------------------------------------------------------------------------
# Enums
@@ -23,6 +24,13 @@ enum MaterialPreset { NONE, WOOD, RUBBER, CARDBOARD, METAL }
## Stable library provenance used by Sandbox Stage scene files.
var spawn_id: String = ""
## Stable identity of this placed prop in one Sandbox Stage scene. Unlike
## spawn_id, this value is unique per instance and survives scene save/load.
var stage_object_id: String = ""
var interaction_anchors: Dictionary = {}
var interaction_reserved_by: int = 0
var interaction_owner_id: int = 0
# ---------------------------------------------------------------------------
# Child node names
# ---------------------------------------------------------------------------
@@ -126,6 +134,33 @@ func _ready() -> void:
body_exited.connect(_on_body_exited)
func set_interaction_anchors(value: Dictionary) -> void:
interaction_anchors = PropAnchorDataScript.normalize_anchors(value)
func get_interaction_anchors() -> Dictionary:
return PropAnchorDataScript.normalize_anchors(interaction_anchors, false)
func ensure_default_grip() -> void:
var normalized := get_interaction_anchors()
if not PropAnchorDataScript.ids_for_role(normalized, "grip").is_empty():
interaction_anchors = normalized
return
var bounds := _visual_local_bounds
if bounds.size == Vector2.ZERO:
if shape_type == ShapeType.CIRCLE:
bounds = Rect2(Vector2(-radius, -radius), Vector2(radius * 2.0, radius * 2.0))
elif polygon_points.size() >= 3:
var min_point := polygon_points[0]
var max_point := polygon_points[0]
for point: Vector2 in polygon_points:
min_point = min_point.min(point)
max_point = max_point.max(point)
bounds = Rect2(min_point, max_point - min_point)
interaction_anchors = PropAnchorDataScript.preset_layout("one_handed", bounds)
func _on_body_entered(body: Node) -> void:
var center: Vector2 = (body as Node2D).global_position if body is Node2D else global_position
var normal: Vector2 = (center - global_position).normalized()
@@ -277,6 +312,10 @@ func has_custom_shapes() -> bool:
func get_visual_local_bounds() -> Rect2:
return _visual_local_bounds
func get_interaction_scale() -> Vector2:
return _play_instance_scale if _play_scale_active else scale.abs()
## RigidBody2D transforms do not reliably retain scale during simulation. Move
## the authored uniform scale into child visuals and collision geometry for
## PLAY, then restore the root scale on return to EDIT for scene persistence.