public partial class AnimationController : Node3D { [Export] public Skeleton3D Skeleton { get; set; } [Export] public BoneAttachment3D RootBone { get; set; } // Contains all the bones in the skeleton, keyed by name (e.g. "LowerArm_L"). Dictionary _bones = []; // Whether the player's body is currently turning to match up with the camera rotation. bool _isTurning = false; // Current amount the body is turned due to walking sideways. float _bodyYaw = 0.0f; CharacterBody3D _player; MovementController _movementController; CameraController _cameraController; Camera3D _camera; Transform3D _cameraDefaultTransform; AnimationTree _animTree; Animation _walkForwardAnim; Animation _walkBackwardAnim; public override void _Ready() { _player = GetParent(); _movementController = _player.GetNode("MovementController"); _cameraController = _player.GetNode("CameraController"); _camera = _cameraController.Camera; _cameraDefaultTransform = _camera.Transform; _animTree = GetNode("AnimationTree"); _walkForwardAnim = _animTree.GetAnimation("walk_forward"); _walkBackwardAnim = _animTree.GetAnimation("walk_backward"); void AddBone(BoneAttachment3D bone) { bone.OverridePose = true; _bones[bone.Name] = bone; } AddBone(RootBone); foreach (var child in RootBone.FindChildren("*", "BoneAttachment3D")) AddBone((BoneAttachment3D)child); } public override void _Process(double delta) { ResetTransforms(); HandleTurning(delta); HandleLookingAnimation(delta); HandleWalkingAnimation(delta); } void ResetTransforms() { foreach (var bone in _bones.Values) bone.Transform = Skeleton.GetBonePose(bone.BoneIdx); _camera.Transform = _cameraDefaultTransform; } void HandleTurning(double delta) { const float TurnBegin = 60.0f; // Start turning when camera is rotated this much. const float TurnEnd = 5.0f; // Stop turning when body is this close to camera rotation. const float TurnSpeed = 6.0f; var yaw = _cameraController.CurrentYaw; // Camera yaw relative to player yaw. var isMoving = _movementController.RealMoveSpeed > 0.01f; _isTurning = isMoving || (Abs(yaw) > DegToRad(TurnBegin)); if (_isTurning) { var yawDelta = Sign(yaw) * Min(Abs(yaw), Abs(yaw) * TurnSpeed * (float)delta); _cameraController.CurrentYaw -= (float)yawDelta; _player.RotateY(yawDelta); if (Abs(_cameraController.CurrentYaw) < DegToRad(TurnEnd)) _isTurning = false; } } void HandleLookingAnimation(double delta) { const float PitchFactorLowerBody = 0.05f; const float PitchFactorUpperBody = 0.20f; const float PitchFactorNeck = 0.25f; const float PitchFactorHead = 0.35f; var pitch = _cameraController.CurrentPitch; _bones["LowerBody"].RotateX(pitch * PitchFactorLowerBody); _bones["UpperBody"].RotateX(-pitch * PitchFactorUpperBody); _bones["Neck"].RotateX(-pitch * PitchFactorNeck); _bones["Head"].RotateX(-pitch * PitchFactorHead); _bones["UpperArm_L"].RotateX(pitch * (PitchFactorLowerBody + PitchFactorUpperBody) / 2); _bones["UpperArm_R"].RotateX(pitch * (PitchFactorLowerBody + PitchFactorUpperBody) / 2); _camera.RotateX(pitch * (1 - PitchFactorLowerBody - PitchFactorUpperBody - PitchFactorNeck - PitchFactorHead)); const float YawFactorLowerBody = 0.06f; const float YawFactorUpperBody = 0.18f; const float YawFactorNeck = 0.2f; const float YawFactorHead = 0.3f; var yaw = _cameraController.CurrentYaw; _bones["LowerBody"].GlobalRotate(Vector3.Up, yaw * YawFactorLowerBody); _bones["UpperBody"].GlobalRotate(Vector3.Up, yaw * YawFactorUpperBody); _bones["Neck"].GlobalRotate(Vector3.Up, yaw * YawFactorNeck); _bones["Head"].GlobalRotate(Vector3.Up, yaw * YawFactorHead); _camera.GlobalRotate(Vector3.Up, yaw * (1 - YawFactorLowerBody - YawFactorUpperBody - YawFactorNeck - YawFactorHead)); // How much of the "ideal" camera rotation (rather than animation rotation) should be applied. const float CameraFactorIdealPitch = 0.7f; const float CameraFactorIdealYaw = 0.8f; const float CameraFactorIdealRoll = 0.9f; var global_yaw = _player.Rotation.Y + yaw; var cameraRotation = _camera.GlobalRotation; cameraRotation.X = LerpAngle(cameraRotation.X, pitch, CameraFactorIdealPitch); cameraRotation.Y = LerpAngle(cameraRotation.Y, global_yaw, CameraFactorIdealYaw); cameraRotation.Z = LerpAngle(cameraRotation.Z, 0, CameraFactorIdealRoll); _camera.GlobalRotation = cameraRotation; } void HandleWalkingAnimation(double delta) { var input = Input.GetVector("move_strafe_left", "move_strafe_right", "move_forward", "move_back"); var isOnFloor = _movementController.TimeSinceOnFloor < 0.25f; var isMoving = _movementController.RealMoveSpeed > 0.01f; var isMovingForward = input.Y <= 0; var walkState = (isOnFloor && isMoving) ? "move" : "idle"; var walkDirection = isMovingForward ? "forward" : "backward"; var walkSpeed = _movementController.RealMoveSpeed / _movementController.MaxSpeed; var targetBodyYaw = -(isMovingForward ? Vector2.Up : Vector2.Down).AngleTo(input); _animTree.Set("parameters/walk_state/transition_request", walkState); _animTree.Set("parameters/walk_direction/transition_request", walkDirection); var prevWalkSpeed = (float)_animTree.Get("parameters/walk_speed/blend_amount"); _animTree.Set("parameters/walk_speed/blend_amount", Lerp(prevWalkSpeed, walkSpeed, 10 * (float)delta)); const float YAW_FACTOR_LOWER_BODY = 0.25f; const float YAW_FACTOR_UPPER_BODY = 0.25f; const float YAW_FACTOR_NECK = 0.50f; _bodyYaw += (targetBodyYaw - _bodyYaw) * (float)delta * 6; _bones["Root"].GlobalRotate(Vector3.Up, _bodyYaw); _bones["LowerBody"].GlobalRotate(Vector3.Up, -_bodyYaw * YAW_FACTOR_LOWER_BODY); _bones["UpperBody"].GlobalRotate(Vector3.Up, -_bodyYaw * YAW_FACTOR_UPPER_BODY); _bones["Neck"].GlobalRotate(Vector3.Up, -_bodyYaw * YAW_FACTOR_NECK); } }