0
\$\begingroup\$

A bit long post but both scripts are connected and working together.

The speed is a parameter in Animator that moves between 0 and 1.

At this speed I'm using first the SlowDown function to slow down smooth the "Forward" to 0 then the transform is stopped.

Then I'm trying to speed up back the transform by increasing the "Forward" value from 0 to 1 :

if (startRotatingBack && turnBack)
                {
                    ThirdPersonUserControl.stop = false;
    
                    float target = 1.0f;
                    float delta = target - current;
                    delta *= Time.deltaTime;
                    current += delta;
                    playerAnimator.SetFloat("Forward", current);
                    currentSpeed = playerAnimator.GetFloat("Forward").ToString();
                }

I can see when increasing the "Forward" back to 1 that it's getting very close to 1 it's getting to the value 0.999999 I'm not sure why it's not getting to 1 but this is not the problem yet.

When it's getting to the value 0.999999 and the transform is moving now fast again it's still not moving as fast as before slowing it down to 0 before slow down to 0 it was moving much faster and now when increasing it back 1 or to 0.999999 it's not moving the same speed as before.

Not that it matters I think if the speed is 0.999999 or 1 but I wonder why it's not getting to 1 when increasing the "Forward" parameter.

To make it reach the speed as before I must pressing the W key the value is 0.999999 but only if I press the W key holding W it will reach then the speed as before at the start before slowing down.

using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityStandardAssets.Characters.ThirdPerson;

public class PlayerSpaceshipAreaColliding : MonoBehaviour
{
    public float rotationSpeed =250;
    public float movingSpeed;
    public float secondsToRotate;
    public GameObject uiSceneText;
    public TextMeshProUGUI textMeshProUGUI;
    public Transform targetToRotateTo;

    private float timeElapsed = 0;
    private float lerpDuration = 3;
    private float startValue = 1;
    private float endValue = 0;
    private float valueToLerp = 0;
    private Animator playerAnimator;
    private bool exitSpaceShipSurroundingArea = false;
    private bool slowd = true;
    private bool startRotatingBack = false;
    private bool displayText = true;
    private bool turnBack = true;

    public float damping = 10;
    private float current = 0.0f;
    public string currentSpeed;

    // Start is called before the first frame update
    void Start()
    {
        playerAnimator = GetComponent<Animator>();
    }

    // Update is called once per frame
    void Update()
    {
        if (exitSpaceShipSurroundingArea)
        {
            if (slowd)
                SlowDown();

            if (playerAnimator.GetFloat("Forward") == 0)
            {
                slowd = false;

                if (displayText)
                {
                    uiSceneText.SetActive(true);

                    if (textMeshProUGUI.text != "")
                        textMeshProUGUI.text = "";

                    textMeshProUGUI.text = "I can see something very far in the distance, but it's too long to walk by foot.";
                    StartCoroutine(UITextWait());
                    displayText = false;
                }
            }

            if (startRotatingBack && turnBack)
            {
                ThirdPersonUserControl.stop = false;

                float target = 1.0f;
                float delta = target - current;
                delta *= Time.deltaTime;
                current += delta;
                playerAnimator.SetFloat("Forward", current);
                currentSpeed = playerAnimator.GetFloat("Forward").ToString();
            }
        }
    }

    IEnumerator UITextWait()
    {
        yield return new WaitForSeconds(5f);

        textMeshProUGUI.text = "";
        uiSceneText.SetActive(false);
        startRotatingBack = true;
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.name == "CrashLandedShipUpDown")
        {
            slowd = true;
            exitSpaceShipSurroundingArea = false;
            Debug.Log("Entered Spaceship Area !");
        }
    }

    private void OnTriggerExit(Collider other)
    {
        if (other.name == "CrashLandedShipUpDown")
        {
            ThirdPersonUserControl.stop = true;
            exitSpaceShipSurroundingArea = true;
            Debug.Log("Exited Spaceship Area !");
        }
    }

    private void SlowDown()
    {
        if (timeElapsed < lerpDuration)
        {
            valueToLerp = Mathf.Lerp(startValue, endValue, timeElapsed / lerpDuration);
            playerAnimator.SetFloat("Forward", valueToLerp);
            timeElapsed += Time.deltaTime;
        }

        playerAnimator.SetFloat("Forward", valueToLerp);
        valueToLerp = 0;
    }
}

This script is where I'm using the keys for the movements including the W key as to move the transform forward faster :

using System;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;

namespace UnityStandardAssets.Characters.ThirdPerson
{
    [RequireComponent(typeof(ThirdPersonCharacter))]
    public class ThirdPersonUserControl : MonoBehaviour
    {
        private ThirdPersonCharacter m_Character; // A reference to the ThirdPersonCharacter on the object
        private Transform m_Cam;                  // A reference to the main camera in the scenes transform
        private Vector3 m_CamForward;             // The current forward direction of the camera
        private Vector3 m_Move;
        private bool m_Jump;                      // the world-relative desired move direction, calculated from the camForward and user input.

        public static bool stop = false;


        private void Start()
        {
            // get the transform of the main camera
            if (Camera.main != null)
            {
                m_Cam = Camera.main.transform;
            }
            else
            {
                Debug.LogWarning(
                    "Warning: no main camera found. Third person character needs a Camera tagged \"MainCamera\", for camera-relative controls.", gameObject);
                // we use self-relative controls in this case, which probably isn't what the user wants, but hey, we warned them!
            }

            // get the third person character ( this should never be null due to require component )
            m_Character = GetComponent<ThirdPersonCharacter>();
        }


        private void Update()
        {
            if (!m_Jump)
            {
                m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
            }
        }


        // Fixed update is called in sync with physics
        private void FixedUpdate()
        {
            if (stop == false)
            {
                // read inputs
                float h = CrossPlatformInputManager.GetAxis("Horizontal");
                float v = CrossPlatformInputManager.GetAxis("Vertical");
                bool crouch = Input.GetKey(KeyCode.C);

                // calculate move direction to pass to character
                if (m_Cam != null)
                {
                    // calculate camera relative direction to move:
                    m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized;
                    m_Move = v * m_CamForward + h * m_Cam.right;
                }
                else
                {
                    // we use world-relative directions in the case of no main camera
                    m_Move = v * Vector3.forward + h * Vector3.right;
                }
#if !MOBILE_INPUT
                // walk speed multiplier
                if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f;
#endif

                // pass all parameters to the character control script
                m_Character.Move(m_Move, crouch, m_Jump);
                m_Jump = false;
            }
        }
    }
}
\$\endgroup\$
1
\$\begingroup\$

Ask Zeno. 😜

More seriously, you can plug your formulas into a spreadsheet to see for yourself:

Spreadsheet example

As current gets closer to target, delta gets closer to zero (and multiplying by deltaTime makes it smaller still, but does not actually help make this calculation framerate independent). So you add less and less of an increment every frame, making slower and slower progress.

After 42 frames at 60 fps, you've made it halfway to the target, but after another 42 frames, you're still only 75% of the way there (you crossed only half of the remaining half), and another 42 frames later you're only 87.5% of the way there (you crossed only half of the remaining quarter).

If you keep taking these 42-frame steps, each covering only half the remaining distance, you'd need an infinite number of such steps to actually reach your destination - otherwise after a step of size x you'd always still have another x distance left to go. At least, that would be the case with unlimited-precision real numbers.

But computers don't have unlimited precision, so you hit an even harder wall. Single-precision floating point numbers have 23 bits of mantissa. That means that close to our target of 1.0, we can only represent numbers in increments of \$2^{-23}\$, or about 0.000 000 119 209.

If the increment we try to add to current is less than half that number, \$2^{-24}\$ or 0.000 000 059 604, then it doesn't take us far enough to round to the next representable number, and current stays the same after the addition - it's like we added zero. At that point, our progress stalls completely.

That happens by around frame 748, or about 12 seconds of running this code at 60 fps.

To avoid this problem, you can try using Mathf.MoveTowards instead, something like this:

const float secondsToMaxSpeed = 2f;
const float acceleration = 1f/secondsToMaxSpeed;

current = Mathf.MoveTowards(current, 1.0f, acceleration * Time.deltaTime);

This gives a linear increase in speed, instead of the exponential decay you had before, and will reach 1.0 exactly in the number of seconds you set. It's also correctly adjusted for the framerate, so you'll get consistent behaviour across different devices.

\$\endgroup\$
1
  • \$\begingroup\$ Just noticed there's an error in the above. We can represent numbers slightly above 1 to a precision of \$2^{-23}\$, but slightly below 1 we can represent them to a precision of \$2^{-24}\$. The same argument applies, just at where our delta falls below \$2^{-25}\$ instead, which will change the frame number where we hit the wall. \$\endgroup\$ – DMGregory May 29 at 1:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.