extends Node ## Procedural locomotion for the placeholder blocky avatar. ## ## There are no animation clips yet -- this just swings the upper arm/leg bones ## around their local X by a sine wave whose phase advances with distance ## travelled, bends the knees on the rear half of the stride, and eases the ## whole thing back to the rest pose when the character stops. ## ## The owner sets `speed` (horizontal m/s) every frame; setup() is given the ## avatar's Skeleton3D. Bones are addressed by canonical name, so this works ## for the local player and for snapshot-driven remote players alike. const STRIDE := 0.42 ## peak hip swing, radians (~24 deg) const ARM_SWING := 0.32 ## peak shoulder swing, radians const KNEE := 0.5 ## peak knee bend, radians const CYCLE := 1.9 ## phase (radians) advanced per metre travelled const BLEND := 12.0 ## ease rate toward the target pose const MOVE_EPS := 0.35 ## m/s below which we treat the avatar as idle var speed: float = 0.0 var _skel: Skeleton3D = null var _idx := {} # bone name -> skeleton index var _phase := 0.0 var _amount := 0.0 # 0 = idle, 1 = full walk const _BONES := [ "LeftUpperLeg", "RightUpperLeg", "LeftLowerLeg", "RightLowerLeg", "LeftUpperArm", "RightUpperArm", ] func setup(skeleton: Skeleton3D) -> void: _skel = skeleton _idx.clear() if skeleton == null: return for b in _BONES: var i := skeleton.find_bone(b) if i != -1: _idx[b] = i func _process(delta: float) -> void: if not is_instance_valid(_skel) or _idx.is_empty(): return var moving := speed > MOVE_EPS _amount = lerpf(_amount, 1.0 if moving else 0.0, clampf(BLEND * delta, 0.0, 1.0)) _phase += maxf(speed, 0.0) * delta * CYCLE if _phase > TAU: _phase -= TAU var s := sin(_phase) var swing := s * STRIDE * _amount var arm := s * ARM_SWING * _amount _pitch("LeftUpperLeg", swing) _pitch("RightUpperLeg", -swing) _pitch("LeftUpperArm", -arm) _pitch("RightUpperArm", arm) # Knee folds only while that leg is swinging back (negative pitch phase). _pitch("LeftLowerLeg", maxf(0.0, -s) * KNEE * _amount) _pitch("RightLowerLeg", maxf(0.0, s) * KNEE * _amount) func _pitch(bone: String, angle: float) -> void: if not _idx.has(bone): return var i: int = _idx[bone] var rest_q := _skel.get_bone_rest(i).basis.get_rotation_quaternion() _skel.set_bone_pose_rotation(i, rest_q * Quaternion(Vector3.RIGHT, angle))