Restore normal speed after an amount of time

So I add a small functionality to restore driver speed to its normal speed after 3 seconds, using Coroutine. Might do the same for the bump part in the future. Please take a look.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarScript : MonoBehaviour
{
  [SerializeField] float SteerSpeed = 230.0f;
  [SerializeField] float TopSpeed = 30.0f;
  [SerializeField] float MoveSpeed = 20.0f;
  [SerializeField] float NeutralSpeed = 10.0f;
  [SerializeField] float BoostTime = 3.0f;

  [SerializeField] float _speed;
  private bool _shouldSlowDown;
  private float _marker;
  private Coroutine _boostCoroutine;

  void Start()
  {
    this._speed = this.MoveSpeed;
    this._shouldSlowDown = false;
  }

  void Update()
  {
    if (this._shouldSlowDown)
    {
      this._shouldSlowDown = false;
      this._boostCoroutine = this.StartCoroutine(this.RestoreSpeed(this.BoostTime));
    }

    float moveAmount = Input.GetAxis("Vertical") * this._speed * Time.deltaTime;
    int steerMark = 1;
    if (moveAmount < 0)
    {
      steerMark = -1;
    }
    float steerAmount = -Input.GetAxis("Horizontal") * this.SteerSpeed * Time.deltaTime * steerMark;
    this.transform.Rotate(0, 0, steerAmount);
    this.transform.Translate(0, moveAmount, 0);
  }

  void OnCollisionEnter2D(Collision2D other)
  {
    this._speed = this.NeutralSpeed;
  }

  void OnTriggerEnter2D(Collider2D other)
  {
    if (other.CompareTag("Boost"))
    {
      this._StopRestoreSpeed();
      this._speed = this.TopSpeed;
    }
  }

  void OnTriggerExit2D(Collider2D other)
  {
    if (other.CompareTag("Boost"))
    {
      this._shouldSlowDown = true;
    }
  }

  IEnumerator RestoreSpeed(float time)
  {
    while (this._marker < time)
    {
      Debug.Log("Time marker: " + this._marker);
      this._marker += Time.deltaTime;
      if (this._marker >= time)
      {
        this._speed = this.MoveSpeed;
        this._StopRestoreSpeed();
      }
      yield return null;
    }
  }

  private void _StopRestoreSpeed() {
    if (this._boostCoroutine != null)
    {
      this._marker = 0;
      this.StopCoroutine(this._boostCoroutine);
    }
  }
}
1 Like

Here is an updated version with both boost and bump speed restoring. Much more simpler.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarScript : MonoBehaviour
{
  [SerializeField] float SteerSpeed = 230.0f;
  [SerializeField] float TopSpeed = 30.0f;
  [SerializeField] float MoveSpeed = 20.0f;
  [SerializeField] float NeutralSpeed = 10.0f;
  [SerializeField] float BoostTime = 3.0f;
  [SerializeField] float BumpTime = 2.0f;

  [SerializeField] float _speed;
  private Coroutine _restoreCoroutine;

  void Start()
  {
    this._speed = this.MoveSpeed;
  }

  void Update()
  {
    float moveAmount = Input.GetAxis("Vertical") * this._speed * Time.deltaTime;
    int steerMark = 1;
    if (moveAmount < 0)
    {
      steerMark = -1;
    }
    float steerAmount = -Input.GetAxis("Horizontal") * this.SteerSpeed * Time.deltaTime * steerMark;
    this.transform.Rotate(0, 0, steerAmount);
    this.transform.Translate(0, moveAmount, 0);
  }

  void OnCollisionEnter2D(Collision2D other)
  {
    this.StopRestoreSpeed();
    this._speed = this.NeutralSpeed;
  }

  void OnCollisionExit2D(Collision2D other)
  {
    this._restoreCoroutine = this.StartCoroutine(this.RestoreSpeed(this.BumpTime));
  }

  void OnTriggerEnter2D(Collider2D other)
  {
    if (other.CompareTag("Boost"))
    {
      this.StopRestoreSpeed();
    }
  }

  void OnTriggerStay2D(Collider2D other)
  {
    if (other.CompareTag("Boost"))
    {
      this._speed = this.TopSpeed;
    }
  }

  void OnTriggerExit2D(Collider2D other)
  {
    if (other.CompareTag("Boost"))
    {
      this._restoreCoroutine = this.StartCoroutine(this.RestoreSpeed(this.BoostTime));
    }
  }

  IEnumerator RestoreSpeed(float time)
  {
    yield return new WaitForSeconds(time);

    this._speed = this.MoveSpeed;
  }

  private void StopRestoreSpeed()
  {
    if (this._restoreCoroutine != null)
    {
      this.StopCoroutine(this._restoreCoroutine);
    }
  }
}
2 Likes

If I’m reading your logic correctly, sometimes with this methodology it causes a bug when you hit two or more boosts.

Say you’re boosting for 3 seconds. You hit the first boost which queues the slow down in 3 seconds. You hit the second boost say at the 2 second mark and the first boost slows it down to the original speed at the 3 second mark wasting the extra 2 seconds from the second boost.

I find having an extra variable/check telling the function what time the boost will slow down can help, depending on the effect you’re trying to have (whether the second boost will go even faster or only extend the boost speed time).

I just have to say, please remove all the this keywords, it makes your code really hard to read and it is unnecessary to have it all the time.

1 Like

The code already has the logic to reset the coroutine when you hit the boost and bump again. I’ve tested with these steps:

  1. Step into a boost.
  2. Step out of that boost, wait for less than 3 seconds.
  3. Step into that boost again.
  4. Step out of that boost again, the slow down time is reset correctly.

Thanks for the advice, I try to minimize the use of “this”. I wish there could be a standard convention that we can all follow. I’ve also do some digging and it seems “this” might be helpful in the project that could include junior programmer, here: https://softwareengineering.stackexchange.com/a/70230/298015

That was quite an interesting read, thanks for sharing that link.

Now that I read your code more carefully, what I don’t really like about it is the inconsistency with your naming conventions, some of your member variables start with an underscore and others with an upper case which I personally don’t like because usual naming conventions (for C#) state that variable names should always start with a lower case letter and that underscores should be avoided.

The only difference I noticed is that _speed changes its value while the others don’t, I suppose that’s why you are making that distinction but… I don’t know, it seems like way too many naming rules to learn, the fewer things you have to memorize to read code the better.

My suggestion would be to remove that speed variable and calculate it on the go instead of having it cached when you don’t actually need it to be cached. I think that’s the whole issue with your code, half of your methods are recalculating _speed which makes me think that you could simplify your code by having a single method to deal with that, I think you can remove the StopRestoreSpeed method with this approach, which is a win because that method is being called everywhere.

The convention I’m using is from microsoft C# conventions, here.

The underscore follows by lower-case character is for private field. And about the “_speed” , it’s suppose to be private, I just add [SerializeField] only for debugging :slight_smile:

Camel case

Use camel casing (“camelCasing”) when naming private or internal fields, and prefix them with _ .

That’s interesting. I believe this used to be the standard operating procedure in Unity a long time back but I never remember that being part of Microsoft’s coding conventions… at least a few years back. Personally speaking, I’ve always liked it when I saw people using underscore for private variables.

I don’t like underscores, because of four reasons:

  1. Use properties instead.
  2. Have as few cached values as possible.
  3. Microsoft contradicts Microsoft. Which is kinda hilarious but not that surprising.
  4. It makes the inspector harder to read unless you treat serialized variables as public which just adds more unnecessary complexicity. That’s why a lot of official Unity tutorials very rarely use underscores unless the code is a pure C# class. You gotta think of your Game Designer too.

Variables marked as serialized fields are still private unless you explicitly marked them as public, which might be a little confusing because setting a variable as public makes it serializable but marking it as serializable doesn’t make it public, so all those variables should have an underscore with the convention you are using, which might also kinda kill the purpose of using the this keyword.

Thanks, I will keep that in mind. I’ll try to create and update my convention to a reasonable sense as I progress my learning.

1 Like

Here is my code for slowing and speeding up. Tried to use IEnumerator and Coroutines but got confused and stuck when trying to grasp how they work :hugs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Driver : MonoBehaviour
{
    [SerializeField] float steerSpeed = 1f;
    [SerializeField] float moveSpeed = 0.01f;
    [SerializeField] float slowSpeed = 0.01f;
    [SerializeField] float boostSpeed = 0.01f;
    [SerializeField] int boostDuration = 2;
    float regularSpeed = 20;

    private bool isBoostActivated = false;
    private bool hasCarCrashed = false;
    
    void Start() 
    {

    }

    void Update()
    {
        float steerAmount = Input.GetAxis("Horizontal") * steerSpeed * Time.deltaTime;
        float moveAmount = Input.GetAxis("Vertical") * moveSpeed * Time.deltaTime; 
        transform.Rotate(0, 0, -steerAmount);
        transform.Translate(0, moveAmount, 0);
        if(isBoostActivated)
        {
            moveSpeed = boostSpeed;
        }
        if(hasCarCrashed)
        {
            moveSpeed = slowSpeed;
        }
    }

    void OnCollisionEnter2D(Collision2D other) 
    {
        if (!hasCarCrashed)
        {
            Debug.Log("Slowing player speed");
            hasCarCrashed = true;
        }
        Invoke("EndCrash", boostDuration);
    }
    void OnTriggerEnter2D(Collider2D other) 
    {
        if(other.tag == "Boost")
        {
            if (!isBoostActivated)
            {
                Debug.Log("Boosting player speed");
                isBoostActivated = true;
            }
            Invoke("EndBoost", boostDuration);
        }

        if(other.tag == "Car")
         
            if (!hasCarCrashed)
            {
                Debug.Log("Slowing player speed");
                hasCarCrashed = true;
            }
            Invoke("EndCrash", boostDuration);
        }
    private void EndBoost()
    {
        isBoostActivated = false;
        moveSpeed = regularSpeed;
    }
    private void EndCrash()
    {
        hasCarCrashed = false;
        moveSpeed = regularSpeed;
    }
}```
1 Like

Hello all!

I just finished the top racer section and I was also wondering how to reset the speed. After looking around I found out about “Invoke” and is by far the easiest way to solve this. All you need to use is:

Invoke(“NameOfYourFuction”, time you want to wait till reset).

Here’s my code:

[Header ("Attributes")]
    [SerializeField] float steerSpeed = 0.1f;
    [SerializeField] float moveSpeed = 20f;
    [SerializeField] float slowSpeed = 15f;
    [SerializeField] float boostSpeed = 30f;
    bool isBoosted = false;


    void Update()
    {
        float steerAmount = Input.GetAxis("Horizontal") * steerSpeed * Time.deltaTime;
        float moveAmount = Input.GetAxis("Vertical") * moveSpeed * Time.deltaTime;
        transform.Rotate(0, 0, -steerAmount);
        transform.Translate(0, moveAmount, 0);

        if(isBoosted)
        {

            isBoosted = false;
            Invoke("ResetSpeed", 1);
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        moveSpeed = slowSpeed;
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag == "Boost")
        {
            moveSpeed = boostSpeed;
            isBoosted = true;
        }

        if(collision.tag == "Obstacle")
        {
            moveSpeed = slowSpeed;
        }
    }

    void ResetSpeed()
    {
        moveSpeed = 20f;
    }

Privacy & Terms