3fb45f0ef2
checkpoint / checkpoint (push) Successful in 1m47s
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
73 lines
2.4 KiB
GDScript
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))
|