Files
modbison/scripts/avatar/limb_animator.gd
T
daddy 3fb45f0ef2
checkpoint / checkpoint (push) Successful in 1m47s
Avatar: tune Roblox proportions + walk cycle from real rendered screenshots
Now that cloudbison can render (Radeon GPU via Xvfb, or llvmpipe software),
added scripts/tools/screenshot_rig.gd + scenes/dev/ so a scene can be booted,
run a few frames, and its rendered frame saved to PNG -- caught three things
the headless smoke never could:

- limbs floated detached from the torso: widened the torso and overlapped the
  arm/leg boxes into it, and pulled limb depth in behind the torso front so
  they stop z-fighting.
- proportions were scrawny: reworked to Roblox R6 ratios (2-stud torso, 2-stud
  arms/legs, 1-stud head) with the head clearly pegged on top.
- the walk swing was a 40-deg lunge: STRIDE 0.7->0.42 rad, separate 0.32 arm
  swing, KNEE 1.0->0.5. Reads as a walk now, not a crouch.

test_level lighting was near-black on the shaded side (ambient was disabled) --
colour ambient at 0.8 + a stronger key light, so the avatar reads in-world.

avatar_preview.tscn (AGP_PREVIEW_WALK=1) renders the assembled avatar walking
in place for quick visual checks.

Verified: avatar smoke 14/14 source + exported pack; fresh-clone boot PASS;
eyeballed in rendered screenshots (game scene + walk sequence).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-06 14:06:46 +00:00

73 lines
2.4 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.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 _skel == null 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))