Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Camera Shakes When Displacing and Rotating Smoothly

Discussion in 'Scripting' started by lifGwaethrakindo, Oct 22, 2019.

  1. lifGwaethrakindo

    lifGwaethrakindo

    Joined:
    Oct 22, 2017
    Posts:
    3
    I tried to make a simple Third Person Camera that displaces and rotates towards a given target, with the following key parameters:
    - Following types for both displacement and rotation. Right now the enum is just { Instant, Smooth }.
    - Axes to ignore for both displacement and rotation, all axes in an enum.
    - Axes to invert for the orbiting functionality.
    - 'Relative Following' for both displacement and rotation. If the flags are checked the displacement/rotation will be relative to the target's orientation ('target.rotation * displacement' and 'target.rotation * rotation' for displacement and rotation respectively).
    - An offset vector, which is affected by the displacement relative following's flag. It is treated as a normalized vector at runtime.
    - A scalar of the aforementioned offset. Which is basically de distance between the camera and the target.
    - An offset vector as Euler for the rotation, it is equally affected by the rotation relative following's flag.
    - Other attributes, such as 'displacementFollowDuration', 'maxDisplacementFollowSpeed', 'rotationFollowDuration', 'maxRotationFollowingSpeed', etc., are for the smooth following. The attributes not mentioned are either self-explanatory or irrelevant (at least that's what I want to think, correct me if I may be wrong).

    The Problem:

    When the smooth flags are enabled for both displacement and rotation, the camera starts to shake, I think it has something to do with the rotation.

    Things I've tried already:

    - Call the rotation and displacement following on different threads (being Update, Late Update and FixedUpdate), not yet on coroutines. This time I made an enum 'LoopType' to encapsulate all threads, and avoid re-compiling each time I move the functions.
    - Use different kinds of time deltas. By the same fashion of the threads, I made an enum 'TimeDelta' which encapsulates the 3 types of time deltas Unity offers.
    - Change the smooth functions for both displacement and rotation, such as Lerp, SmoothDamp, Slerp, even custom elastic functions, etc.
    - Sacrifing 1 frame by following the target's point of the last frame, as proposed here: https://answers.unity.com/questions...ment-of-the-camera-when-rotating-and-m-1.html.
    - Given that the target's displacement is that of a Rigidbody, this post ( https://answers.unity.com/questions...g-custom-camera-smoothness-camera-shak-1.html ) suggests to attach a Rigidbody to the camera and displace/rotate it by Rigidbody.position/Rigidbody.rotation respectively.

    Camera's Script:

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. #if UNITY_EDITOR
    6. using UnityEditor;
    7. #endif
    8.  
    9. [Flags]
    10. public enum Axes
    11. {
    12.     None = 0,
    13.     X = 1,
    14.     Y = 2,
    15.     Z = 4,
    16.  
    17.     XAndY = X | Y,
    18.     XAndZ = X | Z,
    19.     YAndZ = Y | Z,
    20.     All = X | Y | Z
    21. }
    22.  
    23. public enum LoopType { Update, LateUpdate, FixedUpdate }
    24.  
    25. public enum TimeDelta { Default, Fixed, Smooth }
    26.  
    27. public enum FollowingType { Instant, Smooth }
    28.  
    29. /// \TODO Maybe something like Lucky Tale and Resident Evil 4 and 5 camera (return to the original rotation if there are no axes)
    30. [RequireComponent(typeof(Rigidbody))]
    31. public class GameplayCamera : MonoBehaviour
    32. {
    33.     public Transform target;                         /// Camera's Target.
    34.     [Space(5f)]
    35.     [Header("Displacement Following's Attributes:")]
    36.     public LoopType followDisplacementAt;             /// Loop to do the Displacement Following.
    37.     public FollowingType displacementFollowType;     /// Type of Following for Displacement.
    38.     public TimeDelta displacementTimeDelta;         /// Displacement's Time Delta.
    39.     public Axes ignoreDisplacementAxes;             /// Displacement Axes to Ignore.
    40.     public Axes invertAxes;                         /// Axes to Invert.
    41.     public Axes limitOrbitAxes;                     /// Orbit's Axes to Limit.
    42.     public bool relativeDisplacementFollow;         /// Follow Target's Displacement Relative to Target's Orientation?.
    43.     public bool limitDisplacementFollow;             /// Limit Displacement Following's Speed?.
    44.     public Vector3 displacementOffset;                 /// [Normalized] Displacement Offset between Camera and Target.
    45.     public Vector2 orbitSpeed;                         /// Orbit's Speed on each Axis.
    46.     public Vector2 minOrbitLimits;                     /// Orbit's Negative Boundaries.
    47.     public Vector2 maxOrbitLimits;                     /// Orbit's Positive Boundaries.
    48.     public float displacementFollowDuration;         /// Displacement's Follow Duration.
    49.     public float maxDisplacementFolowSpeed;         /// Maximum Displacement's Follow Duration.
    50.     public float minDistance;                         /// Minimum Distance Between Camera and Target.
    51.     public float maxDistance;                         /// Maximum Distance Between Camera and Target.
    52.     [Space(5f)]
    53.     [Header("Rotation Following's Attributes:")]
    54.     public LoopType followRotationAt;                 /// Loop to do the Rotation Following.
    55.     public FollowingType rotationFollowType;         /// Type of Following for Rotation.
    56.     public TimeDelta rotationTimeDelta;             /// Rotations' Time Delta.
    57.     public Axes ignoreRotationAxes;                 /// Rotation Axes to Ignore.
    58.     public bool relativeRotationFollow;             /// Follow Target's Rotation Relative to Target's Orientation?.
    59.     public bool limitRotationFollow;                 /// Limit Rotation Following's Speed?.
    60.     public Vector3 eulerRotationOffset;             /// Rotation Offset between Camera and Target as Euler.
    61.     public float rotationFollowDuration;             /// Rotation's Following Duration.
    62.     public float maxRotationFollowSpeed;             /// Maximum Rotation's Following Speed.
    63.     [Space(5f)]
    64.     public Vector3 up;                                 /// Up Vector's Reference.
    65.     [HideInInspector] public Vector3 forward;         /// Reoriented Forward's Vector.
    66.     private Vector3 eulerOrbitRotation;             /// Current Orbit Rotation as Euler.
    67.     private Vector3 displacementVelocity;             /// Displacement's Velocity.
    68.     private Quaternion orbitRotation;                 /// Orbit Rotation as Quaternion.
    69.     private Quaternion rotationOffset;                 /// Rotation's Offset as Quaternion.
    70.     private Vector2 inputAxes;                         /// Input's Axes.
    71.     private float currentDistance;                     /// Current Distance from Camera and Player.
    72.     private float angularSpeed;                     /// Angular's Speed.
    73.     private Rigidbody _rigidbody;                     /// Rigidbody's Component.
    74.  
    75.     /// <summary>Gets rigidbody Component.</summary>
    76.     public Rigidbody rigidbody
    77.     {
    78.         get
    79.         {
    80.             if(_rigidbody == null) _rigidbody = GetComponent<Rigidbody>();
    81.             return _rigidbody;
    82.         }
    83.     }
    84.  
    85. #region UnityMethods:
    86.     /// <summary>Draws Gizmos on Editor mode.</summary>
    87.     private void OnDrawGizmos()
    88.     {
    89.         Gizmos.color = Color.green;
    90.         Gizmos.DrawRay(rigidbody.position, up);
    91.         Gizmos.color = Color.blue;
    92.         Gizmos.DrawRay(rigidbody.position, forward);
    93.  
    94.         if(target != null)
    95.         {
    96.             Gizmos.color = Color.cyan;
    97.             Gizmos.DrawLine(target.position, GetOffsetPoint());
    98.  
    99.             if(!Application.isPlaying)
    100.             {
    101.                 UpdateRotationOffset();
    102.                 ReorientForward();
    103.             }
    104.  
    105.             Quaternion rotation = rigidbody.rotation * rotationOffset;
    106.  
    107.             Handles.color = new Color(1.0f, 0.0f, 0.0f, 0.35f); /// Red
    108.             Handles.DrawSolidArc(rigidbody.position, transform.right, transform.forward, Vector3.Angle(transform.forward, rotation * Vector3.forward) * Mathf.Sign(eulerRotationOffset.x), 1.0f);
    109.             Handles.color = new Color(0.0f, 1.0f, 0.0f, 0.35f); /// Green
    110.             Handles.DrawSolidArc(rigidbody.position, transform.up, transform.right, Vector3.Angle(transform.right, rotation * Vector3.right) * Mathf.Sign(eulerRotationOffset.y), 1.0f);
    111.             Handles.color = new Color(1.0f, 0.0f, 1.0f, 0.35f); /// Blue
    112.             Handles.DrawSolidArc(rigidbody.position, transform.forward, transform.up, Vector3.Angle(transform.up, rotation * Vector3.up) * Mathf.Sign(eulerRotationOffset.z), 1.0f);
    113.      
    114.             if(!Application.isPlaying)
    115.             {
    116.                 rigidbody.position = GetOffsetPoint();
    117.                 rigidbody.rotation = Quaternion.LookRotation(GetLookDirection()) * rotationOffset;
    118.             }
    119.         }    
    120.     }
    121.  
    122.     /// <summary>Resets GameplayCamera's instance to its default values.</summary>
    123.     private void Reset()
    124.     {
    125.         up = Vector3.up;
    126.     }
    127.  
    128.     /// <summary>GameplayCamera's instance initialization when loaded [Before scene loads].</summary>
    129.     private void Awake()
    130.     {
    131.         rigidbody.isKinematic = true;
    132.     }
    133.  
    134.     /// <summary>GameplayCamera's tick at each frame.</summary>
    135.     private void Update()
    136.     {
    137.         if(target == null) return;
    138.  
    139.         TrackInput();
    140.         UpdateRotationOffset();
    141.  
    142.         if(followDisplacementAt == LoopType.Update) DisplacementFollow();
    143.         if(followRotationAt == LoopType.Update) RotationFollow();
    144.     }
    145.  
    146.     /// <summary>Updates GameplayCamera's instance at the end of each frame.</summary>
    147.     private void LateUpdate()
    148.     {
    149.         if(target == null) return;
    150.  
    151.         if(followDisplacementAt == LoopType.LateUpdate) DisplacementFollow();
    152.         if(followRotationAt == LoopType.LateUpdate) RotationFollow();
    153.  
    154.         ReorientForward();
    155.     }
    156.  
    157.     /// <summary>Updates GameplayCamera's instance at each Physics Thread's frame.</summary>
    158.     private void FixedUpdate()
    159.     {
    160.         if(target == null) return;
    161.  
    162.         if(followDisplacementAt == LoopType.FixedUpdate) DisplacementFollow();
    163.         if(followRotationAt == LoopType.FixedUpdate) RotationFollow();
    164.     }
    165. #endregion
    166.  
    167.     /// <summary>Tracks Input.</summary>
    168.     private void TrackInput()
    169.     {
    170.         inputAxes.x = Input.GetAxis("Mouse Y");
    171.         inputAxes.y = Input.GetAxis("Mouse X");
    172.     }
    173.  
    174.     /// <summary>Performs the Displacement's Following.</summary>
    175.     private void DisplacementFollow()
    176.     {
    177.         if(inputAxes.sqrMagnitude > 0.0f) OrbitInAxes(inputAxes.x, inputAxes.y);
    178.  
    179.         switch(displacementFollowType)
    180.         {
    181.             case FollowingType.Instant:
    182.             rigidbody.position = GetOffsetPoint();
    183.             break;
    184.  
    185.             case FollowingType.Smooth:
    186.             rigidbody.position = GetSmoothDisplacementFollowDirection();
    187.             break;
    188.         }
    189.     }
    190.  
    191.     /// <summary>Performs the Rotation's Following.</summary>
    192.     private void RotationFollow()
    193.     {
    194.         switch(rotationFollowType)
    195.         {
    196.             case FollowingType.Instant:
    197.             rigidbody.rotation = Quaternion.LookRotation(GetLookDirection()) * rotationOffset;
    198.             break;
    199.  
    200.             case FollowingType.Smooth:
    201.             rigidbody.rotation = GetSmoothFollowRotation();
    202.             break;
    203.         }
    204.     }
    205.  
    206.     /// <summary>Orbits Camera in Given Axes.</summary>
    207.     /// <param name="x">X's Axis.</param>
    208.     /// <param name="y">Y's Axis.</param>
    209.     private void OrbitInAxes(float x, float y)
    210.     {
    211.         if((invertAxes | Axes.X) == invertAxes) x *= -1.0f;
    212.         if((invertAxes | Axes.Y) == invertAxes) y *= -1.0f;
    213.  
    214.         float xRotation = (x * orbitSpeed.x * GetTimeDelta(displacementTimeDelta));
    215.         float yRotation = (y * orbitSpeed.y * GetTimeDelta(displacementTimeDelta));
    216.  
    217.         eulerOrbitRotation.x = (limitOrbitAxes | Axes.X) == limitOrbitAxes ?
    218.             Mathf.Clamp(eulerOrbitRotation.x + xRotation, minOrbitLimits.x, maxOrbitLimits.x) : eulerOrbitRotation.x + xRotation;
    219.         eulerOrbitRotation.y = (limitOrbitAxes | Axes.Y) == limitOrbitAxes ?
    220.             Mathf.Clamp(eulerOrbitRotation.y + yRotation, minOrbitLimits.y, maxOrbitLimits.y) : eulerOrbitRotation.y + yRotation;
    221.  
    222.         orbitRotation = Quaternion.Euler(eulerOrbitRotation);
    223.     }
    224.  
    225.     /// <returns>Gets the smooth displacement following's Vector.</returns>
    226.     private Vector3 GetSmoothDisplacementFollowDirection()
    227.     {
    228.         return Vector3.SmoothDamp
    229.         (
    230.             rigidbody.position,
    231.             GetOffsetPoint(),
    232.             ref displacementVelocity,
    233.             displacementFollowDuration,
    234.             limitDisplacementFollow ? maxDisplacementFolowSpeed : Mathf.Infinity,
    235.             GetTimeDelta(displacementTimeDelta)
    236.         );
    237.     }
    238.  
    239.     /// <summary>Gets Offset Point, with the Orbit's Rotation already combined.</summary>
    240.     private Vector3 GetOffsetPoint()
    241.     {
    242.         Vector3 scaledOffset = displacementOffset.normalized * maxDistance;
    243.         Vector3 point = target.position + (orbitRotation * (relativeDisplacementFollow ? target.rotation * scaledOffset : scaledOffset));
    244.      
    245.         if((ignoreDisplacementAxes | Axes.X) == ignoreDisplacementAxes) point.x = rigidbody.position.x;
    246.         if((ignoreDisplacementAxes | Axes.Y) == ignoreDisplacementAxes) point.y = rigidbody.position.y;
    247.  
    248.         return point;
    249.     }
    250.  
    251.     /// <returns>Looking Direction, taking into account the axes to ignore.</returns>
    252.     private Vector3 GetLookDirection()
    253.     {
    254.         Vector3 direction = target.position - rigidbody.position;
    255.  
    256.         if((ignoreRotationAxes | Axes.X) == ignoreRotationAxes) direction.x = rigidbody.position.x;
    257.         if((ignoreRotationAxes | Axes.Y) == ignoreRotationAxes) direction.y = rigidbody.position.y;
    258.         if((ignoreRotationAxes | Axes.Z) == ignoreRotationAxes) direction.z = rigidbody.position.z;
    259.  
    260.         return direction;
    261.     }
    262.  
    263.     /// <return>Following Rotation, with the Rotation's Offset already combined.</return>
    264.     private Quaternion GetSmoothFollowRotation()
    265.     {
    266.         Quaternion rotation = Quaternion.LookRotation(GetLookDirection()) * rotationOffset;
    267.         float angle = Quaternion.Angle(rigidbody.rotation, rotation);
    268.  
    269.         if(angle > 0.0f)
    270.         {
    271.             float t = Mathf.SmoothDampAngle(
    272.                 angle,
    273.                 0.0f,
    274.                 ref angularSpeed,
    275.                 rotationFollowDuration,
    276.                 limitRotationFollow ? maxRotationFollowSpeed : Mathf.Infinity,
    277.                 GetTimeDelta(rotationTimeDelta)
    278.             );
    279.             return Quaternion.Slerp(rigidbody.rotation, rotation, t);
    280.         }
    281.  
    282.         return rotation;
    283.     }
    284.  
    285.     /// <summary>Updates the Rotation's Offset Given the Wuler Representation.</summary>
    286.     private void UpdateRotationOffset()
    287.     {
    288.         Quaternion rotation = Quaternion.Euler(eulerRotationOffset);
    289.         rotationOffset = relativeRotationFollow ? target.rotation * rotation : rotation;
    290.     }
    291.  
    292.     /// <summary>Reorients Forward's Vector.</summary>
    293.     private void ReorientForward()
    294.     {
    295.         forward = Vector3.Cross(transform.right, up);
    296.     }
    297.  
    298.     /// <summary>Gets Time's Delta.</summary>
    299.     /// <param name="_pa">Time Delta's Type.</param>
    300.     /// <returns>Time's Delta of the Given Type.</returns>
    301.     private float GetTimeDelta(TimeDelta _timeDelta = TimeDelta.Default)
    302.     {
    303.         switch(_timeDelta)
    304.         {
    305.             case TimeDelta.Default: return Time.deltaTime;
    306.             case TimeDelta.Fixed:     return Time.fixedDeltaTime;
    307.             case TimeDelta.Smooth:     return Time.smoothDeltaTime;
    308.             default:                 return 0.0f;
    309.         }
    310.     }
    311. }
    I also made a quick Character's script for the sake of giving a quick example (the original Character script I have a has tons of dependencies). So its jump does not have cooldown, and it doesn't evaluate if it is grounded.

    Simple Character Movement's Script:

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. [RequireComponent(typeof(Rigidbody))]
    7. public class CharacterMovement : MonoBehaviour
    8. {
    9.     [SerializeField] private GameplayCamera camera;     /// Gameplay's Camera.
    10.     [Space(5f)]
    11.     [SerializeField] private KeyCode jumpKey;             /// Jump's KeyCode.
    12.     [SerializeField] private KeyCode displacementKey;     /// Displacement's Key.
    13.     [SerializeField] private float displacementSpeed;     /// Displacements Speed.
    14.     [SerializeField] private float jumpForce;             /// Jump's Force .
    15.     [SerializeField] private ForceMode mode;             /// Jump Force' sMode.
    16.     private Rigidbody rigidbody;                         /// Rigidbody's Component.
    17.  
    18. #region UnityMethods:
    19.     /// <summary>CharacterMovement's instance initialization.</summary>
    20.     private void Awake()
    21.     {
    22.         rigidbody = GetComponent<Rigidbody>();
    23.     }
    24.  
    25.     /// <summary>CharacterMovement's tick at each frame.</summary>
    26.     private void Update ()
    27.     {
    28.         Vector3 axes = new Vector3
    29.         (
    30.             Input.GetAxis("Horizontal"),
    31.             0.0f,
    32.             Input.GetAxis("Vertical")
    33.         );
    34.  
    35.         if(axes.sqrMagnitude > 0.0f)
    36.         {
    37.             transform.rotation = Quaternion.LookRotation(axes);
    38.             transform.Translate(transform.forward * displacementSpeed * Time.deltaTime, Space.World);
    39.         }
    40.         if(Input.GetKeyDown(jumpKey)) Jump();
    41.     }
    42. #endregion
    43.  
    44.     /// <summary>Performs Jump.</summary>
    45.     private void Jump()
    46.     {
    47.         rigidbody.AddForce(Vector3.up * jumpForce, mode);
    48.     }
    49. }
    What I Want to Know:

    If I am missing something, I am using the wrong functions, calling the functions in the wrong threads/orders, etc.
    Please let me know if there is more information I have to provide.
     
    Last edited: Oct 22, 2019
  2. stuartiannaylor

    stuartiannaylor

    Joined:
    Oct 20, 2019
    Posts:
    19
    I don't have any answers but I am the same and slightly confused.

    Tried several methods using Lerp, Smoothdamp and like yours above puzzled about update, lateupdate and fixedupdate.

    What I am trying to do is a follow cam with the lookat function that smooths transition but also returns smoothly to a set z rotation axis behind the player.

    I seem to be in this catch 22 situation of either relatively smooth player and shaky scene or smooth scene with shaky player.
    The game type is driving and beginning to thinking much is going to be accomplished by just having a much smoother terrain.

    There are quite a lot of simple examples out there that track but don't try and return to a set camera rotation offset by player rotation and when they do they often have the Lerp deltatime errors in.
    So apols for not giving any answers but just wondered if anyone knows of some good resources for some follow camera scripts that are a bit more concise that what the majority seem to be is a few lines of code.
    I will have a look at your code and see how it pans out in my scene but hoping someone can forward some resources and offer some solutions to reducing shake?

    Hope you get a solution also so apols for a slight noob derail but also wondering if player transform damping is also much to do with the solution.
    But hopefully the more questions the merrier.