Inventory management focused game written in Godot / C#
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
8.0 KiB

public partial class AnimationController : Node3D
{
[Export] public Skeleton3D Skeleton { get; set; }
// Contains all the bones in the skeleton, keyed by name (e.g. "LowerArm_L").
Dictionary<string, BoneAttachment3D> _bones = [];
bool _isTurning = false; // Whether the player's body is currently turning to match up with the camera rotation.
float _bodyYaw = 0.0f; // Current amount the body is turned due to walking sideways.
Player _player;
Transform3D _cameraDefaultTransform;
AnimationTree _animTree;
Animation _walkForwardAnim;
Animation _walkBackwardAnim;
BoneAttachment3D _rootBone;
public override void _Ready()
{
_player = GetParent<Player>();
_animTree = GetNode<AnimationTree>("AnimationTree");
_walkForwardAnim = _animTree.GetAnimation("walk_forward");
_walkBackwardAnim = _animTree.GetAnimation("walk_backward");
_rootBone = GetNode<BoneAttachment3D>("Root");
foreach (var bone in FindChildren("*").OfType<BoneAttachment3D>())
{ bone.OverridePose = true; _bones[bone.Name] = bone; }
// We disable the AnimationTree while in the editor so our
// BoneAttackment3D nodes don't get updated, resulting in
// those changes being picked up by version control.
_animTree.Active = true;
}
public override void _Process(double delta)
{
ResetTransforms();
HandleTurning(delta);
HandleLookingAnimation(delta);
HandleWalkingAnimation(delta);
HandleHoldingAnimation(delta);
}
void ResetTransforms()
{
foreach (var bone in _bones.Values) {
bone.Transform = Skeleton.GetBonePose(bone.BoneIdx);
if (bone == _rootBone) {
// This is a dirty hack that makes sure the `Root` bone has the same
// Transform as it would if it was parented to `Player/Model/Skeleton`.
var skel = Skeleton.GetParent<Node3D>();
var model = skel.GetParent<Node3D>();
bone.Transform = model.Transform * skel.Transform * bone.Transform;
}
}
if (_player.Camera.Camera is Camera3D camera)
camera.Transform = _player.Camera.DefaultTransform;
}
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 = _player.Camera.CurrentYaw; // Camera yaw relative to player yaw.
var movement = _player.Movement;
var isWalking = movement.LocalMoveVector.Length() > movement.MaxSpeed / 4;
_isTurning = isWalking || (Abs(yaw) > DegToRad(TurnBegin));
if (_isTurning) {
var yawDelta = Sign(yaw) * Min(Abs(yaw), Abs(yaw) * TurnSpeed * (float)delta);
_player.Camera.CurrentYaw -= (float)yawDelta;
_player.RotateY(yawDelta);
if (Abs(_player.Camera.CurrentYaw) < DegToRad(TurnEnd))
_isTurning = false;
}
}
void HandleLookingAnimation(double delta)
{
var camera = _player.Camera.Camera;
var pitch = _player.Camera.CurrentPitch;
var yaw = _player.Camera.CurrentYaw;
const float PitchFactorLowerBody = 0.05f;
const float PitchFactorUpperBody = 0.20f;
const float PitchFactorNeck = 0.25f;
const float PitchFactorHead = 0.35f;
_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;
_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));
if (camera != null) {
// 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 globalYaw = yaw + _player.Rotation.Y;
var rot = camera.GlobalRotation;
// FIXME: This doesn't apply correctly when looking up or down.
rot.X = LerpAngle(rot.X, pitch, CameraFactorIdealPitch);
rot.Y = LerpAngle(rot.Y, globalYaw, CameraFactorIdealYaw);
rot.Z = LerpAngle(rot.Z, 0, CameraFactorIdealRoll);
camera.GlobalRotation = rot;
}
}
float _yawFactorLowerBody = 0.15f;
float _yawFactorUpperBody = 0.20f;
float _yawFactorNeck = 0.45f;
void HandleWalkingAnimation(double delta)
{
const float ForwardAngle = 95.0f;
var movement = _player.Movement;
var localAngle = movement.LocalMoveAngle;
var isOnFloor = movement.TimeSinceOnFloor < 0.25f;
var isMovingForward = Abs(localAngle) <= DegToRad(ForwardAngle);
var walkState = (isOnFloor && movement.IsMoving) ? "move" : "idle";
var walkDirection = isMovingForward ? "forward" : "backward";
var walkSpeed = movement.LocalMoveVector.Length() / movement.MaxSpeed;
const string WalkStateParam = "parameters/walk_state/transition_request";
const string WalkDirectionParam = "parameters/walk_direction/transition_request";
const string WalkSpeedParam = "parameters/walk_speed/blend_amount";
_animTree.Set(WalkStateParam, walkState);
_animTree.Set(WalkDirectionParam, walkDirection);
var prevWalkSpeed = (float)_animTree.Get(WalkSpeedParam);
_animTree.Set(WalkSpeedParam, Lerp(prevWalkSpeed, walkSpeed, 10 * (float)delta));
const float EmptyYawFactorLowerBody = 0.15f;
const float EmptyYawFactorUpperBody = 0.20f;
const float EmptyYawFactorNeck = 0.45f;
const float HoldingYawFactorLowerBody = 0.35f;
const float HoldingYawFactorUpperBody = 0.25f;
const float HoldingYawFactorNeck = 0.10f;
// TODO: This should probably be using delta somehow, but I'm not sure how.
var held = _player.Pickup.HasItemsHeld;
_yawFactorLowerBody = (_yawFactorLowerBody + (held ? HoldingYawFactorLowerBody : EmptyYawFactorLowerBody)) / 2;
_yawFactorUpperBody = (_yawFactorLowerBody + (held ? HoldingYawFactorUpperBody : EmptyYawFactorUpperBody)) / 2;
_yawFactorNeck = (_yawFactorLowerBody + (held ? HoldingYawFactorNeck : EmptyYawFactorNeck )) / 2;
if (movement.IsMoving) {
var targetBodyYaw = localAngle;
if (!isMovingForward) targetBodyYaw -= Sign(localAngle) * Tau / 2;
_bodyYaw += (targetBodyYaw - _bodyYaw) * (float)delta * 6;
} else
_bodyYaw -= _bodyYaw * (float)delta * 2;
_bones["Root" ].GlobalRotate(Vector3.Up, _bodyYaw * (_yawFactorLowerBody + _yawFactorUpperBody + _yawFactorNeck));
_bones["LowerBody"].GlobalRotate(Vector3.Up, -_bodyYaw * _yawFactorLowerBody);
_bones["UpperBody"].GlobalRotate(Vector3.Up, -_bodyYaw * _yawFactorUpperBody);
_bones["Neck" ].GlobalRotate(Vector3.Up, -_bodyYaw * _yawFactorNeck);
}
Vector3? _defaultPickupPosition;
void HandleHoldingAnimation(double delta)
{
var posWristR = _bones["Wrist_R"].GlobalPosition;
var posWristL = _bones["Wrist_L"].GlobalPosition;
var bodyYaw = _bones["UpperBody"].GlobalRotation.Y;
var posPickup = _defaultPickupPosition ??= _player.Pickup.Position;
posPickup = _player.GlobalPosition + posPickup.Rotated(Vector3.Up, Tau / 2 + bodyYaw);
_player.Pickup.GlobalPosition = (posPickup + posWristR + posWristL) / 3;
_player.Pickup.GlobalRotation = _player.Pickup.GlobalRotation with { Y = bodyYaw };
const string IsHoldingParam = "parameters/is_holding/blend_amount";
var isHolding = _player.Pickup.HasItemsHeld ? 0.9f : 0.0f;
var prevIsHolding = (float)_animTree.Get(IsHoldingParam);
_animTree.Set(IsHoldingParam, Lerp(prevIsHolding, isHolding, 8 * (float)delta));
}
}