73 lines
2.5 KiB
GDScript
73 lines
2.5 KiB
GDScript
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.0 ## peak knee bend, radians (0.0 for rigid placeholder limbs so feet/shoes don't dislocate)
|
|
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))
|