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

Bug Head Bob with Sine not working

Discussion in 'Scripting' started by Thrazamund, Jul 18, 2023.

  1. Thrazamund

    Thrazamund

    Joined:
    Apr 14, 2017
    Posts:
    36
    I found a sine head bob script online for my FPS which looks great but when I first move the character I sometimes get a jarring jolt to the camera. This only happens from moving from idle and I believe it has to do with the speed of the head bob initially being too much. I thought I could ease from a slower speed to my desired speed using a coroutine but the results I'm getting are unexpected. The period variable controls the time in between bobs I think. The period variable set to 0.5f is very slow and 0.11f is my desired speed but when I lerp the variable period from 0.5f to 0.11f the headbob actually speeds way up and then slows down to what I want when it reaches 0.11f. Am I misunderstanding what period or the math is here? I've tried so many things that any help would be greatly appreciated.

    Code (CSharp):
    1. public class HeadBobSine : MonoBehaviour
    2. {
    3.     [SerializeField]
    4.     private CMFirstPersonCharacter character;
    5.  
    6.     Vector3 startPos;
    7.     public float amplitude = 0.07f;
    8.     public float period = 0.11f;
    9.     public float returnSpeed = 3f;
    10.     public float bobPeriodStart 0.5f;
    11.     public float bobTimeToFullSpeed = 0.5f;  
    12.     public float bobPeroidFull =0.11f;
    13.  
    14.     void Start()
    15.     {
    16.         startPos = transform.localPosition;
    17.  
    18.         bobPeroidFull = period;
    19.     }
    20.  
    21.     void Update()
    22.     {
    23.         if (PlayerController.instance.walking)
    24.         {
    25.             if (period == bobPeriodStart)
    26.             {
    27.                 StartCoroutine(ChangeSpeedValue(period, bobPeroidFull, bobTimeToFullSpeed));
    28.             }
    29.  
    30.             float theta = Time.timeSinceLevelLoad / period;
    31.             float distance = amplitude * Mathf.Sin(theta);
    32.  
    33.             transform.localPosition = startPos + Vector3.up * distance;
    34.         }
    35.         else if (PlayerController.instance.running)
    36.         {
    37.             float theta = Time.timeSinceLevelLoad / (period / 1.25f);
    38.             float distance = (amplitude) * Mathf.Sin(theta);
    39.             transform.localPosition = startPos + Vector3.up * distance;
    40.         }
    41.         else
    42.         {
    43.             period = bobPeriodStart;
    44.             if (transform.localPosition != startPos)
    45.             {
    46.                 transform.localPosition = new Vector3(startPos.x, Mathf.Lerp(transform.localPosition.y, startPos.y, returnSpeed * Time.deltaTime), startPos.z);
    47.             }
    48.  
    49.         }
    50.  
    51.     }
    52.  
    53.     public IEnumerator ChangeSpeedValue(float oldValue, float newValue, float duration)
    54.     {
    55.         for (float t = 0f; t < duration; t += Time.deltaTime)
    56.         {
    57.             period = Mathf.Lerp(oldValue, newValue, t / duration);
    58.             yield return null;
    59.             period = newValue;
    60.         }
    61.     }
    62. }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,558
    I think that might be your problem.

    Generally you want to advance the phasor (the input to the sine function) by something relative to your lateral speed.

    Leave the amplitude alone until you fix the phase.
     
  3. Thrazamund

    Thrazamund

    Joined:
    Apr 14, 2017
    Posts:
    36
    I see. So you're saying to tie it to my player character's velocity? Can you give me an example? This is out of my knowledge base math wise what variable would I multiply with my velocity if not the amplitude?
     
    Last edited: Jul 18, 2023
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,558
    Yes I can. I just added head bob to the BasicFPCC character controller.

    I added these variables:

    Code (csharp):
    1.     float bobPhasorAccumulator;
    2.     [Header("Try 2.0 as a starting point (bob speed).")]
    3.     public float bobPhasorFactor = 2;
    4.     [Header("A non-rotating pivot to lift/lower camera.")]
    5.     public Transform BobTransform;
    And added this code immediately before the one call to controller.Move():

    Code (csharp):
    1.         bobPhasorAccumulator += calc.magnitude * bobPhasorFactor;
    2.         BobTransform.localPosition = Vector3.up * (Mathf.Sin( bobPhasorAccumulator) * 0.1f);
    The bob distance is the 0.1f

    I restructured the Player as follows:

    Screen Shot 2023-07-17 at 8.20.24 PM.png

    and hooked up the BobHead Transform to my script field.

    BAM.

    EDIT: Oh yeah, the BasicFPCC controller... I nearly forgot! Start here:

    https://forum.unity.com/threads/a-basic-first-person-character-controller-for-prototyping.1169491/

    That one has run, walk, jump, slide, crouch... it's crazy-nutty!!
     
  5. Thrazamund

    Thrazamund

    Joined:
    Apr 14, 2017
    Posts:
    36
    I really appreciate that. I got it up and running which is great but the bob seems different or maybe it's just because I have the exact settings. I want to get my old script to work. Here it is in it's simplest form:
    Code (CSharp):
    1. public class HeadBobSine : MonoBehaviour
    2. {
    3.     Vector3 startPos;
    4.     public float amplitude = 10f;
    5.     public float period = 5f;
    6.     public float returnSpeed = 3f;
    7.  
    8.     void Start()
    9.     {
    10.         startPos = transform.localPosition;
    11.     }
    12.  
    13.     void Update()
    14.     {
    15.         if (PlayerController.instance.walking)
    16.         {
    17.             float theta = Time.timeSinceLevelLoad / period;
    18.             float distance = amplitude * Mathf.Sin(theta);
    19.  
    20.             transform.localPosition = startPos + Vector3.up * distance;
    21.         }
    22.         else
    23.         {
    24.             if (transform.localPosition != startPos)
    25.             {
    26.                 transform.localPosition = new Vector3(startPos.x, Mathf.Lerp(transform.localPosition.y, startPos.y, returnSpeed * Time.deltaTime), startPos.z);
    27.             }
    28.         }
    29.     }
    30. }
    Is there anyone who can tell me why this might cause a jerk on the camera sometimes when going from a full stop to walking? Anyone have any solutions? I'd be forever grateful.
     
  6. KillDashNine

    KillDashNine

    Joined:
    Apr 19, 2020
    Posts:
    449
    Let me just say that head bobs are horrible and annoying and should be banned from FPS world and shunned by all humanity.

    Having said that, you should debug your bob function. Output its values and make sure the sine wave starts from 0. sin(x) oscillates between 1 and -1 based on x, and is zero at start (x=0).

    However, in Unity, anywhere you need a smooth transition, you want Lerp. The way I'd do it is to apply Lerp to the final value of your bob function, based on the time you character has been walking. So, from the start of walking to, say, 0.5 seconds, Lerp from 0 to 1. So when the infernal neck wobbling that everybody hates starts, it will phase in rather than starting directly. Also you might phase it out after the character is stopped, Lerp the camera to its original position.

    Note that you can also Lerp Vector3s with Vector3.Lerp.
     
    zulo3d likes this.
  7. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    510
    It's because you're starting walking at different points in time and so the sin value is always different. You'll need to create your own timer/counter that you can reset every time you stop walking. Or you could change it so that the head position constantly lerps to the intended bob height instead of being set directly to the height.

    Doing both of the above would actually be best.
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,558
    Because you're tacking onto a constantly-moving phasor, in this case Time.timeSinceLevelLoaded.

    That means when you start moving, the sine wave could be anywhere, not where you "left it."

    Use your own accumulator the way I showed above. Don't advance it when you're not moving.