using UnityEngine; namespace Unity.Cinemachine.Samples { /// /// Add-on for SimplePlayerController that controls animation for the Cameron character. /// It is hardcoded specifically to drive the CameronSimpleController animation controller asset. /// If the SimplePlayerController behaviour is present, this behaviour will work with it, otherwise /// it will monitor player velocity and apply the appropriate animation based on player movement. /// [RequireComponent(typeof(Animator))] public class SimplePlayerAnimator : MonoBehaviour { [Tooltip("Tune this to the animation in the model: feet should not slide when walking at this speed")] public float NormalWalkSpeed = 1.7f; [Tooltip("Tune this to the animation in the model: feet should not slide when sprinting at this speed")] public float NormalSprintSpeed = 5; [Tooltip("Never speed up the sprint animation more than this, to avoid absurdly fast movement")] public float MaxSprintScale = 1.4f; [Tooltip("Scale factor for the overall speed of the jump animation")] public float JumpAnimationScale = 0.65f; Animator m_Animator; SimplePlayerControllerBase m_Controller; Vector3 m_PreviousPosition; // used if m_Controller == null or disabled bool m_WasWalking; bool m_WasRunning; const float k_IdleThreshold = 0.2f; void Start() { m_PreviousPosition = transform.position; TryGetComponent(out m_Animator); m_Controller = GetComponentInParent(); if (m_Controller != null) { // Install our callbacks to handle jump and animation based on velocity m_Controller.StartJump += () => m_Animator.SetTrigger("Jump"); m_Controller.EndJump += () => m_Animator.SetTrigger("Land"); m_Controller.PostUpdate += (vel, jumpAnimationScale) => UpdateAnimation(vel, jumpAnimationScale); } } // We use LateUpdate so we normally don't have to worry about script execution order: // we can assume that the player has already been moved. void LateUpdate() { // In no-controller mode, we monitor the player's motion and deduce the appropriate animation. // We don't support jumping in this mode. if (m_Controller == null || !m_Controller.enabled) { // Get velocity in player-local coords var pos = transform.position; var vel = Quaternion.Inverse(transform.rotation) * (pos - m_PreviousPosition) / Time.deltaTime; m_PreviousPosition = pos; UpdateAnimation(vel, 1); } } // Set animation params for current velocity void UpdateAnimation(Vector3 vel, float jumpAnimationScale) { vel.y = 0; // we don't consider vertical movement var speed = vel.magnitude; // Hysteresis reduction bool isRunning = speed > NormalWalkSpeed * 2 + (m_WasRunning ? -0.15f : 0.15f); bool isWalking = !isRunning && speed > k_IdleThreshold + (m_WasWalking ? -0.05f : 0.05f); m_WasWalking = isWalking; m_WasRunning = isRunning; // Set the normalized direction of motion and scale the animation speed to match motion speed var dir = speed > k_IdleThreshold ? vel / speed : Vector3.zero; var motionScale = isWalking ? speed / NormalWalkSpeed : 1; // We scale the sprint animation speed to loosely match the actual speed, but we cheat // at the high end to avoid making the animation look ridiculous if (isRunning) motionScale = (speed < NormalSprintSpeed) ? speed / NormalSprintSpeed : Mathf.Min(MaxSprintScale, 1 + (speed - NormalSprintSpeed) / (3 * NormalSprintSpeed)); m_Animator.SetFloat("DirX", dir.x); m_Animator.SetFloat("DirZ", dir.z); m_Animator.SetFloat("MotionScale", motionScale); m_Animator.SetBool("Walking", isWalking); m_Animator.SetBool("Running", isRunning); m_Animator.SetFloat("JumpScale", JumpAnimationScale * jumpAnimationScale); } } }