Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Resolved Animations have random delays with varying windows from instant to 1-5 seconds.

Discussion in 'Animation' started by Draugrlord, Jan 12, 2023.

  1. Draugrlord

    Draugrlord

    Joined:
    Oct 1, 2021
    Posts:
    30
    Apologies in advance for extremely long thread.

    TL;DR My attack animations are working but they will have random delays that vary in time when repeatedly pressing the attack button and I can't figure out why.

    As the thread title states I am having issues with my animations not playing correctly. Specifically, it is my attack animation. I was able to get my other animations to play with little issue, but it seems no matter which approach I do for my attack animation, there is always a weird delay in the animations. After playing around I realize my attack animation had to have an exit time when transitions from the attack back to my default state (idle/walk/run state).

    If I go and turn it off the attack animation plays but then my character gets stuck in the final frame of the attack and wont transition unless I spam the attack button. With the exit time on this doesn't happen and the transition back to my default state but then either there is a weird 1 - 5 second delay where either I can repeatedly hit my attack button and it works or it seems to have an internal delay before the attack continues.

    Yes, I know that I could easily make it to where to player holds down the attack button while the animation goes on loop, but I would like to add in additional attacks after my first one to create combos, but I'd first like to get a single attack animation to work as I am intending it to do.

    Here are images showing my animator as well as the code I am using to trigger my animation. I am using the Starter Asset pack from the unity store and adding in code for my attack animations, which if we ignore the weird animation delays, is working so I feel the code isn't the issue, but I am extremely new to this and might be overlooking an obvious fix.

    The code for the attack animation is at the bottom of the code. I wasn't sure if everything would be needed and wanted to be safer than sorry.

    Code (CSharp):
    1. using UnityEngine;
    2. #if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
    3. using UnityEngine.InputSystem;
    4. #endif
    5.  
    6. /* Note: animations are called via the controller for both the character and capsule using animator null checks
    7. */
    8.  
    9. namespace StarterAssets
    10. {
    11.     [RequireComponent(typeof(CharacterController))]
    12. #if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
    13.     [RequireComponent(typeof(PlayerInput))]
    14. #endif
    15.     public class ThirdPersonController : MonoBehaviour
    16.     {
    17.         [Header("Player")]
    18.         [Tooltip("Move speed of the character in m/s")]
    19.         public float MoveSpeed = 2.0f;
    20.  
    21.         [Tooltip("Sprint speed of the character in m/s")]
    22.         public float SprintSpeed = 5.335f;
    23.  
    24.         [Tooltip("How fast the character turns to face movement direction")]
    25.         [Range(0.0f, 0.3f)]
    26.         public float RotationSmoothTime = 0.12f;
    27.  
    28.         [Tooltip("Acceleration and deceleration")]
    29.         public float SpeedChangeRate = 10.0f;
    30.  
    31.         public AudioClip LandingAudioClip;
    32.         public AudioClip[] FootstepAudioClips;
    33.         [Range(0, 1)] public float FootstepAudioVolume = 0.5f;
    34.  
    35.         [Space(10)]
    36.         [Tooltip("The height the player can jump")]
    37.         public float JumpHeight = 1.2f;
    38.  
    39.         [Tooltip("The character uses its own gravity value. The engine default is -9.81f")]
    40.         public float Gravity = -15.0f;
    41.  
    42.         [Space(10)]
    43.         [Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
    44.         public float JumpTimeout = 0.50f;
    45.  
    46.         [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
    47.         public float FallTimeout = 0.15f;
    48.  
    49.         [Header("Player Grounded")]
    50.         [Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
    51.         public bool Grounded = true;
    52.  
    53.         [Tooltip("Useful for rough ground")]
    54.         public float GroundedOffset = -0.14f;
    55.  
    56.         [Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
    57.         public float GroundedRadius = 0.28f;
    58.  
    59.         [Tooltip("What layers the character uses as ground")]
    60.         public LayerMask GroundLayers;
    61.  
    62.         [Header("Cinemachine")]
    63.         [Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")]
    64.         public GameObject CinemachineCameraTarget;
    65.  
    66.         [Tooltip("How far in degrees can you move the camera up")]
    67.         public float TopClamp = 70.0f;
    68.  
    69.         [Tooltip("How far in degrees can you move the camera down")]
    70.         public float BottomClamp = -30.0f;
    71.  
    72.         [Tooltip("Additional degress to override the camera. Useful for fine tuning camera position when locked")]
    73.         public float CameraAngleOverride = 0.0f;
    74.  
    75.         [Tooltip("For locking the camera position on all axis")]
    76.         public bool LockCameraPosition = false;
    77.  
    78.         // cinemachine
    79.         private float _cinemachineTargetYaw;
    80.         private float _cinemachineTargetPitch;
    81.  
    82.         // player
    83.         private float _speed;
    84.         private float _animationBlend;
    85.         private float _targetRotation = 0.0f;
    86.         private float _rotationVelocity;
    87.         private float _verticalVelocity;
    88.         private float _terminalVelocity = 53.0f;
    89.  
    90.         // timeout deltatime
    91.         private float _jumpTimeoutDelta;
    92.         private float _fallTimeoutDelta;
    93.  
    94.         // animation IDs
    95.         private int _animIDSpeed;
    96.         private int _animIDGrounded;
    97.         private int _animIDJump;
    98.         private int _animIDFreeFall;
    99.         private int _animIDMotionSpeed;
    100.         private int _animIDAttack;
    101.  
    102. #if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
    103.         private PlayerInput _playerInput;
    104. #endif
    105.         private Animator _animator;
    106.         private CharacterController _controller;
    107.         private StarterAssetsInputs _input;
    108.         private GameObject _mainCamera;
    109.  
    110.         private const float _threshold = 0.01f;
    111.  
    112.         private bool _hasAnimator;
    113.  
    114.         private bool IsCurrentDeviceMouse
    115.         {
    116.             get
    117.             {
    118. #if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
    119.                 return _playerInput.currentControlScheme == "KeyboardMouse";
    120. #else
    121.                 return false;
    122. #endif
    123.             }
    124.         }
    125.  
    126.  
    127.         private void Awake()
    128.         {
    129.             // get a reference to our main camera
    130.             if (_mainCamera == null)
    131.             {
    132.                 _mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
    133.             }
    134.         }
    135.  
    136.         private void Start()
    137.         {
    138.             _cinemachineTargetYaw = CinemachineCameraTarget.transform.rotation.eulerAngles.y;
    139.          
    140.             _hasAnimator = TryGetComponent(out _animator);
    141.             _controller = GetComponent<CharacterController>();
    142.             _input = GetComponent<StarterAssetsInputs>();
    143. #if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
    144.             _playerInput = GetComponent<PlayerInput>();
    145. #else
    146.             Debug.LogError( "Starter Assets package is missing dependencies. Please use Tools/Starter Assets/Reinstall Dependencies to fix it");
    147. #endif
    148.  
    149.             AssignAnimationIDs();
    150.  
    151.             // reset our timeouts on start
    152.             _jumpTimeoutDelta = JumpTimeout;
    153.             _fallTimeoutDelta = FallTimeout;
    154.         }
    155.  
    156.         private void Update()
    157.         {
    158.             _hasAnimator = TryGetComponent(out _animator);
    159.  
    160.             JumpAndGravity();
    161.             GroundedCheck();
    162.             Move();
    163.         }
    164.  
    165.         private void LateUpdate()
    166.         {
    167.             CameraRotation();
    168.         }
    169.  
    170.         private void AssignAnimationIDs()
    171.         {
    172.             _animIDSpeed = Animator.StringToHash("Speed");
    173.             _animIDGrounded = Animator.StringToHash("Grounded");
    174.             _animIDJump = Animator.StringToHash("Jump");
    175.             _animIDFreeFall = Animator.StringToHash("FreeFall");
    176.             _animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
    177.             _animIDAttack = Animator.StringToHash("Attack");
    178.         }
    179.  
    180.         private void GroundedCheck()
    181.         {
    182.             // set sphere position, with offset
    183.             Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset,
    184.                 transform.position.z);
    185.             Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers,
    186.                 QueryTriggerInteraction.Ignore);
    187.  
    188.             // update animator if using character
    189.             if (_hasAnimator)
    190.             {
    191.                 _animator.SetBool(_animIDGrounded, Grounded);
    192.             }
    193.         }
    194.  
    195.         private void CameraRotation()
    196.         {
    197.             // if there is an input and camera position is not fixed
    198.             if (_input.look.sqrMagnitude >= _threshold && !LockCameraPosition)
    199.             {
    200.                 //Don't multiply mouse input by Time.deltaTime;
    201.                 float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;
    202.  
    203.                 _cinemachineTargetYaw += _input.look.x * deltaTimeMultiplier;
    204.                 _cinemachineTargetPitch += _input.look.y * deltaTimeMultiplier;
    205.             }
    206.  
    207.             // clamp our rotations so our values are limited 360 degrees
    208.             _cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
    209.             _cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);
    210.  
    211.             // Cinemachine will follow this target
    212.             CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride,
    213.                 _cinemachineTargetYaw, 0.0f);
    214.         }
    215.  
    216.         private void Move()
    217.         {
    218.             // set target speed based on move speed, sprint speed and if sprint is pressed
    219.             float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;
    220.  
    221.             // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon
    222.  
    223.             // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
    224.             // if there is no input, set the target speed to 0
    225.             if (_input.move == Vector2.zero) targetSpeed = 0.0f;
    226.  
    227.             // a reference to the players current horizontal velocity
    228.             float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;
    229.  
    230.             float speedOffset = 0.1f;
    231.             float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;
    232.  
    233.             // accelerate or decelerate to target speed
    234.             if (currentHorizontalSpeed < targetSpeed - speedOffset ||
    235.                 currentHorizontalSpeed > targetSpeed + speedOffset)
    236.             {
    237.                 // creates curved result rather than a linear one giving a more organic speed change
    238.                 // note T in Lerp is clamped, so we don't need to clamp our speed
    239.                 _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
    240.                     Time.deltaTime * SpeedChangeRate);
    241.  
    242.                 // round speed to 3 decimal places
    243.                 _speed = Mathf.Round(_speed * 1000f) / 1000f;
    244.             }
    245.             else
    246.             {
    247.                 _speed = targetSpeed;
    248.             }
    249.  
    250.             _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
    251.             if (_animationBlend < 0.01f) _animationBlend = 0f;
    252.  
    253.             // normalise input direction
    254.             Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;
    255.  
    256.             // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
    257.             // if there is a move input rotate player when the player is moving
    258.             if (_input.move != Vector2.zero)
    259.             {
    260.                 _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg +
    261.                                   _mainCamera.transform.eulerAngles.y;
    262.                 float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
    263.                     RotationSmoothTime);
    264.  
    265.                 // rotate to face input direction relative to camera position
    266.                 transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
    267.             }
    268.  
    269.  
    270.             Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;
    271.  
    272.             // move the player
    273.             _controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) +
    274.                              new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
    275.  
    276.             // update animator if using character
    277.             if (_hasAnimator)
    278.             {
    279.                 _animator.SetFloat(_animIDSpeed, _animationBlend);
    280.                 _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
    281.             }
    282.         }
    283.  
    284.         private void JumpAndGravity()
    285.         {
    286.             if (Grounded)
    287.             {
    288.                 // reset the fall timeout timer
    289.                 _fallTimeoutDelta = FallTimeout;
    290.  
    291.                 // update animator if using character
    292.                 if (_hasAnimator)
    293.                 {
    294.                     _animator.SetBool(_animIDJump, false);
    295.                     _animator.SetBool(_animIDFreeFall, false);
    296.                 }
    297.  
    298.                 // stop our velocity dropping infinitely when grounded
    299.                 if (_verticalVelocity < 0.0f)
    300.                 {
    301.                     _verticalVelocity = -2f;
    302.                 }
    303.  
    304.                 // Jump
    305.                 if (_input.jump && _jumpTimeoutDelta <= 0.0f)
    306.                 {
    307.                     // the square root of H * -2 * G = how much velocity needed to reach desired height
    308.                     _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);
    309.  
    310.                     // update animator if using character
    311.                     if (_hasAnimator)
    312.                     {
    313.                         _animator.SetBool(_animIDJump, true);
    314.                     }
    315.                 }
    316.  
    317.                 // jump timeout
    318.                 if (_jumpTimeoutDelta >= 0.0f)
    319.                 {
    320.                     _jumpTimeoutDelta -= Time.deltaTime;
    321.                 }
    322.             }
    323.             else
    324.             {
    325.                 // reset the jump timeout timer
    326.                 _jumpTimeoutDelta = JumpTimeout;
    327.  
    328.                 // fall timeout
    329.                 if (_fallTimeoutDelta >= 0.0f)
    330.                 {
    331.                     _fallTimeoutDelta -= Time.deltaTime;
    332.                 }
    333.                 else
    334.                 {
    335.                     // update animator if using character
    336.                     if (_hasAnimator)
    337.                     {
    338.                         _animator.SetBool(_animIDFreeFall, true);
    339.                     }
    340.                 }
    341.  
    342.                 // if we are not grounded, do not jump
    343.                 _input.jump = false;
    344.             }
    345.  
    346.             // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
    347.             if (_verticalVelocity < _terminalVelocity)
    348.             {
    349.                 _verticalVelocity += Gravity * Time.deltaTime;
    350.             }
    351.         }
    352.  
    353.         private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
    354.         {
    355.             if (lfAngle < -360f) lfAngle += 360f;
    356.             if (lfAngle > 360f) lfAngle -= 360f;
    357.             return Mathf.Clamp(lfAngle, lfMin, lfMax);
    358.         }
    359.  
    360.         private void OnDrawGizmosSelected()
    361.         {
    362.             Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
    363.             Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);
    364.  
    365.             if (Grounded) Gizmos.color = transparentGreen;
    366.             else Gizmos.color = transparentRed;
    367.  
    368.             // when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
    369.             Gizmos.DrawSphere(
    370.                 new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z),
    371.                 GroundedRadius);
    372.         }
    373.  
    374.         private void OnFootstep(AnimationEvent animationEvent)
    375.         {
    376.             if (animationEvent.animatorClipInfo.weight > 0.5f)
    377.             {
    378.                 if (FootstepAudioClips.Length > 0)
    379.                 {
    380.                     var index = Random.Range(0, FootstepAudioClips.Length);
    381.                     AudioSource.PlayClipAtPoint(FootstepAudioClips[index], transform.TransformPoint(_controller.center), FootstepAudioVolume);
    382.                 }
    383.             }
    384.         }
    385.  
    386.         private void OnLand(AnimationEvent animationEvent)
    387.         {
    388.             if (animationEvent.animatorClipInfo.weight > 0.5f)
    389.             {
    390.                 AudioSource.PlayClipAtPoint(LandingAudioClip, transform.TransformPoint(_controller.center), FootstepAudioVolume);
    391.             }
    392.         }
    393.  
    394.         // Attack animation
    395.  
    396.         private void Attack()
    397.         {
    398.             _animator.SetTrigger("Attack");
    399.         }
    400.  
    401.         private void FixedUpdate()
    402.         {
    403.             if (Input.GetMouseButtonDown(0))
    404.             {
    405.                 Attack();
    406.             }
    407.         }
    408.     }
    409. }
    Screenshot (5).png Screenshot (6).png Screenshot (7).png
     
  2. JustinNaicker

    JustinNaicker

    Joined:
    Jan 4, 2023
    Posts:
    47
    Do you have a video of both instances with and without ExitTime? Would help alot.
     
  3. Draugrlord

    Draugrlord

    Joined:
    Oct 1, 2021
    Posts:
    30
    Apologies I hadn't thought of getting videos of what was going on at the time.

    However, I was able to fix the issue when I took a different approach and put the attack animation on a separate layer in my animator instead of having everything on the base layer and somehow that fixed the issue. Once I had done that, I was able to trigger my attack animation without fail every time I hit my attack button.

    Still not entirely sure why having two separate layers worked in fixing the issue. I was going through different tutorials online of how people set up their controllers and animators for a third person game and one of them had created the second layer and so I tried that approach which had fixed the issue.
     
  4. JustinNaicker

    JustinNaicker

    Joined:
    Jan 4, 2023
    Posts:
    47
    Glad you were able to fix it. That’s quite interesting.