1

I have a coroutine which I'm trying to make a dash script from (using the unity New Input System & standard unity CharacterController) and calling the Coroutine in the scriptable object makes unity seize up.

If I were to put the controller.Move() in Activate() it would work but blink instead of dash. I've tried to use a coroutine to avoid this, but I can't use coroutines in ScriptableObjects so instead I made a blank MonoBehaviour called MonoInstance (which you can see below). I use this solely to call the StartCoroutine function from.

This crashes Unity, now my head hurts. Help


Tl;Dr - Unity crash. How make not crash. Brain hurt.


Ability

public class Ability : ScriptableObject
{
    public new string name;
    public float cooldownTime;
    public float activeTime;

    public virtual void Activate(GameObject parent) { }
}

DashAbility

[CreateAssetMenu]
public class DashAbility : Ability
{
    private PlayerController movement;
    private CharacterController controller;

    public float dashVelocity;

    public override void Activate(GameObject parent)
    {
        movement = parent.GetComponent<PlayerController>();
        controller = parent.GetComponent<CharacterController>();

        MonoInstance.instance.StartCoroutine(Dashing());
    }

    IEnumerator Dashing()
    {
        float startTime = Time.time;
     
        while(Time.time < startTime + activeTime)
        {
            controller.Move(movement.move.normalized * dashVelocity * Time.deltaTime);
        }
     
        yield return null;
    }
}

MonoInstance

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

public class MonoInstance : MonoBehaviour
{
    public static MonoInstance instance;

    private void Start()
    {
        MonoInstance.instance = this;
    }
}

3 Answers3

1

I hate unity, fix & explanation down below:

DashAbility

    IEnumerator Dashing()
    {
        float startTime = Time.time;

        while (Time.time < startTime + activeTime)
        {
            controller.Move(movement.move.normalized * dashVelocity * Time.deltaTime);
            yield return new WaitForSeconds(0.01f);
        }
     
        yield return null;
    }

Unity tried to do too much at once, and called the time while loop thousands of times per millisecond where the character controller (probably) wouldn't even have moved yet (because Time.deltaTime was 0, since the time difference in frames is nothing). This made unity cry. This made me cry.

Adding a WaitForSeconds(?.??f) made unity not do so much, and now it doesn't crash.


Tl;dr - Told unity to take a chill pill and it stopped seizing up

  • You had a while loop with no return, so it would simply just loop it until done without quitting. Now you added that WaitForSeconds that should be a yield return null while the last line of your method does nothing (well it returns and comes back just to quit). Now that you added the WaitForSeconds, it will run the loop, then return for that frame. Next frame back where it left off and runs the loop again and this run/return process until the loop is done. – Everts May 24 '22 at 05:58
1

In fact, your code should be written this way. Because 0.01 seconds does not show the duration of 1 frame.

IEnumerator Dashing()
{
    var startTime = Time.time;

    while (Time.time < startTime + activeTime)
    {
        controller.Move(movement.move.normalized * dashVelocity * Time.deltaTime);
        yield return new WaitForEndOfFrame();
    }
}
KiynL
  • 4,097
  • 2
  • 16
  • 34
  • I'm not sure I quite understand, what's the functional difference between waiting for X seconds and waiting for the end of frame? – Peter Hopkins May 23 '22 at 15:13
  • `WaitForEndOfFrame () = WaitForSeconds (Time.deltaTime)`; It makes sense. Because the time of a frame is not always 0.01f seconds and is variable. For example in a game with a rate of 60 frames. The 1 second wait in while, is repeated 100 times on yield `WaitForSeconds (0.01)` but 60 times in `WaitForEndOfFrame`. – KiynL May 23 '22 at 15:20
  • I thought that frames per second was variable though, sometimes 30 sometimes 120 depending on how fast the processor is running. If not then I would absolutely use end of frame. EDIT: I also think I should point out here I'm not nessecarily trying to match the frame, is there a reason I should be. – Peter Hopkins May 23 '22 at 15:25
  • @PeterHopkins [Time.deltaTime](https://medium.com/star-gazers/understanding-time-deltatime-6528a8c2b5c8#:~:text=deltaTime%20is%20the%20completion%20time,executed%20at%20the%20same%20speed.) this link help you to better understand Time.deltaTime – KiynL May 23 '22 at 16:54
  • yield return null would do the same since it uses Time.deltaTime in the method. WaitForEndOfFrame would be used for actions that for instance needs to wait for the rendering to be done, like a screenshot. in this case, it makes no difference – Everts May 24 '22 at 05:59
  • You must use endOfFrame to build DASH. Do not forget that the player can stop the game during the dash. @Everts – KiynL May 24 '22 at 06:18
0

Here's how you'd have the same result, but you would not use any new WaitForXXX of any kind.

IEnumerator Dashing(Action onComplete)
{
    float timer = 0f;
    while (timer < activeTime)
    {
        timer += Time.deltaTime;
        controller.Move(movement.move.normalized * dashVelocity * Time.deltaTime);
        yield return null
    }
    onComplete?.Invoke();
}

This returns to the program on the yield until the timer is greater than the activeTime.

I added a delegate to call when the run is over. You may want to block some actions from happening while you do this, say while dashing the player should not be able to dash again, like this:

private bool dashing;
void Update()
{
    if(Input.GetKeyDown(KeyCode.Space) && dashing == false)
    {
         StartCoroutine(Dashing(ResetDashing));
    }
} 

void ResetDashing()
{
     dashing = false;
}

Above is a simple case where you cannot dash again until the Dashing coroutine is done looping and then will call the ResetDashing callback.

Everts
  • 10,408
  • 2
  • 34
  • 45
  • Hi, thanks for the solution! Unfortunately, I'm using both the New Input System & my dash is a Scriptable Object (want to be able to swap this out for a new ability on the fly). – Peter Hopkins May 24 '22 at 17:18