如何在可编写脚本的对象中调用协程 [Unity3D]

How do I call a Co-Routine in a Scriptable Object [Unity3D]

我有一个协程,我正在尝试从中制作破折号脚本(使用统一新输入系统和标准统一 CharacterController)并在可编写脚本的对象中调用协程使统一卡住。

如果我将 controller.Move() 放在 Activate() 中,它会起作用,但会闪烁而不是破折号。我尝试使用协程来避免这种情况,但我不能在 ScriptableObjects 中使用协程,所以我创建了一个名为 MonoInstance 的空白 MonoBehaviour(您可以在下面看到)。我仅使用它来调用 StartCoroutine 函数。

这让 Unity 崩溃了,现在我的头很痛。 帮助


Tl;Dr - Unity 崩溃。怎么弄不死机。伤脑筋。


能力

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

    public virtual void Activate(GameObject parent) { }
}

冲刺能力

[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;
    }
}

单实例

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

public class MonoInstance : MonoBehaviour
{
    public static MonoInstance instance;

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

我讨厌团结,请在下面修复和解释:

冲刺能力

    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 试图一次做太多事情,并且每毫秒调用 time while 循环数千次,此时角色控制器(可能)甚至还没有移动(因为 Time.deltaTime 是 0,因为帧中的时间差不算什么)。这让unity哭了。这让我哭了。

添加 WaitForSeconds(?.??f) 使 unity 不再做太多事情,现在它不会崩溃。


Tl;dr - 告诉 unity 吃一颗镇静药,它就停止发作了

其实你的代码应该这样写。因为0.01秒不表示1帧的时长。

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

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

如果您不使用任何类型的新 WaitForXXX,您会得到相同的结果。

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();
}

这个returns让程序在定时器大于activeTime之前就yield。

我添加了一个委托,以便在 运行 结束时调用。你可能想在执行此操作时阻止某些动作发生,比如玩家在冲刺时不应再次冲刺,如下所示:

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

void ResetDashing()
{
     dashing = false;
}

上面是一个简单的例子,在 Dashing 协程完成循环之前你不能再次冲刺,然后将调用 ResetDashing 回调。