|
|
|
@@ -3,6 +3,8 @@ extends Node
|
|
|
|
|
## Owns navigation, walking/gait, terrain following, animated falls, and landings.
|
|
|
|
|
## StickmanRig schedules this component and exposes its public facade.
|
|
|
|
|
|
|
|
|
|
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
|
|
|
|
|
|
|
|
|
enum TerrainMotion { DISABLED, AIRBORNE, GROUNDED, LANDING_IMPACT }
|
|
|
|
|
|
|
|
|
|
const TERRAIN_PROBE_UP: float = 96.0
|
|
|
|
@@ -47,6 +49,19 @@ const NAV_PATH_DESIRED_DISTANCE: float = 8.0
|
|
|
|
|
const NAV_TARGET_DESIRED_DISTANCE: float = 12.0
|
|
|
|
|
const LATCH_PROBE_MAX_FRAMES: int = 6
|
|
|
|
|
const WALK_CYCLE_DURATION: float = 0.8
|
|
|
|
|
const RUN_CYCLE_DURATION: float = 0.75
|
|
|
|
|
const RUN_RIGHT_CONTACT_START: float = 0.0
|
|
|
|
|
const RUN_LEFT_CONTACT_START: float = 0.375
|
|
|
|
|
const RUN_CONTACT_DURATION: float = 0.1875
|
|
|
|
|
const RUN_CONTACT_EDGE_FADE: float = 0.03125
|
|
|
|
|
const ARRIVAL_SETTLE_DURATION: float = 0.15
|
|
|
|
|
const LEFT_STANDING_ANIMATION: StringName = &"walk_left"
|
|
|
|
|
const RIGHT_STANDING_ANIMATION: StringName = &"walk_right"
|
|
|
|
|
const MOVE_STYLE_DATA: Dictionary = {
|
|
|
|
|
"walk": {"clip": "walk_right", "pace": 300.0, "cycle": WALK_CYCLE_DURATION, "run": false},
|
|
|
|
|
"run": {"clip": "run_right", "pace": 500.0, "cycle": RUN_CYCLE_DURATION, "run": true},
|
|
|
|
|
}
|
|
|
|
|
const MOVE_MARKER_NAMES: Array[String] = ["Torso", "Head", "Left_Hand", "Right_Hand", "Left_Leg", "Right_Leg"]
|
|
|
|
|
|
|
|
|
|
const HEAD_COLLISION_RADIUS: float = 90.0
|
|
|
|
|
const TORSO_COLLISION_RADIUS: float = 20.0
|
|
|
|
@@ -106,12 +121,22 @@ var _terrain_paused_walk_animation: bool = false
|
|
|
|
|
var _nav_agent: NavigationAgent2D = null
|
|
|
|
|
var _nav_anchor: Node2D = null
|
|
|
|
|
var _walking: bool = false
|
|
|
|
|
var _settling: bool = false
|
|
|
|
|
var _settle_waiting_for_ground: bool = false
|
|
|
|
|
var _walk_target_feet: Vector2 = Vector2.ZERO
|
|
|
|
|
var _walk_speed_current: float = 300.0
|
|
|
|
|
var _active_move_style: String = "walk"
|
|
|
|
|
var _active_animation_name: StringName = &"walk_right"
|
|
|
|
|
var _active_cycle_duration: float = WALK_CYCLE_DURATION
|
|
|
|
|
var _walk_mode: String = "nav"
|
|
|
|
|
var _walk_mode_latched: bool = false
|
|
|
|
|
var _walk_latch_probe_frames: int = 0
|
|
|
|
|
var _walk_settle_frames: int = 0
|
|
|
|
|
var _walk_settle_elapsed: float = 0.0
|
|
|
|
|
var _walk_settle_start_pose: Dictionary = {}
|
|
|
|
|
var _walk_settle_end_pose: Dictionary = {}
|
|
|
|
|
var _walk_debug_settle_frames: int = 0
|
|
|
|
|
var _walk_debug_progress_elapsed: float = 0.0
|
|
|
|
|
var _arrival_emitted: bool = false
|
|
|
|
|
var _walk_done: bool = false
|
|
|
|
|
var _leg_base_markers: Dictionary = {}
|
|
|
|
|
var _last_grounded_walk_velocity_x: float = 0.0
|
|
|
|
@@ -309,27 +334,65 @@ func tick_movement(delta: float, _rig_state: int) -> void:
|
|
|
|
|
if terrain_following_enabled and terrain_motion == TerrainMotion.GROUNDED:
|
|
|
|
|
_last_grounded_walk_velocity_x = 0.0
|
|
|
|
|
_update_walking(delta)
|
|
|
|
|
_settle_walk_markers(delta)
|
|
|
|
|
_update_terrain_following(delta)
|
|
|
|
|
_settle_walk_markers()
|
|
|
|
|
_resume_settle_after_terrain()
|
|
|
|
|
if _walk_debug_enabled() and _walking:
|
|
|
|
|
_walk_debug_progress_elapsed += delta
|
|
|
|
|
if _walk_debug_progress_elapsed >= 1.0:
|
|
|
|
|
_walk_debug_progress_elapsed = 0.0
|
|
|
|
|
_debug_walk_pose("RUN_PROGRESS terrain=%s" % TerrainMotion.keys()[int(terrain_motion)])
|
|
|
|
|
|
|
|
|
|
func tick_post_skeleton(delta: float, _rig_state: int) -> void:
|
|
|
|
|
if _rig != null:
|
|
|
|
|
apply_impact_reactions(delta)
|
|
|
|
|
sync_collision_bodies()
|
|
|
|
|
if _walk_debug_settle_frames > 0:
|
|
|
|
|
_walk_debug_settle_frames -= 1
|
|
|
|
|
if _walk_debug_settle_frames == 0:
|
|
|
|
|
_debug_walk_pose("SETTLED")
|
|
|
|
|
|
|
|
|
|
func walk_to(target: Vector2, speed: float = -1.0) -> bool:
|
|
|
|
|
return move_to(target, "walk", speed)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func move_to(target: Vector2, style: String = ACTION_REGISTRY.WALK, speed_override: float = -1.0) -> bool:
|
|
|
|
|
if _walk_debug_enabled():
|
|
|
|
|
print("[WalkDebug] MOVE_TO style=%s target=%s root=%s" % [style, target, _rig.global_position if _rig != null else Vector2.ZERO])
|
|
|
|
|
if _rig == null or _rig.state != StickmanRig.RigState.ANIMATED:
|
|
|
|
|
push_warning("StickmanRig: walk_to ignored; not ANIMATED.")
|
|
|
|
|
push_warning("StickmanRig: Move To ignored; not ANIMATED.")
|
|
|
|
|
return false
|
|
|
|
|
if _rig._is_action_queue_blocked():
|
|
|
|
|
push_warning("StickmanRig: walk_to ignored during landing impact.")
|
|
|
|
|
push_warning("StickmanRig: Move To ignored during landing impact.")
|
|
|
|
|
return false
|
|
|
|
|
if _nav_agent == null or _nav_anchor == null:
|
|
|
|
|
push_warning("StickmanLocomotion: walk_to ignored; navigation is unavailable.")
|
|
|
|
|
push_warning("StickmanLocomotion: Move To ignored; navigation is unavailable.")
|
|
|
|
|
return false
|
|
|
|
|
var checked_style: String = style if MOVE_STYLE_DATA.has(style) else ACTION_REGISTRY.WALK
|
|
|
|
|
var style_data: Dictionary = MOVE_STYLE_DATA[checked_style]
|
|
|
|
|
_active_move_style = checked_style
|
|
|
|
|
_active_animation_name = StringName(style_data.get("clip", "walk_right"))
|
|
|
|
|
_active_cycle_duration = float(style_data.get("cycle", WALK_CYCLE_DURATION))
|
|
|
|
|
_walk_target_feet = target
|
|
|
|
|
_walk_speed_current = speed if speed > 0.0 else _rig.walk_speed
|
|
|
|
|
var default_pace: float = _rig.walk_speed if checked_style == "walk" else float(style_data.get("pace", 500.0))
|
|
|
|
|
_walk_speed_current = speed_override if speed_override > 0.0 else default_pace
|
|
|
|
|
_nav_agent.max_speed = _walk_speed_current
|
|
|
|
|
_walk_done = false
|
|
|
|
|
_arrival_emitted = false
|
|
|
|
|
_settling = false
|
|
|
|
|
_settle_waiting_for_ground = false
|
|
|
|
|
_walk_settle_elapsed = 0.0
|
|
|
|
|
_walk_settle_start_pose.clear()
|
|
|
|
|
_walk_settle_end_pose.clear()
|
|
|
|
|
_reset_foot_contacts()
|
|
|
|
|
var target_root: Vector2 = target + FOOT_OFFSET
|
|
|
|
|
if (target - (_rig.global_position - FOOT_OFFSET)).length() <= ARRIVE_DISTANCE:
|
|
|
|
|
_rig.global_position = target_root
|
|
|
|
|
_walking = false
|
|
|
|
|
_walk_done = true
|
|
|
|
|
_arrival_emitted = true
|
|
|
|
|
arrived.emit(_walk_target_feet)
|
|
|
|
|
return true
|
|
|
|
|
_nav_agent.target_position = target
|
|
|
|
|
var dx: float = target.x - _rig.global_position.x
|
|
|
|
|
if dx < -0.5:
|
|
|
|
@@ -337,14 +400,12 @@ func walk_to(target: Vector2, speed: float = -1.0) -> bool:
|
|
|
|
|
elif dx > 0.5:
|
|
|
|
|
_rig.set_facing_profile(StickmanRig.FacingProfile.RIGHT)
|
|
|
|
|
_rig._set_limb_mods_enabled(true)
|
|
|
|
|
if _rig._anim_player != null and is_instance_valid(_rig._anim_player) and _rig._anim_player.has_animation("walk_right"):
|
|
|
|
|
_rig._anim_player.play("walk_right")
|
|
|
|
|
if _rig._anim_player != null and is_instance_valid(_rig._anim_player) and _rig._anim_player.has_animation(_active_animation_name):
|
|
|
|
|
_rig._anim_player.play(_active_animation_name)
|
|
|
|
|
_walking = true
|
|
|
|
|
_walk_done = false
|
|
|
|
|
_walk_mode = "nav"
|
|
|
|
|
_walk_mode_latched = false
|
|
|
|
|
_walk_latch_probe_frames = 0
|
|
|
|
|
_walk_settle_frames = 0
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
func cancel_walk(restore_idle_pose: bool = true) -> void:
|
|
|
|
@@ -352,19 +413,24 @@ func cancel_walk(restore_idle_pose: bool = true) -> void:
|
|
|
|
|
return
|
|
|
|
|
var current_limb_rotations: Dictionary = _rig._capture_limb_rotations()
|
|
|
|
|
if _rig._anim_player != null and is_instance_valid(_rig._anim_player):
|
|
|
|
|
_rig._anim_player.stop(true)
|
|
|
|
|
_rig._anim_player.stop(false)
|
|
|
|
|
if _nav_agent != null and _nav_anchor != null:
|
|
|
|
|
_nav_agent.target_position = _nav_anchor.global_position
|
|
|
|
|
_walking = false
|
|
|
|
|
_settling = false
|
|
|
|
|
_settle_waiting_for_ground = false
|
|
|
|
|
_walk_done = false
|
|
|
|
|
_walk_mode_latched = false
|
|
|
|
|
_walk_latch_probe_frames = 0
|
|
|
|
|
_walk_settle_frames = 0
|
|
|
|
|
_walk_settle_elapsed = 0.0
|
|
|
|
|
_walk_settle_start_pose.clear()
|
|
|
|
|
_walk_settle_end_pose.clear()
|
|
|
|
|
_reset_foot_contacts()
|
|
|
|
|
if restore_idle_pose:
|
|
|
|
|
_rig._apply_idle_limb_solver_mode(current_limb_rotations)
|
|
|
|
|
|
|
|
|
|
func is_walking() -> bool:
|
|
|
|
|
return _walking
|
|
|
|
|
return _walking or _settling
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func ensure_grounded_walk_animation() -> void:
|
|
|
|
@@ -372,7 +438,7 @@ func ensure_grounded_walk_animation() -> void:
|
|
|
|
|
return
|
|
|
|
|
if terrain_motion != TerrainMotion.GROUNDED or blocks_walking():
|
|
|
|
|
return
|
|
|
|
|
if _rig._anim_player == null or not is_instance_valid(_rig._anim_player) or not _rig._anim_player.has_animation("walk_right"):
|
|
|
|
|
if _rig._anim_player == null or not is_instance_valid(_rig._anim_player) or not _rig._anim_player.has_animation(_active_animation_name):
|
|
|
|
|
return
|
|
|
|
|
if _terrain_paused_walk_animation:
|
|
|
|
|
if _rig._anim_player.is_playing():
|
|
|
|
@@ -380,7 +446,7 @@ func ensure_grounded_walk_animation() -> void:
|
|
|
|
|
else:
|
|
|
|
|
_resume_walk_animation()
|
|
|
|
|
elif not _rig._anim_player.is_playing():
|
|
|
|
|
_rig._anim_player.play("walk_right")
|
|
|
|
|
_rig._anim_player.play(_active_animation_name)
|
|
|
|
|
|
|
|
|
|
func get_foot_offset() -> Vector2:
|
|
|
|
|
return FOOT_OFFSET
|
|
|
|
@@ -439,21 +505,213 @@ func _update_walking(delta: float) -> void:
|
|
|
|
|
_rig.global_position.x = final_root.x
|
|
|
|
|
_finish_walk()
|
|
|
|
|
|
|
|
|
|
func _settle_walk_markers() -> void:
|
|
|
|
|
if _walk_settle_frames > 0:
|
|
|
|
|
_walk_settle_frames -= 1
|
|
|
|
|
_rig._restore_standing_markers()
|
|
|
|
|
func _settle_walk_markers(delta: float) -> void:
|
|
|
|
|
if not _settling or _rig == null:
|
|
|
|
|
return
|
|
|
|
|
if _rig.state != StickmanRig.RigState.ANIMATED:
|
|
|
|
|
cancel_walk(false)
|
|
|
|
|
return
|
|
|
|
|
if terrain_following_enabled and terrain_motion != TerrainMotion.GROUNDED:
|
|
|
|
|
_settle_waiting_for_ground = true
|
|
|
|
|
return
|
|
|
|
|
_walk_settle_elapsed = minf(_walk_settle_elapsed + delta, ARRIVAL_SETTLE_DURATION)
|
|
|
|
|
var weight: float = smoothstep(0.0, ARRIVAL_SETTLE_DURATION, _walk_settle_elapsed)
|
|
|
|
|
_apply_walk_settle_pose(weight)
|
|
|
|
|
if _walk_settle_elapsed >= ARRIVAL_SETTLE_DURATION:
|
|
|
|
|
_settling = false
|
|
|
|
|
_settle_waiting_for_ground = false
|
|
|
|
|
_walk_settle_start_pose.clear()
|
|
|
|
|
_walk_settle_end_pose.clear()
|
|
|
|
|
var final_limb_rotations: Dictionary = _rig._capture_limb_rotations()
|
|
|
|
|
_rig._apply_idle_limb_solver_mode(final_limb_rotations)
|
|
|
|
|
_walk_debug_settle_frames = 2 if _walk_debug_enabled() else 0
|
|
|
|
|
_walk_done = true
|
|
|
|
|
|
|
|
|
|
func _finish_walk() -> void:
|
|
|
|
|
var final_limb_rotations: Dictionary = _rig._capture_limb_rotations()
|
|
|
|
|
if not _walking or _arrival_emitted:
|
|
|
|
|
return
|
|
|
|
|
# Capture the evaluated cycle before stopping it so the settle can blend from
|
|
|
|
|
# the actual run pose rather than from the animation reset values.
|
|
|
|
|
_walk_settle_start_pose = _capture_walk_marker_pose()
|
|
|
|
|
_debug_walk_pose("ARRIVAL_RUN_POSE")
|
|
|
|
|
if _rig._anim_player != null and is_instance_valid(_rig._anim_player):
|
|
|
|
|
# Keep the evaluated run pose for the first frame of the settle blend.
|
|
|
|
|
_rig._anim_player.stop(true)
|
|
|
|
|
_walking = false
|
|
|
|
|
_rig._apply_idle_limb_solver_mode(final_limb_rotations)
|
|
|
|
|
_walk_settle_frames = 0
|
|
|
|
|
_walk_done = true
|
|
|
|
|
_settling = true
|
|
|
|
|
_settle_waiting_for_ground = false
|
|
|
|
|
_walk_settle_elapsed = 0.0
|
|
|
|
|
_arrival_emitted = true
|
|
|
|
|
_walk_done = false
|
|
|
|
|
_restore_walk_standing_pose()
|
|
|
|
|
_walk_settle_end_pose = _capture_walk_marker_pose()
|
|
|
|
|
_apply_walk_settle_pose(0.0)
|
|
|
|
|
if _walk_debug_enabled():
|
|
|
|
|
print("[WalkDebug] SETTLE_TARGETS start=%s end=%s" % [_walk_settle_start_pose, _walk_settle_end_pose])
|
|
|
|
|
_debug_walk_pose("SETTLE_BEGIN")
|
|
|
|
|
arrived.emit(_walk_target_feet)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _capture_walk_marker_pose() -> Dictionary:
|
|
|
|
|
var pose: Dictionary = {}
|
|
|
|
|
for marker_name: String in MOVE_MARKER_NAMES:
|
|
|
|
|
var marker: Marker2D = _rig._get_ik_marker(marker_name)
|
|
|
|
|
if marker == null:
|
|
|
|
|
continue
|
|
|
|
|
pose[marker_name] = {"position": marker.position, "rotation": marker.rotation}
|
|
|
|
|
for leg_name: String in ["Left_Leg", "Right_Leg"]:
|
|
|
|
|
var base: Marker2D = _leg_base_markers.get(leg_name) as Marker2D
|
|
|
|
|
if base != null:
|
|
|
|
|
pose["base:" + leg_name] = {"position": base.position, "rotation": base.rotation}
|
|
|
|
|
return pose
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _apply_walk_settle_pose(weight: float) -> void:
|
|
|
|
|
for pose_key: String in _walk_settle_end_pose:
|
|
|
|
|
var marker: Marker2D
|
|
|
|
|
if pose_key.begins_with("base:"):
|
|
|
|
|
marker = _leg_base_markers.get(pose_key.trim_prefix("base:")) as Marker2D
|
|
|
|
|
else:
|
|
|
|
|
marker = _rig._get_ik_marker(pose_key)
|
|
|
|
|
var start: Dictionary = _walk_settle_start_pose.get(pose_key, {})
|
|
|
|
|
var finish: Dictionary = _walk_settle_end_pose.get(pose_key, {})
|
|
|
|
|
if marker == null or start.is_empty() or finish.is_empty():
|
|
|
|
|
continue
|
|
|
|
|
marker.position = (start["position"] as Vector2).lerp(finish["position"] as Vector2, weight)
|
|
|
|
|
marker.rotation = lerpf(float(start["rotation"]), float(finish["rotation"]), weight)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _restore_walk_standing_pose() -> void:
|
|
|
|
|
_rig._restore_standing_markers()
|
|
|
|
|
var standing_pose: Dictionary = {}
|
|
|
|
|
var standing_animation: StringName = &""
|
|
|
|
|
if _rig.facing_profile == StickmanRig.FacingProfile.LEFT:
|
|
|
|
|
standing_animation = LEFT_STANDING_ANIMATION
|
|
|
|
|
elif _rig.facing_profile == StickmanRig.FacingProfile.RIGHT:
|
|
|
|
|
standing_animation = RIGHT_STANDING_ANIMATION
|
|
|
|
|
if not standing_animation.is_empty():
|
|
|
|
|
standing_pose = _sample_animation_first_frame(standing_animation)
|
|
|
|
|
_apply_walk_marker_pose(standing_pose)
|
|
|
|
|
for leg_name: String in ["Left_Leg", "Right_Leg"]:
|
|
|
|
|
var base: Marker2D = _leg_base_markers.get(leg_name) as Marker2D
|
|
|
|
|
var target: Marker2D = _rig._get_ik_marker(leg_name)
|
|
|
|
|
if base == null or target == null:
|
|
|
|
|
continue
|
|
|
|
|
var base_key := "base:" + leg_name
|
|
|
|
|
if standing_pose.has(base_key):
|
|
|
|
|
# The baked walk clip keys the animated base target. Seed the IK target
|
|
|
|
|
# from the same frame so it also works while terrain following is off.
|
|
|
|
|
target.position = base.position
|
|
|
|
|
else:
|
|
|
|
|
base.position = target.position
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _sample_animation_first_frame(animation_name: StringName) -> Dictionary:
|
|
|
|
|
var sampled: Dictionary = {}
|
|
|
|
|
if _rig == null or _rig._anim_player == null or not _rig._anim_player.has_animation(animation_name):
|
|
|
|
|
return sampled
|
|
|
|
|
var animation: Animation = _rig._anim_player.get_animation(animation_name)
|
|
|
|
|
for track_idx: int in animation.get_track_count():
|
|
|
|
|
var track_path := String(animation.track_get_path(track_idx))
|
|
|
|
|
var separator_idx := track_path.rfind(":")
|
|
|
|
|
if separator_idx < 0:
|
|
|
|
|
continue
|
|
|
|
|
var property_name := track_path.substr(separator_idx + 1)
|
|
|
|
|
if property_name != "position" and property_name != "rotation":
|
|
|
|
|
continue
|
|
|
|
|
var node_path := track_path.substr(0, separator_idx)
|
|
|
|
|
var pose_key := ""
|
|
|
|
|
if node_path.begins_with("IK_Targets/"):
|
|
|
|
|
pose_key = node_path.get_file()
|
|
|
|
|
elif node_path.begins_with("AnimationTargets/") and node_path.get_file().ends_with("_Base"):
|
|
|
|
|
pose_key = "base:" + node_path.get_file().trim_suffix("_Base")
|
|
|
|
|
if pose_key.is_empty():
|
|
|
|
|
continue
|
|
|
|
|
var key_count := animation.track_get_key_count(track_idx)
|
|
|
|
|
if key_count == 0:
|
|
|
|
|
continue
|
|
|
|
|
var first_key_idx := 0
|
|
|
|
|
for key_idx: int in range(1, key_count):
|
|
|
|
|
if animation.track_get_key_time(track_idx, key_idx) < animation.track_get_key_time(track_idx, first_key_idx):
|
|
|
|
|
first_key_idx = key_idx
|
|
|
|
|
var key_value: Variant = animation.track_get_key_value(track_idx, first_key_idx)
|
|
|
|
|
if not sampled.has(pose_key):
|
|
|
|
|
sampled[pose_key] = {}
|
|
|
|
|
var properties: Dictionary = sampled[pose_key]
|
|
|
|
|
properties[property_name] = key_value
|
|
|
|
|
return sampled
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _apply_walk_marker_pose(pose: Dictionary) -> void:
|
|
|
|
|
for pose_key: String in pose:
|
|
|
|
|
var marker: Marker2D
|
|
|
|
|
if pose_key.begins_with("base:"):
|
|
|
|
|
marker = _leg_base_markers.get(pose_key.trim_prefix("base:")) as Marker2D
|
|
|
|
|
else:
|
|
|
|
|
marker = _rig._get_ik_marker(pose_key)
|
|
|
|
|
if marker == null:
|
|
|
|
|
continue
|
|
|
|
|
var properties: Dictionary = pose[pose_key]
|
|
|
|
|
if properties.get("position") is Vector2:
|
|
|
|
|
marker.position = properties["position"]
|
|
|
|
|
if properties.get("rotation") is float or properties.get("rotation") is int:
|
|
|
|
|
marker.rotation = float(properties["rotation"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _resume_settle_after_terrain() -> void:
|
|
|
|
|
if not _settling or not _settle_waiting_for_ground or terrain_motion != TerrainMotion.GROUNDED:
|
|
|
|
|
return
|
|
|
|
|
_walk_settle_start_pose = _capture_walk_marker_pose()
|
|
|
|
|
_restore_walk_standing_pose()
|
|
|
|
|
_walk_settle_end_pose = _capture_walk_marker_pose()
|
|
|
|
|
_apply_walk_settle_pose(0.0)
|
|
|
|
|
_walk_settle_elapsed = 0.0
|
|
|
|
|
_settle_waiting_for_ground = false
|
|
|
|
|
if _walk_debug_enabled():
|
|
|
|
|
print("[WalkDebug] SETTLE_RESUMED start=%s end=%s" % [_walk_settle_start_pose, _walk_settle_end_pose])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _walk_debug_enabled() -> bool:
|
|
|
|
|
return OS.get_environment("STICKMAN_WALK_DEBUG") == "1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _debug_walk_pose(label: String) -> void:
|
|
|
|
|
if not _walk_debug_enabled() or _rig == null:
|
|
|
|
|
return
|
|
|
|
|
var markers: Dictionary = {}
|
|
|
|
|
for marker_name: String in MOVE_MARKER_NAMES:
|
|
|
|
|
var marker: Marker2D = _rig._get_ik_marker(marker_name)
|
|
|
|
|
if marker != null:
|
|
|
|
|
markers[marker_name] = marker.position
|
|
|
|
|
var bases: Dictionary = {}
|
|
|
|
|
for leg_name: String in ["Left_Leg", "Right_Leg"]:
|
|
|
|
|
var base: Marker2D = _leg_base_markers.get(leg_name) as Marker2D
|
|
|
|
|
if base != null:
|
|
|
|
|
bases[leg_name] = base.position
|
|
|
|
|
var bend_flags: Dictionary = {}
|
|
|
|
|
for joint: String in ["LeftArm", "RightArm", "LeftLeg", "RightLeg"]:
|
|
|
|
|
bend_flags[joint] = _rig.get_joint_bend_flipped(joint)
|
|
|
|
|
var tips: Dictionary = {}
|
|
|
|
|
var skeleton := _rig.get_node_or_null(NodePath("Skeleton2D")) as Skeleton2D
|
|
|
|
|
if skeleton != null:
|
|
|
|
|
var bone_paths: Dictionary = {
|
|
|
|
|
"Left_Upper_Arm": "Torso/LeftUpperArm",
|
|
|
|
|
"Left_Lower_Arm": "Torso/LeftUpperArm/LeftLowerArm",
|
|
|
|
|
"Right_Upper_Arm": "Torso/RightUpperArm",
|
|
|
|
|
"Right_Lower_Arm": "Torso/RightUpperArm/RightLowerArm",
|
|
|
|
|
"Left_Upper_Leg": "Torso/LeftUpperLeg",
|
|
|
|
|
"Left_Lower_Leg": "Torso/LeftUpperLeg/LeftLowerLeg",
|
|
|
|
|
"Right_Upper_Leg": "Torso/RightUpperLeg",
|
|
|
|
|
"Right_Lower_Leg": "Torso/RightUpperLeg/RightLowerLeg",
|
|
|
|
|
}
|
|
|
|
|
for bone_name: String in bone_paths:
|
|
|
|
|
var bone := skeleton.get_node_or_null(NodePath(String(bone_paths[bone_name]))) as Bone2D
|
|
|
|
|
if bone == null:
|
|
|
|
|
continue
|
|
|
|
|
var tip_world := bone.to_global(Vector2(bone.length, 0.0).rotated(deg_to_rad(bone.bone_angle)))
|
|
|
|
|
tips[bone_name] = _rig.to_local(tip_world)
|
|
|
|
|
print("[WalkDebug] %s facing=%d bends=%s root=%s markers=%s leg_bases=%s limb_rotations=%s limb_tips=%s" % [label, int(_rig.facing_profile), bend_flags, _rig.global_position, markers, bases, _rig._capture_limb_rotations(), tips])
|
|
|
|
|
|
|
|
|
|
func begin_terrain_fall() -> void:
|
|
|
|
|
if _rig == null:
|
|
|
|
|
return
|
|
|
|
@@ -562,10 +820,24 @@ func probe_ground_y(world_x: float, top_y: float, bottom_y: float, exclude: Arra
|
|
|
|
|
func _stance_weight(leg_name: String) -> float:
|
|
|
|
|
if not _walking or _rig._anim_player == null:
|
|
|
|
|
return 1.0
|
|
|
|
|
var phase: float = fmod(_rig._anim_player.get_current_animation_position(), WALK_CYCLE_DURATION)
|
|
|
|
|
var phase: float = fmod(_rig._anim_player.get_current_animation_position(), _active_cycle_duration)
|
|
|
|
|
if _active_move_style == ACTION_REGISTRY.RUN:
|
|
|
|
|
var contact_start: float = RUN_RIGHT_CONTACT_START if leg_name == "Right_Leg" else RUN_LEFT_CONTACT_START
|
|
|
|
|
return _run_contact_weight(phase, contact_start)
|
|
|
|
|
var right_weight: float = 0.5 + 0.5 * cos(TAU * phase / WALK_CYCLE_DURATION)
|
|
|
|
|
return right_weight if leg_name == "Right_Leg" else 1.0 - right_weight
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _run_contact_weight(phase: float, contact_start: float) -> float:
|
|
|
|
|
var cycle_phase: float = fposmod(phase, RUN_CYCLE_DURATION)
|
|
|
|
|
var contact_phase: float = fposmod(cycle_phase - contact_start, RUN_CYCLE_DURATION)
|
|
|
|
|
if contact_phase >= RUN_CONTACT_DURATION:
|
|
|
|
|
return 0.0
|
|
|
|
|
var fade: float = minf(RUN_CONTACT_EDGE_FADE, RUN_CONTACT_DURATION * 0.5)
|
|
|
|
|
var fade_in: float = smoothstep(0.0, fade, contact_phase)
|
|
|
|
|
var fade_out: float = 1.0 - smoothstep(RUN_CONTACT_DURATION - fade, RUN_CONTACT_DURATION, contact_phase)
|
|
|
|
|
return clampf(fade_in * fade_out, 0.0, 1.0)
|
|
|
|
|
|
|
|
|
|
func _update_terrain_following(delta: float) -> void:
|
|
|
|
|
if not terrain_following_enabled or _rig.state != StickmanRig.RigState.ANIMATED or _leg_base_markers.size() != 2:
|
|
|
|
|
terrain_pelvis_offset = 0.0
|
|
|
|
|