Search Unity

Head Bob When Landing

Discussion in 'Scripting' started by Studio_Akiba, Oct 29, 2020.

  1. Studio_Akiba

    Studio_Akiba

    Joined:
    Mar 3, 2014
    Posts:
    1,421
    Easing (Lerp) is just... awful.
    With that out of the way, here's my problem.
    I'm (pathetically) trying to make a first-person character controller, and am having unbelievable amounts of trouble actually getting anything even remotely related to the camera working.

    Part of the problem is I want this dynamic, otherwise, I'd just use animations and forget the concept of math ever existed.

    I'm trying to get the camera to bob down and then back up when the player lands after jumping (or falling), which is proving to be substantially more difficult than I ever thought possible for such a simple thing.

    I'm trying to replicate exponential ease-out, to very little success. This is probably the wrong approach to this but I'm not even able to achieve the wrong thing after several hours of staring at strange websites and trying to get anything at all working.

    The coroutine supposedly meant to achieve the easing is at 238, and the call for it is at 163 (not even sure this is the best way to check if the player lands either).

    I'm completely out of ideas and have absolutely no idea where to go from here.

    Please excuse my trash code, I'm not a programmer.

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Security.Principal;
    5. using System.Threading;
    6. using UnityEngine;
    7.  
    8. [RequireComponent(typeof(CharacterController))]
    9. [AddComponentMenu("Arcane/Player/Character Controller")]
    10. public class ArcaneCharacterController : MonoBehaviour
    11. {
    12.     //Public Reference Access
    13.     public static ArcaneCharacterController arcaneCharacter;
    14.  
    15.     //Variables and defaults
    16.     public float playerLookSensitivity = 8f;
    17.     public float playerWalkingSpeed = 5f;
    18.     public float playerRunningSpeed = 10f;
    19.     public float playerJumpStrength = 4f;
    20.     public float vertRotLimit = 80f;
    21.     float gravityModifier = 1.0f;
    22.  
    23.     //Backend
    24.     float forwardMovt;
    25.     float sidewaysMovt;
    26.     float verticalVelocity;
    27.     bool airControl;
    28.     bool hasJumped;
    29.  
    30.     float vertRot = 0;
    31.  
    32.     //CharacterController Reference
    33.     CharacterController cc;
    34.  
    35.     //Camera head bob
    36.     float bobSpeedWalk = 0.18f;
    37.     float bobSpeedRun = 0.32f;
    38.     float bobHeight = 0.2f;
    39.     [HideInInspector]
    40.     public float bobMid = 0.7f;
    41.     bool isHeadBobbing = true;
    42.     float bobTimer = 0f;
    43.  
    44.     void Awake()
    45.     {
    46.         //If player already exists, destroy. If not, set reference to this and set player to DDOL.
    47.         if (arcaneCharacter != null)
    48.         {
    49.             Destroy(gameObject);
    50.         }
    51.         else
    52.         {
    53.             arcaneCharacter = this;
    54.             DontDestroyOnLoad(gameObject);
    55.         }
    56.  
    57.         cc = GetComponent<CharacterController>();
    58.         Cursor.visible = false;
    59.         Cursor.lockState = CursorLockMode.Locked;
    60.     }
    61.  
    62.     void Update()
    63.     {
    64.         #region Movement
    65.         //Set variables to preferences (obtained through Arcane_GameManager static reference)
    66.         playerLookSensitivity = float.Parse(Arcane_GameManager.manager.GetPref("playerLookSpeed"));
    67.         playerWalkingSpeed = float.Parse(Arcane_GameManager.manager.GetPref("playerWalkingSpeed"));
    68.         playerRunningSpeed = float.Parse(Arcane_GameManager.manager.GetPref("playerRunningSpeed"));
    69.         playerJumpStrength = float.Parse(Arcane_GameManager.manager.GetPref("playerJumpHeight"));
    70.         vertRotLimit = float.Parse(Arcane_GameManager.manager.GetPref("playerVertLimit"));
    71.         gravityModifier = float.Parse(Arcane_GameManager.manager.GetPref("playerGravMod"));
    72.         airControl = bool.Parse(Arcane_GameManager.manager.GetPref("playerAirCtrl"));
    73.  
    74.         //Horizontal Look
    75.         float horiRot = Input.GetAxis("Mouse X") * (playerLookSensitivity / 4);
    76.         transform.Rotate(0, horiRot, 0);
    77.  
    78.         //Vertical Look
    79.         vertRot -= Input.GetAxis("Mouse Y") * (playerLookSensitivity / 4);
    80.  
    81.         //Clamp (Limit) view angles to +vertRotLimit and -vertRotLimit
    82.         vertRot = Mathf.Clamp(vertRot, -vertRotLimit, vertRotLimit);
    83.  
    84.         //Set camera rotation
    85.         Camera.main.transform.localRotation = Quaternion.Euler(vertRot, 0, 0);
    86.  
    87.         //Look With Keys
    88.         //Horizontal
    89.         if (Input.GetKey(Arcane_GameManager.manager.GetCont("contRotLeft")))
    90.         {
    91.             transform.Rotate(0, -1.25f, 0);
    92.         }
    93.         if (Input.GetKey(Arcane_GameManager.manager.GetCont("contRotRight")))
    94.         {
    95.             transform.Rotate(0, 1.25f, 0);
    96.         }
    97.         //Vertical
    98.         if (Input.GetKey(Arcane_GameManager.manager.GetCont("contLookUp")))
    99.         {
    100.             vertRot += -1f;
    101.         }
    102.         if (Input.GetKey(Arcane_GameManager.manager.GetCont("contLookDown")))
    103.         {
    104.             vertRot += 1f;
    105.         }
    106.  
    107.         //Movement
    108.         if (!airControl && cc.isGrounded)
    109.         {
    110.             forwardMovt = CustomAxis(0f, Arcane_GameManager.manager.GetCont("contForward"), Arcane_GameManager.manager.GetCont("contBackward")) * playerWalkingSpeed;
    111.             sidewaysMovt = CustomAxis(0f, Arcane_GameManager.manager.GetCont("contRight"), Arcane_GameManager.manager.GetCont("contLeft")) * playerWalkingSpeed;
    112.  
    113.             //Sprint toggle
    114.             if (Input.GetKeyDown(Arcane_GameManager.manager.GetCont("contSprintToggle")))
    115.             {
    116.                 Arcane_GameManager.manager.sprintToggle = !Arcane_GameManager.manager.sprintToggle;
    117.             }
    118.  
    119.             //If Running
    120.             if (Input.GetKey(Arcane_GameManager.manager.GetCont("contSprintHold")) || Arcane_GameManager.manager.sprintToggle)
    121.             {
    122.                 forwardMovt = Input.GetAxis("Vertical") * playerRunningSpeed;
    123.                 sidewaysMovt = Input.GetAxis("Horizontal") * playerRunningSpeed;
    124.             }
    125.         }
    126.         else if (airControl)
    127.         {
    128.             forwardMovt = CustomAxis(0f, Arcane_GameManager.manager.GetCont("contForward"), Arcane_GameManager.manager.GetCont("contBackward")) * playerWalkingSpeed;
    129.             sidewaysMovt = CustomAxis(0f, Arcane_GameManager.manager.GetCont("contRight"), Arcane_GameManager.manager.GetCont("contLeft")) * playerWalkingSpeed;
    130.  
    131.             //Sprint toggle
    132.             if (Input.GetKeyDown(Arcane_GameManager.manager.GetCont("contSprintToggle")))
    133.             {
    134.                 Arcane_GameManager.manager.sprintToggle = !Arcane_GameManager.manager.sprintToggle;
    135.             }
    136.  
    137.             //If Running
    138.             if (Input.GetKey(Arcane_GameManager.manager.GetCont("contSprintHold")) || Arcane_GameManager.manager.sprintToggle)
    139.             {
    140.                 forwardMovt = Input.GetAxis("Vertical") * playerRunningSpeed;
    141.                 sidewaysMovt = Input.GetAxis("Horizontal") * playerRunningSpeed;
    142.             }
    143.         }
    144.  
    145.         //Add Gravity
    146.         verticalVelocity += (Physics.gravity.y * gravityModifier) * Time.deltaTime;
    147.  
    148.         //Jump
    149.         if (Input.GetKey(Arcane_GameManager.manager.GetCont("contJump")) && cc.isGrounded)
    150.         {
    151.             verticalVelocity = playerJumpStrength;
    152.         }
    153.  
    154.         //Player Lands
    155.         if(Input.GetKey(Arcane_GameManager.manager.GetCont("contJump")) && !cc.isGrounded)
    156.         {
    157.             hasJumped = true;
    158.         }
    159.  
    160.         //Head bob
    161.         if(hasJumped && cc.isGrounded)
    162.         {
    163.             StartCoroutine(LandHeadBob(Camera.main.transform.localPosition, bobHeight, 1.0f));
    164.             hasJumped = false;
    165.         }
    166.  
    167.         //Create movement vector
    168.         Vector3 playerMovement = new Vector3(sidewaysMovt, verticalVelocity, forwardMovt);
    169.         //Apply movement vector to CharacterController
    170.         cc.Move(transform.rotation * playerMovement * Time.deltaTime);
    171.         #endregion
    172.        
    173.         #region Camera Bob
    174.         bobSpeedWalk = float.Parse(Arcane_GameManager.manager.GetPref("playerHBobWlkSpd"));
    175.         bobSpeedRun = float.Parse(Arcane_GameManager.manager.GetPref("playerHBobRunSpd"));
    176.         bobHeight = float.Parse(Arcane_GameManager.manager.GetPref("playerHBobHgt"));
    177.         bobMid = float.Parse(Arcane_GameManager.manager.GetPref("playerHeight")) - (float)(Math.Truncate(float.Parse(Arcane_GameManager.manager.GetPref("playerHeight"))));
    178.  
    179.         float waveslice = 0f;
    180.  
    181.         Vector3 posConvert = Camera.main.transform.localPosition;
    182.  
    183.         if (Mathf.Abs(sidewaysMovt) == 0 && Mathf.Abs(forwardMovt) == 0)
    184.         {
    185.             bobTimer = 0f;
    186.         }
    187.         else
    188.         {
    189.             waveslice = Mathf.Sin(bobTimer);
    190.             //If Running
    191.             if (Input.GetKey(Arcane_GameManager.manager.GetCont("contSprintHold")) || Arcane_GameManager.manager.sprintToggle)
    192.             {
    193.                 bobTimer = bobTimer + bobSpeedRun;
    194.             }
    195.             else
    196.             {
    197.                 //Walking
    198.                 bobTimer = bobTimer + bobSpeedWalk;
    199.             }
    200.  
    201.             if (bobTimer > Mathf.PI * 2)
    202.             {
    203.                 bobTimer = bobTimer - (Mathf.PI * 2);
    204.             }
    205.         }
    206.  
    207.         if (waveslice != 0)
    208.         {
    209.             float translateChange = waveslice * bobHeight;
    210.             float totalAxes = Mathf.Abs(sidewaysMovt) + Mathf.Abs(forwardMovt);
    211.             totalAxes = Mathf.Clamp(totalAxes, 0.0f, 1.0f);
    212.             translateChange = totalAxes * translateChange;
    213.             if (isHeadBobbing)
    214.             {
    215.                 posConvert.y = bobMid + translateChange;
    216.             }
    217.             else if (!isHeadBobbing)
    218.             {
    219.                 posConvert.x = translateChange;
    220.             }
    221.         }
    222.         else
    223.         {
    224.             if (isHeadBobbing)
    225.             {
    226.                 posConvert.y = bobMid;
    227.             }
    228.             else if (!isHeadBobbing)
    229.             {
    230.                 posConvert.x = 0;
    231.             }
    232.         }
    233.  
    234.         Camera.main.transform.localPosition = posConvert;
    235.         #endregion
    236.     }
    237.  
    238.     IEnumerator LandHeadBob(Vector3 start, float y_target, float duration)
    239.     {
    240.         float elapsed_time = 0;
    241.         Vector3 pos = start;
    242.         float y_start = pos.y;
    243.  
    244.         while(elapsed_time < duration)
    245.         {
    246.             pos.y = EaseOutExpo(y_start, y_target, elapsed_time / duration);
    247.             start = pos;
    248.             yield return null;
    249.             elapsed_time += Time.deltaTime;
    250.         }
    251.     }
    252.  
    253.     public static float EaseOutExpo(float start, float end, float value)
    254.     {
    255.         end -= start;
    256.         return end * (-Mathf.Pow(2, -10 * value) + 1) + start;
    257.     }
    258.  
    259.     //Smooth movement output
    260.     float CustomAxis(float value, KeyCode positive, KeyCode negative)
    261.     {
    262.         float value2 = value;
    263.  
    264.         if (Input.GetKey(positive) && !Input.GetKey(negative))
    265.         {
    266.             value2 = Mathf.SmoothStep(value, 1f, 1f);
    267.         }
    268.         if (Input.GetKey(negative) && !Input.GetKey(positive))
    269.         {
    270.             value2 = Mathf.SmoothStep(value, -1f, 1f);
    271.         }
    272.         if (!Input.GetKey(positive) && !Input.GetKey(negative))
    273.         {
    274.             value2 = Mathf.SmoothStep(value, 0f, 1f);
    275.         }
    276.         return value2;
    277.     }
    278.  
    279.     public void SetPlayerHeight(float totalHeight)
    280.     {
    281.         //Set CharacterController Height
    282.         cc.height = (float)(Math.Truncate(totalHeight) + 1);
    283.  
    284.         //Set Camera Height
    285.         Vector3 camPos = new Vector3(0, (cc.height - totalHeight), 0);
    286.         Camera.main.transform.localPosition = camPos;
    287.     }
    288. }
    289.  
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,697
    Camera stuff is tricky. Have you looked into Cinemachine? It might do exactly what you want out of the box.

    Otherwise you need to accumulate your own offset to the camera based on the acceleration of your character, which is the first derivative of the speed, i.e., you take the speed THIS frame, compare it to the speed LAST frame, and that is the instantaneous acceleration you experienced, such as slamming into the ground.

    Then you use that offset in some kind of accumulator that slowly fades itself back to zero (Vector3.Lerp works for this) so that large inputs (such as landing) will transiently deflect it, then it will restore itself.

    I implemented such a system in my Jetpack Kurt Space Flight game, with a fair amount of magic numbers and fiddling. Here is the source code for the function that accumulates and deflects the camera from sudden changes to velocity.

    Code (csharp):
    1.     Vector3 previousVelocity;
    2.     Vector3 AccumulatedDifferential;
    3.     Vector3 AccumulatedDeflection;  // this is added to offset the camera from its desired neutral point
    4.  
    5.     public void MyUpdateCameraPhysics( Rigidbody rb)
    6.     {
    7.         Vector3 currentVelocity = rb.velocity;
    8.  
    9.         Vector3 acceleration = previousVelocity - currentVelocity;
    10.  
    11.         const float AccelerationScale = 200.0f;
    12.         const float MaxAcceleration = 0.2f;
    13.         const float Rebound = 5.0f;
    14.         const float Damping = 10.0f;
    15.  
    16.         acceleration /= AccelerationScale;
    17.  
    18.         if (acceleration.magnitude > MaxAcceleration)
    19.         {
    20.             acceleration = acceleration.normalized * MaxAcceleration;
    21.         }
    22.  
    23.         AccumulatedDifferential += acceleration;
    24.  
    25.         AccumulatedDeflection += AccumulatedDifferential;
    26.  
    27.         AccumulatedDifferential -= AccumulatedDeflection * Rebound * Time.deltaTime;
    28.  
    29.         AccumulatedDeflection -= AccumulatedDeflection * Damping * Time.deltaTime;
    30.  
    31.         previousVelocity = currentVelocity;
    32.     }
    33.  
    All of that is deep within the camera infrastructure, which also drives an airframe shaking effect when you add huge amounts of power, and also allows gaze-around control, etc.

    You can see it in action here: