namespace TPSBR { using UnityEngine; using Fusion.Addons.KCC; using Fusion.Addons.AnimationController; public sealed class MoveState : MultiBlendTreeState { // PUBLIC MEMBERS public Vector3 FixedDirection => _fixedDirection; public float FixedMagnitude => _fixedMagnitude; public Vector3 InterpolatedDirection => _interpolatedDirection; public float InterpolatedMagnitude => _interpolatedMagnitude; // PRIVATE MEMBERS [SerializeField] private float _minAnimationSpeed = 0.25f; [SerializeField] private float _maxAnimationSpeed = 2.0f; [SerializeField] private float _directionSmoothingSpeed = 16.0f; [SerializeField] private float _magnitudeSmoothingSpeed = 16.0f; private KCC _kcc; private Agent _agent; private Weapons _weapons; private Vector3 _fixedDirection; private float _fixedMagnitude; private Vector3 _interpolatedDirection; private float _interpolatedMagnitude; // PUBLIC METHODS public float GetBaseSpeed(Vector2 localNormalizedDirection, float multiplier) { if (multiplier == default) { multiplier = GetMultiplier(); } return GetMaxBaseSpeed(localNormalizedDirection) * multiplier; } // MultiBlendTreeState INTERFACE public override Vector2 GetBlendPosition(bool interpolated) { Vector3 direction = interpolated == true ? _interpolatedDirection : _fixedDirection; float magnitude = interpolated == true ? _interpolatedMagnitude : _fixedMagnitude; Vector3 blendPosition = _kcc.transform.InverseTransformDirection(direction).XZ0().normalized * magnitude; if (_agent != null && _agent.Runner != null && _agent.LeftSide == true) { blendPosition.x = -blendPosition.x; } return blendPosition; } public override float GetSpeedMultiplier() { KCCData kccData = _kcc.Object.IsInSimulation == true ? _kcc.FixedData : _kcc.Data; float maxBaseSpeed = GetMaxBaseSpeed((Quaternion.Inverse(kccData.TransformRotation) * kccData.KinematicDirection).XZ0().normalized); if (maxBaseSpeed > 0.0f && kccData.RealSpeed > maxBaseSpeed) return Mathf.Clamp(kccData.RealSpeed / maxBaseSpeed, _minAnimationSpeed, _maxAnimationSpeed); return 1.0f; } public override int GetSetID() { int currentWeaponSlot = _weapons.CurrentWeaponSlot; if (currentWeaponSlot > 2) { currentWeaponSlot = 1; // For grenades we use pistol set } if (currentWeaponSlot < 0) return 0; return currentWeaponSlot; } // AnimationState INTERFACE protected override void OnInitialize() { base.OnInitialize(); _kcc = Controller.GetComponentNoAlloc(); _agent = Controller.GetComponentNoAlloc(); _weapons = Controller.GetComponentNoAlloc(); } protected override void OnSpawned() { base.OnSpawned(); _fixedDirection = Vector3.forward; _fixedMagnitude = 0.0f; _interpolatedDirection = Vector3.forward; _interpolatedMagnitude = 0.0f; } protected override void OnFixedUpdate() { SetFixedProperties(); base.OnFixedUpdate(); } protected override void OnInterpolate() { SetInterpolatedProperties(); base.OnInterpolate(); } // PRIVATE METHODS private void SetFixedProperties() { KCCData kccFixedData = _kcc.FixedData; Vector3 targetDirection = _fixedDirection; float targetMagnitude; if (Controller.HasInputAuthority == true || Controller.HasStateAuthority == true) { if (kccFixedData.InputDirection.OnlyXZ().IsAlmostZero(0.025f) == false) { targetDirection = kccFixedData.InputDirection.OnlyXZ().normalized; } else if (kccFixedData.KinematicDirection.OnlyXZ().IsAlmostZero(0.025f) == false) { targetDirection = kccFixedData.KinematicDirection.OnlyXZ().normalized; } else if (kccFixedData.DesiredVelocity.OnlyXZ().IsAlmostZero(0.025f) == false) { targetDirection = kccFixedData.DesiredVelocity.OnlyXZ().normalized; } float realVelocityMagnitude = kccFixedData.RealVelocity.OnlyXZ().magnitude; float desiredVelocityMagnitude = kccFixedData.DesiredVelocity.OnlyXZ().magnitude; float kinematicVelocityMagnitude = kccFixedData.KinematicVelocity.OnlyXZ().magnitude; targetMagnitude = Mathf.Min(realVelocityMagnitude, Mathf.Max(kinematicVelocityMagnitude, desiredVelocityMagnitude)); } else { if (kccFixedData.RealVelocity.OnlyXZ().IsAlmostZero(0.025f) == false) { targetDirection = kccFixedData.RealVelocity.OnlyXZ().normalized; } targetMagnitude = kccFixedData.RealSpeed; } _fixedDirection = targetDirection; _fixedMagnitude = targetMagnitude; } private void SetInterpolatedProperties() { KCCData kccFixedData = _kcc.FixedData; KCCData kccRenderData = _kcc.RenderData; Vector3 targetDirection = _interpolatedDirection; float targetMagnitude; if (Controller.HasInputAuthority == true || Controller.HasStateAuthority == true) { if (kccRenderData.InputDirection.OnlyXZ().IsAlmostZero(0.025f) == false) { targetDirection = kccRenderData.InputDirection.OnlyXZ().normalized; } else if (kccRenderData.KinematicDirection.OnlyXZ().IsAlmostZero(0.025f) == false) { targetDirection = kccRenderData.KinematicDirection.OnlyXZ().normalized; } else if (kccFixedData.DesiredVelocity.OnlyXZ().IsAlmostZero(0.025f) == false) { targetDirection = kccFixedData.DesiredVelocity.OnlyXZ().normalized; } float directionDot = Vector3.Dot(targetDirection, _interpolatedDirection); if (directionDot < -0.5f) { const float angleTolerance = 25.0f; float angle = Vector3.SignedAngle(_kcc.transform.forward, _interpolatedDirection, Vector3.up); if (angle.AlmostEquals(90.0f, angleTolerance) == true) { targetDirection = Quaternion.Euler(0.0f, -90.0f, 0.0f) * _interpolatedDirection; } else if (angle.AlmostEquals(-90.0f, angleTolerance) == true) { targetDirection = Quaternion.Euler(0.0f, 90.0f, 0.0f) * _interpolatedDirection; } else if (angle.AlmostEquals(0.0f, angleTolerance) == true) { targetDirection = Quaternion.Euler(0.0f, -90.0f, 0.0f) * _interpolatedDirection; } else if (Mathf.Abs(angle).AlmostEquals(180.0f, angleTolerance) == true) { targetDirection = Quaternion.Euler(0.0f, 90.0f, 0.0f) * _interpolatedDirection; } } float realVelocityMagnitude = kccFixedData.RealVelocity.OnlyXZ().magnitude; float desiredVelocityMagnitude = kccFixedData.DesiredVelocity.OnlyXZ().magnitude; float kinematicVelocityMagnitude = kccFixedData.KinematicVelocity.OnlyXZ().magnitude; targetMagnitude = Mathf.Min(realVelocityMagnitude, Mathf.Max(kinematicVelocityMagnitude, desiredVelocityMagnitude)); } else { if (kccRenderData.RealVelocity.OnlyXZ().IsAlmostZero(0.025f) == false) { targetDirection = kccRenderData.RealVelocity.OnlyXZ().normalized; targetMagnitude = kccRenderData.RealSpeed; } else { targetMagnitude = 0.0f; } } _interpolatedDirection = Vector3.Slerp(_interpolatedDirection, targetDirection, _directionSmoothingSpeed * Time.deltaTime).normalized; _interpolatedMagnitude = Mathf.Lerp(_interpolatedMagnitude, targetMagnitude, _magnitudeSmoothingSpeed * Time.deltaTime); } private float GetMaxBaseSpeed(Vector2 localNormalizedDirection) { if (localNormalizedDirection == Vector2.zero) return 0.0f; int setID = GetSetID(); if (setID < 0) return 0.0f; BlendTreeNode[] nodes = Sets[setID].Nodes; int fromNodeIndex; int toNodeIndex; float alpha; float angle = Vector2.Angle(localNormalizedDirection, Vector2.up); if (angle >= 0.0f) { if (angle <= 45.0f) { fromNodeIndex = 1; toNodeIndex = 6; alpha = Mathf.Clamp01((angle - 0.0f) / 45.0f); } else if (angle <= 90.0f) { fromNodeIndex = 6; toNodeIndex = 4; alpha = Mathf.Clamp01((angle - 45.0f) / 45.0f); } else if (angle <= 135.0f) { fromNodeIndex = 4; toNodeIndex = 8; alpha = Mathf.Clamp01((angle - 90.0f) / 45.0f); } else { fromNodeIndex = 8; toNodeIndex = 2; alpha = Mathf.Clamp01((angle - 135.0f) / 45.0f); } } else { if (angle >= -45.0f) { fromNodeIndex = 1; toNodeIndex = 5; alpha = Mathf.Clamp01((angle + 0.0f) / -45.0f); } else if (angle >= -90.0f) { fromNodeIndex = 5; toNodeIndex = 3; alpha = Mathf.Clamp01((angle + 45.0f) / -45.0f); } else if (angle >= -135.0f) { fromNodeIndex = 3; toNodeIndex = 7; alpha = Mathf.Clamp01((angle + 90.0f) / -45.0f); } else { fromNodeIndex = 7; toNodeIndex = 2; alpha = Mathf.Clamp01((angle + 135.0f) / -45.0f); } } return Mathf.Lerp(nodes[fromNodeIndex].Position.magnitude, nodes[toNodeIndex].Position.magnitude, alpha); } private float GetMultiplier() { switch (_weapons.CurrentWeaponSlot) { case 0: { return 1.0f; } case 1: { return 0.95f; } case 2: { return 0.9f; } } return 0.95f; } } }