Add new scene files and implement stage impact resolver
- Created three new scene files: `wobble.scene`, `wobble2.scene`, and `wobble3.scene` with various terrain and prop configurations. - Introduced `stage_impact_resolver.gd` script to manage impact detection and response between props and stickmen. - Added unit tests for actor blocking overlap to ensure proper interaction with stationary props.
This commit is contained in:
@@ -44,6 +44,7 @@ const STAGE_RULE_AUTHORING_SCRIPT := preload("res://scripts/editors/stage/stage_
|
||||
const STAGE_ACTION_QUEUE_SCRIPT := preload("res://scripts/editors/stage/stage_action_queue_controller.gd")
|
||||
const STAGE_DIRECTOR_CONTROLLER_SCRIPT := preload("res://scripts/editors/stage/stage_director_controller.gd")
|
||||
const STAGE_RUN_SESSION_SCRIPT := preload("res://scripts/editors/stage/stage_run_session.gd")
|
||||
const STAGE_IMPACT_RESOLVER_SCRIPT := preload("res://scripts/stage/stage_impact_resolver.gd")
|
||||
const STAGE_ASSET_BROWSER_SCRIPT := preload("res://scripts/editors/stage/stage_asset_browser.gd")
|
||||
const STAGE_MODAL_VIEWS_SCRIPT := preload("res://scripts/editors/stage/stage_modal_views.gd")
|
||||
const STAGE_THEME_CONTROLLER_SCRIPT := preload("res://scripts/editors/stage/stage_theme_controller.gd")
|
||||
@@ -241,6 +242,7 @@ var _grid: StageGrid
|
||||
var _spatial_index: StageSpatialIndex
|
||||
var _terrain_painter: StageTerrainPainter
|
||||
var _rule_engine: StageRuleEngine
|
||||
var _impact_resolver: StageImpactResolver
|
||||
var _rule_authoring: StageRuleAuthoringController
|
||||
var _action_queue_controller: StageActionQueueController
|
||||
var _speech_session: StageSpeechSession
|
||||
@@ -248,6 +250,7 @@ var _asset_browser: StageAssetBrowser
|
||||
var _modal_views: StageModalViews
|
||||
var _chrome: StageChrome
|
||||
var _prop_settings_view: StagePropSettingsView
|
||||
var _edit_wobble_preview_prop: PropBlock
|
||||
var _use_spot_overlay: StageUseSpotOverlay
|
||||
var _use_target_capture_bar: StageUseTargetCaptureBar
|
||||
var _placement_overlay = null # StagePlacementOverlay (preloaded)
|
||||
@@ -1181,6 +1184,7 @@ func _ready() -> void:
|
||||
get_tree().auto_accept_quit = false
|
||||
_camera.make_current()
|
||||
_run_session = STAGE_RUN_SESSION_SCRIPT.new() as StageRunSession
|
||||
_impact_resolver = STAGE_IMPACT_RESOLVER_SCRIPT.new() as StageImpactResolver
|
||||
_document = STAGE_DOCUMENT_CONTROLLER_SCRIPT.new() as StageDocumentController
|
||||
_document.open_path_requested.connect(_on_document_open_path_requested)
|
||||
_document.save_path_selected.connect(_on_scene_save_path_selected)
|
||||
@@ -1311,6 +1315,8 @@ func _process(delta: float) -> void:
|
||||
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if _impact_resolver != null:
|
||||
_impact_resolver.finish_sweep_tick(Engine.get_physics_frames())
|
||||
_update_throw_preview_cache()
|
||||
_update_use_target_preview_cache()
|
||||
var elapsed_timestamp: float = _run_session.advance_elapsed(delta) if _run_session != null else -1.0
|
||||
@@ -1676,6 +1682,7 @@ func set_mode(mode: StageMode) -> void:
|
||||
_sync_mode_buttons()
|
||||
return
|
||||
var previous_mode: StageMode = current_mode
|
||||
_cancel_edit_wobble_preview()
|
||||
if mode == StageMode.PLAY and _run_state == RunState.STOPPED and not _cli_startup:
|
||||
if not _save_scene_to_path(AUTOSAVE_PATH, true):
|
||||
push_warning("SandboxStage: autosave before Play failed.")
|
||||
@@ -1781,6 +1788,9 @@ func _enter_play_mode() -> void:
|
||||
|
||||
|
||||
func _start_run() -> void:
|
||||
_cancel_edit_wobble_preview()
|
||||
if _impact_resolver != null:
|
||||
_impact_resolver.reset()
|
||||
# STOP reasserts the authored transforms for a few physics frames. If the
|
||||
# child immediately presses PLAY, those delayed writes must not put an
|
||||
# authored prop scale back on the root after begin_play_instance_scale()
|
||||
@@ -1795,6 +1805,9 @@ func _start_run() -> void:
|
||||
func _pause_run() -> void:
|
||||
if not _run_session.pause_run(_world_children_selectable()):
|
||||
return
|
||||
if _impact_resolver != null:
|
||||
_impact_resolver.reset()
|
||||
_seed_impact_contact_baseline()
|
||||
if _use_spot_overlay != null:
|
||||
_use_spot_overlay.set_paused(true)
|
||||
_refresh_event_edge_baseline()
|
||||
@@ -1813,6 +1826,9 @@ func _resume_run() -> void:
|
||||
_clear_director_pending()
|
||||
if not _run_session.resume_run(_world_children_selectable(), _rule_engine):
|
||||
return
|
||||
if _impact_resolver != null:
|
||||
_impact_resolver.reset()
|
||||
_seed_impact_contact_baseline()
|
||||
if _use_spot_overlay != null:
|
||||
_use_spot_overlay.set_paused(false)
|
||||
_sync_run_controls()
|
||||
@@ -1820,6 +1836,9 @@ func _resume_run() -> void:
|
||||
|
||||
|
||||
func _reset_run() -> void:
|
||||
_cancel_edit_wobble_preview()
|
||||
if _impact_resolver != null:
|
||||
_impact_resolver.reset()
|
||||
if PROP_TRACE_ENABLED:
|
||||
print("[PropTrace] stage reset_run mode=%s run_state=%s" % [StageMode.keys()[int(current_mode)], RunState.keys()[int(_run_state)]])
|
||||
_run_session.begin_reset()
|
||||
@@ -1856,6 +1875,7 @@ func _sync_run_controls() -> void:
|
||||
|
||||
func _finish_resume_baseline() -> void:
|
||||
if _run_state == RunState.RUNNING and current_mode == StageMode.PLAY:
|
||||
_seed_impact_contact_baseline()
|
||||
_event_dispatch_ready = true
|
||||
|
||||
|
||||
@@ -1876,6 +1896,8 @@ func _refresh_event_edge_baseline() -> void:
|
||||
_rule_engine.refresh_event_edge_baseline(_world_children_selectable())
|
||||
|
||||
func _save_object_state(node: Node2D) -> void:
|
||||
if node is StickmanRig:
|
||||
(node as StickmanRig).reset_collision_proxy_velocity_baselines()
|
||||
_run_session.save_object_state(node)
|
||||
|
||||
|
||||
@@ -1957,12 +1979,19 @@ func _finalize_placed_node(node: Node2D) -> void:
|
||||
rig.action_finished.connect(_on_rig_action_finished.bind(rig))
|
||||
rig.speech_finished.connect(_on_rig_speech_finished.bind(rig))
|
||||
rig.state_changed.connect(_on_rig_state_changed.bind(rig))
|
||||
rig.blocking_prop_sweep_hit.connect(_on_blocking_prop_sweep_hit)
|
||||
rig.prop_interaction_event.connect(_on_rig_prop_interaction_event.bind(rig))
|
||||
rig.use_completed.connect(_on_rig_use_completed.bind(rig))
|
||||
rig.use_failed.connect(_on_rig_use_failed.bind(rig))
|
||||
_director_visuals.mark_dirty()
|
||||
if node is PropBlock:
|
||||
var prop := node as PropBlock
|
||||
if _sandbox_theme != null:
|
||||
prop.set_wobble_style(
|
||||
_sandbox_theme.number("effects.impact_wobble.duration", PropBlock.DEFAULT_WOBBLE_DURATION, 0.05, 2.0),
|
||||
_sandbox_theme.number("effects.impact_wobble.maximum_angle_degrees", PropBlock.DEFAULT_WOBBLE_ANGLE_DEGREES, 0.0, 30.0),
|
||||
_sandbox_theme.number("effects.impact_wobble.sideways_offset", PropBlock.DEFAULT_WOBBLE_OFFSET_LOCAL, 0.0, 24.0)
|
||||
)
|
||||
prop.collided.connect(_on_prop_collided.bind(prop))
|
||||
if not prop.physics_profile_changed.is_connected(_on_prop_physics_profile_changed):
|
||||
prop.physics_profile_changed.connect(_on_prop_physics_profile_changed)
|
||||
@@ -2864,6 +2893,7 @@ func _build_prop_settings_view(ui: CanvasLayer) -> void:
|
||||
_prop_settings_view.configure(_sandbox_theme as SandboxTheme, TOP_BAR_HEIGHT, STATUS_BAR_HEIGHT)
|
||||
_prop_settings_view.role_choice_requested.connect(_on_prop_role_choice)
|
||||
_prop_settings_view.role_toggle_requested.connect(_on_prop_role_toggle)
|
||||
_prop_settings_view.wobble_toggled.connect(_on_prop_wobble_toggled)
|
||||
_prop_settings_view.spot_add_requested.connect(_on_spot_add_requested)
|
||||
_prop_settings_view.spot_move_requested.connect(_on_spot_move_requested)
|
||||
_prop_settings_view.spot_rename_requested.connect(_on_spot_rename_requested)
|
||||
@@ -3002,6 +3032,29 @@ func _on_prop_role_toggle(enabled: bool, key: String) -> void:
|
||||
_refresh_prop_settings_card()
|
||||
|
||||
|
||||
func _on_prop_wobble_toggled(enabled: bool) -> void:
|
||||
var prop: PropBlock = _selection.get_primary() as PropBlock if _selection != null else null
|
||||
if prop == null or not is_instance_valid(prop) or current_mode != StageMode.EDIT:
|
||||
return
|
||||
prop.set_wobble_on_impact(enabled)
|
||||
if enabled:
|
||||
_cancel_edit_wobble_preview()
|
||||
prop.preview_wobble()
|
||||
_edit_wobble_preview_prop = prop
|
||||
else:
|
||||
_edit_wobble_preview_prop = null
|
||||
_save_object_state(prop)
|
||||
_mark_scene_dirty()
|
||||
_refresh_prop_settings_card()
|
||||
|
||||
|
||||
func _cancel_edit_wobble_preview() -> void:
|
||||
var prop: PropBlock = _edit_wobble_preview_prop
|
||||
_edit_wobble_preview_prop = null
|
||||
if prop != null and is_instance_valid(prop):
|
||||
prop.cancel_wobble()
|
||||
|
||||
|
||||
func _on_spot_add_requested() -> void:
|
||||
var prop: PropBlock = _selection.get_primary() as PropBlock if _selection != null else null
|
||||
if current_mode == StageMode.EDIT and prop != null and is_instance_valid(prop):
|
||||
@@ -3846,6 +3899,7 @@ func _on_grid_size_changed(value: float) -> void:
|
||||
|
||||
|
||||
func _on_selection_changed(nodes: Array[Node2D]) -> void:
|
||||
_cancel_edit_wobble_preview()
|
||||
if _spot_edit_active and (_spot_edit_prop == null or not is_instance_valid(_spot_edit_prop) or not nodes.has(_spot_edit_prop)):
|
||||
_cancel_spot_work()
|
||||
_gizmos.set_targets(nodes)
|
||||
@@ -4654,7 +4708,10 @@ func _on_prop_collided(other: Node, normal: Vector2, position: Vector2, prop: Pr
|
||||
# authoritative snap_to_standing() cleanup has run.
|
||||
if current_mode != StageMode.PLAY or _run_state != RunState.RUNNING or not _event_dispatch_ready:
|
||||
return
|
||||
if other == null or not is_instance_valid(other) or _rule_engine == null:
|
||||
if other == null or not is_instance_valid(other):
|
||||
return
|
||||
_route_impact_contact(prop, other, normal, position)
|
||||
if _rule_engine == null:
|
||||
return
|
||||
if not _rule_engine.register_contact(prop, other):
|
||||
return
|
||||
@@ -4679,6 +4736,8 @@ func _on_prop_collided(other: Node, normal: Vector2, position: Vector2, prop: Pr
|
||||
|
||||
|
||||
func _on_prop_separated(other: Node, prop: PropBlock) -> void:
|
||||
if _impact_resolver != null and other != null and is_instance_valid(other):
|
||||
_impact_resolver.end_contact(prop, other)
|
||||
if current_mode != StageMode.PLAY or _run_state != RunState.RUNNING or not _event_dispatch_ready:
|
||||
return
|
||||
if other == null or not is_instance_valid(other) or _rule_engine == null:
|
||||
@@ -4692,12 +4751,157 @@ func _on_prop_separated(other: Node, prop: PropBlock) -> void:
|
||||
_rule_engine.unregister_rig_body_contact(rig, prop, other)
|
||||
|
||||
|
||||
func _route_impact_contact(prop: PropBlock, other: Node, normal: Vector2, position: Vector2) -> void:
|
||||
if _impact_resolver == null or prop == null or other == null:
|
||||
return
|
||||
var receiver_velocity: Vector2 = prop.get_impact_velocity()
|
||||
var other_velocity: Vector2 = _impact_other_velocity(other)
|
||||
var sample: Dictionary = {
|
||||
"receiver": prop,
|
||||
"other": other,
|
||||
"normal": normal,
|
||||
"position": position,
|
||||
"receiver_velocity": receiver_velocity,
|
||||
"other_velocity": other_velocity,
|
||||
"source": &"body_contact",
|
||||
}
|
||||
if other.has_meta("stickman_rig"):
|
||||
sample["rig"] = other.get_meta("stickman_rig")
|
||||
if _impact_resolver.begin_contact(sample, Engine.get_physics_frames()):
|
||||
prop.play_impact_wobble()
|
||||
if other is PropBlock:
|
||||
(other as PropBlock).play_impact_wobble()
|
||||
|
||||
|
||||
func _on_blocking_prop_sweep_hit(
|
||||
rig: StickmanRig,
|
||||
prop: PropBlock,
|
||||
normal: Vector2,
|
||||
position: Vector2,
|
||||
requested_velocity: Vector2
|
||||
) -> void:
|
||||
if current_mode != StageMode.PLAY or _run_state != RunState.RUNNING or not _event_dispatch_ready:
|
||||
return
|
||||
if rig == null or prop == null or not is_instance_valid(rig) or not is_instance_valid(prop):
|
||||
return
|
||||
var sample: Dictionary = {
|
||||
"receiver": prop,
|
||||
"other": rig,
|
||||
"rig": rig,
|
||||
"normal": normal,
|
||||
"position": position,
|
||||
"receiver_velocity": prop.get_impact_velocity(),
|
||||
"other_velocity": requested_velocity,
|
||||
"source": &"blocking_sweep",
|
||||
}
|
||||
if _impact_resolver != null and _impact_resolver.note_sweep(sample, Engine.get_physics_frames()):
|
||||
prop.play_impact_wobble()
|
||||
|
||||
|
||||
func _impact_other_velocity(other: Node) -> Vector2:
|
||||
if other is PropBlock:
|
||||
return (other as PropBlock).get_impact_velocity()
|
||||
var rig: StickmanRig = other.get_meta("stickman_rig") as StickmanRig if other.has_meta("stickman_rig") else null
|
||||
if other.has_meta("stickman_ragdoll_body") and bool(other.get_meta("stickman_ragdoll_body")):
|
||||
return rig.ragdoll_body_velocity(other as RigidBody2D) if rig != null and other is RigidBody2D else Vector2.ZERO
|
||||
if rig != null and other is PhysicsBody2D:
|
||||
return rig.collision_proxy_velocity(other as PhysicsBody2D)
|
||||
if other is RigidBody2D and not (other as RigidBody2D).freeze:
|
||||
var velocity: Vector2 = (other as RigidBody2D).linear_velocity
|
||||
return velocity if velocity.is_finite() else Vector2.ZERO
|
||||
return Vector2.ZERO
|
||||
|
||||
|
||||
func _seed_impact_contact_baseline() -> void:
|
||||
if _impact_resolver == null or _world == null:
|
||||
return
|
||||
var tick: int = Engine.get_physics_frames()
|
||||
for node: Node in _world.get_children():
|
||||
if not node is PropBlock or not is_instance_valid(node):
|
||||
continue
|
||||
var prop: PropBlock = node as PropBlock
|
||||
var direct_state: PhysicsDirectBodyState2D = PhysicsServer2D.body_get_direct_state(prop.get_rid())
|
||||
if direct_state == null:
|
||||
continue
|
||||
for contact_index: int in direct_state.get_contact_count():
|
||||
var collider_id: int = direct_state.get_contact_collider_id(contact_index)
|
||||
var collider_object: Object = instance_from_id(collider_id)
|
||||
if not collider_object is Node:
|
||||
continue
|
||||
var other: Node = collider_object as Node
|
||||
var contact_normal: Vector2 = direct_state.get_contact_local_normal(contact_index)
|
||||
if contact_normal.is_zero_approx():
|
||||
contact_normal = other.global_position - prop.global_position if other is Node2D else Vector2.RIGHT
|
||||
var sample: Dictionary = {
|
||||
"receiver": prop,
|
||||
"other": other,
|
||||
"normal": contact_normal,
|
||||
"position": direct_state.get_contact_local_position(contact_index),
|
||||
"receiver_velocity": Vector2.ZERO,
|
||||
"other_velocity": Vector2.ZERO,
|
||||
"source": &"baseline",
|
||||
}
|
||||
if other.has_meta("stickman_rig"):
|
||||
sample["rig"] = other.get_meta("stickman_rig")
|
||||
_impact_resolver.begin_contact(sample, tick)
|
||||
|
||||
|
||||
func _on_rig_state_changed(new_state: int, rig: StickmanRig) -> void:
|
||||
if _impact_resolver != null:
|
||||
_impact_resolver.end_rig_contacts(rig)
|
||||
if new_state == StickmanRig.RigState.RAGDOLL:
|
||||
_connect_ragdoll_impact_contacts(rig)
|
||||
var rig_ids: Array[int] = [rig.get_instance_id()]
|
||||
_purge_collision_pairs_for_ids(rig_ids)
|
||||
|
||||
|
||||
func _connect_ragdoll_impact_contacts(rig: StickmanRig) -> void:
|
||||
if rig == null or not is_instance_valid(rig):
|
||||
return
|
||||
for body: RigidBody2D in rig.get_ragdoll_bodies():
|
||||
if body == null or not is_instance_valid(body):
|
||||
continue
|
||||
body.contact_monitor = true
|
||||
body.max_contacts_reported = maxi(body.max_contacts_reported, 8)
|
||||
var entered_callback: Callable = Callable(self, "_on_ragdoll_body_entered").bind(rig, body)
|
||||
if not body.body_entered.is_connected(entered_callback):
|
||||
body.body_entered.connect(entered_callback)
|
||||
var exited_callback: Callable = Callable(self, "_on_ragdoll_body_exited").bind(rig, body)
|
||||
if not body.body_exited.is_connected(exited_callback):
|
||||
body.body_exited.connect(exited_callback)
|
||||
|
||||
|
||||
func _on_ragdoll_body_entered(other: Node, rig: StickmanRig, body: RigidBody2D) -> void:
|
||||
if current_mode != StageMode.PLAY or _run_state != RunState.RUNNING or not _event_dispatch_ready:
|
||||
return
|
||||
if rig == null or body == null or other == null:
|
||||
return
|
||||
if not is_instance_valid(rig) or not is_instance_valid(body) or not is_instance_valid(other):
|
||||
return
|
||||
if not other is PropBlock:
|
||||
return
|
||||
var prop: PropBlock = other as PropBlock
|
||||
var normal: Vector2 = body.global_position - prop.global_position
|
||||
if normal.is_zero_approx():
|
||||
normal = Vector2.UP
|
||||
var position: Vector2 = (body.global_position + prop.global_position) * 0.5
|
||||
var direct_state: PhysicsDirectBodyState2D = PhysicsServer2D.body_get_direct_state(body.get_rid())
|
||||
if direct_state != null:
|
||||
for contact_index: int in direct_state.get_contact_count():
|
||||
if direct_state.get_contact_collider_id(contact_index) != prop.get_instance_id():
|
||||
continue
|
||||
position = body.to_global(direct_state.get_contact_local_position(contact_index))
|
||||
break
|
||||
_route_impact_contact(prop, body, normal.normalized(), position)
|
||||
|
||||
|
||||
func _on_ragdoll_body_exited(other: Node, _rig: StickmanRig, body: RigidBody2D) -> void:
|
||||
if _impact_resolver == null or other == null or not is_instance_valid(other):
|
||||
return
|
||||
if other is PropBlock:
|
||||
_impact_resolver.end_contact(other as PropBlock, body)
|
||||
|
||||
|
||||
func _purge_collision_pairs_for_ids(ids: Array[int]) -> void:
|
||||
if _rule_engine != null:
|
||||
_rule_engine.purge_collision_pairs_for_ids(ids)
|
||||
|
||||
@@ -10,6 +10,7 @@ const PROP_STAGE_ROLES := preload("res://scripts/props/prop_stage_roles.gd")
|
||||
|
||||
signal role_choice_requested(key: String, value: String)
|
||||
signal role_toggle_requested(enabled: bool, key: String)
|
||||
signal wobble_toggled(enabled: bool)
|
||||
signal spot_add_requested
|
||||
signal spot_move_requested(spot_id: String)
|
||||
signal spot_rename_requested(spot_id: String, name: String)
|
||||
@@ -26,6 +27,7 @@ var _fields: VBoxContainer
|
||||
var _notice: Label
|
||||
var _depth_group: StagePanelChoiceGroup
|
||||
var _motion_group: StagePanelChoiceGroup
|
||||
var _wobble_row: StagePanelToggleRow
|
||||
var _controls: Dictionary = {}
|
||||
var _spot_rows: VBoxContainer
|
||||
var _spot_empty_label: Label
|
||||
@@ -193,6 +195,7 @@ func _build() -> void:
|
||||
_controls["blocks_stickmen"] = _make_toggle_row(_fields, "blocks_stickmen", "Blocks stickmen")
|
||||
_controls["can_pick_up"] = _make_toggle_row(_fields, "can_pick_up", "Stickmen can pick it up")
|
||||
_add_release_options()
|
||||
_add_wobble_section()
|
||||
_add_use_spots_section()
|
||||
|
||||
_reopen_button = Button.new()
|
||||
@@ -239,6 +242,19 @@ func _add_use_spots_section() -> void:
|
||||
section.add_child(_spot_rows)
|
||||
|
||||
|
||||
func _add_wobble_section() -> void:
|
||||
_add_heading(_fields, "When bumped")
|
||||
_wobble_row = STAGE_PANEL_TOGGLE_ROW.new() as StagePanelToggleRow
|
||||
_wobble_row.name = "WobbleWhenBumped"
|
||||
_wobble_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_wobble_row.configure("Wobble when bumped")
|
||||
_wobble_row.set_help_text("A hard bump makes this prop wobble once in PLAY.")
|
||||
_wobble_row.apply_theme(_stage_theme)
|
||||
_fields.add_child(_wobble_row)
|
||||
_wobble_row.preferred_height_changed.connect(_on_row_height_changed)
|
||||
_wobble_row.toggled.connect(_on_wobble_toggled)
|
||||
|
||||
|
||||
func _sync_spots(spots: Array[Dictionary], selected_spot_id: String) -> void:
|
||||
if _spot_rows == null:
|
||||
return
|
||||
@@ -390,6 +406,9 @@ func _sync_roles(prop: PropBlock, roles: Dictionary) -> void:
|
||||
else:
|
||||
_depth_group.select_no_signal(StringName(String(roles.get("depth", PROP_STAGE_ROLES.DEPTH_BEHIND))))
|
||||
_motion_group.select_no_signal(StringName(String(roles.get("start_motion", PROP_STAGE_ROLES.START_STILL))))
|
||||
if _wobble_row != null:
|
||||
_wobble_row.set_checked_no_signal(prop.wobble_on_impact)
|
||||
_wobble_row.set_help_text("A hard bump makes this prop wobble once in PLAY.")
|
||||
for key: String in ["holds_other_props", "blocks_stickmen", "can_pick_up", "released_blocks_stickmen", "released_bumps_other_props"]:
|
||||
var control := _controls.get(key) as StagePanelToggleRow
|
||||
if control == null:
|
||||
@@ -453,6 +472,10 @@ func _on_role_toggled(enabled: bool, key: String) -> void:
|
||||
role_toggle_requested.emit(enabled, key)
|
||||
|
||||
|
||||
func _on_wobble_toggled(enabled: bool) -> void:
|
||||
wobble_toggled.emit(enabled)
|
||||
|
||||
|
||||
func _on_row_height_changed() -> void:
|
||||
if _card != null:
|
||||
_card.request_fit()
|
||||
|
||||
@@ -10,6 +10,8 @@ const STATE_RUNNING: int = 1
|
||||
const STATE_PAUSED: int = 2
|
||||
## Set true temporarily when diagnosing authored/play prop scale transfer.
|
||||
const PROP_SCALE_TRACE_ENABLED: bool = false
|
||||
## Temporary, low-volume diagnostics for props that should block stickmen.
|
||||
const ACTOR_PROP_DEBUG_ENABLED: bool = true
|
||||
|
||||
var run_state: int = STATE_STOPPED
|
||||
var elapsed_seconds: float = 0.0
|
||||
@@ -44,14 +46,18 @@ func start_run(
|
||||
for node: Node2D in world_nodes:
|
||||
if node is PropBlock:
|
||||
var prop: PropBlock = node as PropBlock
|
||||
prop.cancel_wobble()
|
||||
trace_prop_scale(prop, "before_play_scale_transfer", stage_mode_name, "RUNNING", camera_zoom)
|
||||
prop.begin_play_instance_scale()
|
||||
trace_prop_scale(prop, "after_play_scale_transfer", stage_mode_name, "RUNNING", camera_zoom)
|
||||
var start_role: PropBlock.RuntimeRole = PropBlock.RuntimeRole.START_STILL if String(prop.stage_roles.get("start_motion", PROP_STAGE_ROLES.START_STILL)) == PROP_STAGE_ROLES.START_STILL else PropBlock.RuntimeRole.START_PHYSICS
|
||||
prop.apply_runtime_role(start_role)
|
||||
if ACTOR_PROP_DEBUG_ENABLED:
|
||||
_debug_actor_blocking_prop(prop)
|
||||
trace_prop_scale(prop, "after_play_role_applied", stage_mode_name, "RUNNING", camera_zoom)
|
||||
elif node is STICKMAN_RIG_SCRIPT:
|
||||
var rig: StickmanRig = node as StickmanRig
|
||||
rig.reset_collision_proxy_velocity_baselines()
|
||||
rig.reset_simulation_pause()
|
||||
rig.stop_queue()
|
||||
rig.clear_reactive_actions()
|
||||
@@ -60,6 +66,44 @@ func start_run(
|
||||
rig.start_queue()
|
||||
elif node is RigidBody2D:
|
||||
(node as RigidBody2D).freeze = false
|
||||
if node is PropBlock:
|
||||
(node as PropBlock).reset_impact_velocity_baseline()
|
||||
|
||||
|
||||
func _debug_actor_blocking_prop(prop: PropBlock) -> void:
|
||||
if prop == null or not is_instance_valid(prop):
|
||||
return
|
||||
var role_index: int = clampi(int(prop.runtime_role), 0, PropBlock.RuntimeRole.keys().size() - 1)
|
||||
var collision_nodes := PackedStringArray()
|
||||
for child: Node in prop.get_children():
|
||||
if child is CollisionPolygon2D:
|
||||
var polygon: CollisionPolygon2D = child as CollisionPolygon2D
|
||||
collision_nodes.append(
|
||||
"CollisionPolygon2D(disabled=%s,points=%d,global=%s)"
|
||||
% [str(polygon.disabled), polygon.polygon.size(), str(polygon.global_transform)]
|
||||
)
|
||||
elif child is CollisionShape2D:
|
||||
var shape: CollisionShape2D = child as CollisionShape2D
|
||||
collision_nodes.append(
|
||||
"CollisionShape2D(disabled=%s,type=%s,global=%s)"
|
||||
% [str(shape.disabled), shape.shape.get_class() if shape.shape != null else "null", str(shape.global_transform)]
|
||||
)
|
||||
print(
|
||||
"[PropCollisionDebug][PlayProp] id=%s asset=%s role=%s blocking=%s roles=%s layer=%d mask=%d freeze=%s pos=%s scale=%s collision=%s"
|
||||
% [
|
||||
prop.stage_object_id,
|
||||
prop.spawn_id,
|
||||
PropBlock.RuntimeRole.keys()[role_index],
|
||||
str(prop.is_actor_blocking()),
|
||||
str(prop.stage_roles),
|
||||
prop.collision_layer,
|
||||
prop.collision_mask,
|
||||
str(prop.freeze),
|
||||
str(prop.global_position),
|
||||
str(prop.global_scale),
|
||||
"; ".join(collision_nodes),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
func pause_run(world_nodes: Array[Node2D]) -> bool:
|
||||
@@ -69,7 +113,11 @@ func pause_run(world_nodes: Array[Node2D]) -> bool:
|
||||
run_state = STATE_PAUSED
|
||||
resume_baseline_frames = 0
|
||||
for node: Node2D in world_nodes:
|
||||
if node is STICKMAN_RIG_SCRIPT:
|
||||
if node is PropBlock:
|
||||
var prop: PropBlock = node as PropBlock
|
||||
prop.set_wobble_paused(true)
|
||||
capture_prop_pause_state(prop)
|
||||
elif node is STICKMAN_RIG_SCRIPT:
|
||||
(node as StickmanRig).pause_simulation()
|
||||
elif node is RigidBody2D:
|
||||
capture_prop_pause_state(node as RigidBody2D)
|
||||
@@ -91,8 +139,13 @@ func resume_run(world_nodes: Array[Node2D], rule_engine: StageRuleEngine) -> boo
|
||||
body.sleeping = bool(snapshot.get("sleeping", false))
|
||||
prop_pause_states.clear()
|
||||
for node: Node2D in world_nodes:
|
||||
if node is STICKMAN_RIG_SCRIPT:
|
||||
(node as StickmanRig).resume_simulation()
|
||||
if node is PropBlock:
|
||||
(node as PropBlock).set_wobble_paused(false)
|
||||
(node as PropBlock).reset_impact_velocity_baseline()
|
||||
elif node is STICKMAN_RIG_SCRIPT:
|
||||
var rig: StickmanRig = node as StickmanRig
|
||||
rig.reset_collision_proxy_velocity_baselines()
|
||||
rig.resume_simulation()
|
||||
run_state = STATE_RUNNING
|
||||
event_dispatch_ready = false
|
||||
resume_baseline_frames = 2
|
||||
@@ -127,6 +180,7 @@ func reset_world(
|
||||
var body: RigidBody2D = node as RigidBody2D
|
||||
if node is PropBlock:
|
||||
var prop: PropBlock = node as PropBlock
|
||||
prop.cancel_wobble()
|
||||
var authored: Dictionary = authored_state.get(prop.get_instance_id(), {})
|
||||
trace_prop_scale(prop, "before_stop_scale_restore", stage_mode_name, "STOPPED", camera_zoom)
|
||||
prop.set_stage_roles(authored.get("roles", prop.stage_roles))
|
||||
@@ -185,7 +239,9 @@ func save_object_state(node: Node2D) -> void:
|
||||
if not node is STICKMAN_RIG_SCRIPT:
|
||||
entry["scale"] = node.scale
|
||||
if node is PropBlock:
|
||||
entry["roles"] = (node as PropBlock).stage_roles.duplicate(true)
|
||||
var prop: PropBlock = node as PropBlock
|
||||
entry["roles"] = prop.stage_roles.duplicate(true)
|
||||
entry["wobble_on_impact"] = prop.wobble_on_impact
|
||||
authored_state[node.get_instance_id()] = entry
|
||||
|
||||
|
||||
@@ -210,13 +266,18 @@ func restore_authored_state(stage_mode_name: String, run_state_name: String, cam
|
||||
trace_prop_scale(node as PropBlock, "after_authored_scale_write", stage_mode_name, run_state_name, camera_zoom)
|
||||
if node is STICKMAN_RIG_SCRIPT and entry.has("facing_profile"):
|
||||
(node as STICKMAN_RIG_SCRIPT).set_facing_profile(int(entry["facing_profile"]))
|
||||
if node is STICKMAN_RIG_SCRIPT:
|
||||
(node as StickmanRig).reset_collision_proxy_velocity_baselines()
|
||||
if node is RigidBody2D:
|
||||
var body: RigidBody2D = node as RigidBody2D
|
||||
body.linear_velocity = Vector2.ZERO
|
||||
body.angular_velocity = 0.0
|
||||
if node is PropBlock:
|
||||
var prop: PropBlock = node as PropBlock
|
||||
prop.cancel_wobble()
|
||||
prop.set_stage_roles(entry.get("roles", prop.stage_roles))
|
||||
prop.set_wobble_on_impact(bool(entry.get("wobble_on_impact", prop.wobble_on_impact)))
|
||||
prop.reset_impact_velocity_baseline()
|
||||
prop.apply_runtime_role(PropBlock.RuntimeRole.EDIT)
|
||||
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ func collect(
|
||||
entry["roles"] = prop.stage_roles.duplicate(true)
|
||||
entry["use_spots"] = PROP_USE_SPOT_DATA.encode(prop.get_use_spots())
|
||||
entry["next_use_spot_number"] = prop.next_use_spot_number
|
||||
entry["wobble_on_impact"] = prop.wobble_on_impact
|
||||
var prop_scale: Vector2 = authored_state.get("scale", prop.scale)
|
||||
entry["scale"] = STAGE_SCENE_IO.vector_to_data(prop_scale)
|
||||
entry["material_preset"] = prop.material_preset
|
||||
@@ -175,6 +176,7 @@ func apply(data: Dictionary, world: Node2D, spawner: StageSpawner) -> Dictionary
|
||||
if node is PropBlock:
|
||||
var prop: PropBlock = node as PropBlock
|
||||
prop.stage_object_id = String(entry.get("stage_object_id", ""))
|
||||
prop.set_wobble_on_impact(bool(entry.get("wobble_on_impact", false)))
|
||||
var decoded_spots: Array[Dictionary] = PROP_USE_SPOT_DATA.decode(entry.get("use_spots", []) as Array)
|
||||
prop.set_use_spots(decoded_spots, int(entry.get("next_use_spot_number", 1)))
|
||||
var default_roles: Dictionary = PROP_STAGE_ROLES.new_placement(prop.has_usable_grip())
|
||||
@@ -212,6 +214,7 @@ func apply(data: Dictionary, world: Node2D, spawner: StageSpawner) -> Dictionary
|
||||
(node as StickmanRig).queue_action(action)
|
||||
else:
|
||||
warnings.append("%s: invalid action queue. Open this character's queue and review its actions." % file_id)
|
||||
(node as StickmanRig).reset_collision_proxy_velocity_baselines()
|
||||
loaded_nodes.append(node)
|
||||
file_to_runtime[file_id] = node.get_instance_id()
|
||||
var decoded_rules: Dictionary = STAGE_SCENE_IO.decode_rules(data.get("event_rules", []) as Array, file_to_runtime, unavailable)
|
||||
|
||||
Reference in New Issue
Block a user