统一管理时间事件的正确方法

Muh*_*han 1 c# time events unity-game-engine

我必须做一些基于时间的任务(模拟),例如,当游戏开始时:

  • 2 分钟后,A 列列车启动。
  • 6 分钟后,星号列车 B
  • 12分钟后开始火车C等

但请记住,如果我想快速查看模拟,计时器应该能够加快速度。

现在我正在考虑两种方法(也欢迎其他方法,这就是问题的目的)

每个对象(Train)脚本通过 WaitForSeconds 管理其时间:

void Start()
{
    StartCoroutine("MyEvent");
}
     
private IEnumerator MyEvent()
{
    
    yield return new WaitForSeconds(120f); // wait two minutes
    //Launch Train
  
}
Run Code Online (Sandbox Code Playgroud)

这种脚本附加到每个需要在一定时间后执行操作的对象:

问题:

  1. 我怎样才能加快它的时间?
  2. 也许性能密集型。由于每个脚本管理自己的协同例程(也许我错了)

计时器的一个全局脚本:

    function Update ()
    {
    Timer += Time.deltaTime; //Time.deltaTime will increase the value with 1 every second.
    if (timer>= 120){
    //launch train, an so one conditions
   //Or get timer variable in other script and compare time on update 
    }

} 
Run Code Online (Sandbox Code Playgroud)

现在使用上面的脚本,我可以在另一个脚本中获取 Timer 变量,并可以根据方法中的时间执行我的任务update

问题是我该如何管理它?第一种方式还是第二种方式还是第三种方式(由你决定)?因为我也想加快时间,这在注册后的协同例程中似乎是不可能的。

需要你们的帮助!

der*_*ugo 5

您可以使用这两种方法中的任何一种,如果您想在示例中查看更快/更慢等,只需更改Time.timescaleSpace即可按:

public class Example : MonoBehaviour
{
    // Toggles the time scale between 1 (normal) and 0.5 (twice as fast)
    // whenever the user hits the Space key.

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (Time.timeScale == 1.0f)
            {
                Time.timeScale = 0.5f;
            }
            else
            {
                Time.timeScale = 1.0f;
            }

            // Also adjust fixed delta time according to timescale
            // The fixed delta time will now be 0.02 frames per real-time second
            Time.fixedDeltaTime = 0.02f * Time.timeScale;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于协程来说,无需更改任何内容即可实现此功能,因为WaitForSeconds受以下因素影响Time.timescale

实际暂停时间等于给定时间乘以Time.timeScale。


Time.deltaTimeafaik不受此影响,Time.timescale因此为了更快地重播,您必须这样做

private void Update ()
{
    Timer += Time.deltaTime * (1 / Time.timescale); 

    if (timer >= 120)
    {
        //launch train, an so one conditions
        //Or get timer variable in other script and compare time on update 
    }
}
Run Code Online (Sandbox Code Playgroud)

一个Update或多个协程的性能是否更高在很大程度上取决于特定的用例。老实说,直到您运行大约 10.000 个协程时,这一点才真正引人注目(不要在这里用数字来指责我;))。

在您仅引发一个或多个事件的情况下,最好坚持使用一种Update()方法并调用一个事件或类似的事件。

但是- 为什么不简单地使用一个协程而不是一个协程Update

public class GlobalTimer : MonoBehaviour
{
    public float Delay = 2.0f;
    public UnityEvent onTimerDone;

    // Unity allows to use Start as IEnumerator instead of a void
    private IEnumerator Start()
    {
        yield return new WaitforSeconds(delay);

        onTimerDone.Invoke();
    }
}
Run Code Online (Sandbox Code Playgroud)

比您只有一个调用的方法并且可以向该计时器添加回调,例如

globalTimerReference.onTimerDone.AddListener(() => {
    Debug.LogFormat("Timer of {0} seconds is done now.", globalTimerReference.Delay);
});
Run Code Online (Sandbox Code Playgroud)

或通过检查员(如a中UI.Button.onClick)。

对于多个事件,我刚刚想出了一个快速而肮脏的解决方案,因此您可以简单地通过检查器定义多个事件并添加各种回调和内容:

public class GlobalTimer : MonoBehaviour
{
    public List<UnityEvent> events;
    public List<float> delays;

    private void Start()
    {
        var validPairs = Mathf.Min(events.Count, delays.Count);

        for (int i = 0; i < validPairs; i++)
        {
            StartCoroutine(InvokeDelayed(events[i], delays[i]));
        }
    }

    private IEnumerator InvokeDelayed(UnityEvent unityEvent, float delay)
    {
        yield return new WaitForSeconds(delay);

        unityEvent.Invoke();
    }
}
Run Code Online (Sandbox Code Playgroud)

只需确保列表中的每个事件都有延迟即可。将来您可能想要编写一个合适的自定义编辑器,以便在检查器中编辑得更漂亮。

更新

或者你可以拿我的:D

public class ExampleScript : MonoBehaviour
{
    [SerializeField] private List<EventDelayPair> EventDelayPairs;

    private void Start()
    {
        foreach (var eventDelayPair in EventDelayPairs)
        {
            StartCoroutine(InvokeDelayed(eventDelayPair.unityEvent, eventDelayPair.Delay));
        }
    }

    private IEnumerator InvokeDelayed(UnityEvent unityEvent, float delay)
    {
        yield return new WaitForSeconds(delay);

        unityEvent.Invoke();
    }

    [Serializable]
    private class EventDelayPair
    {
        public UnityEvent unityEvent;
        public float Delay;
    }
}

[CustomEditor(typeof(ExampleScript))]
public class ExampleInspector : Editor
{
    private SerializedProperty EventDelayPairs;
    private ReorderableList list;

    private ExampleScript _exampleScript;

    private void OnEnable()
    {
        _exampleScript = (ExampleScript)target;

        EventDelayPairs = serializedObject.FindProperty("EventDelayPairs");

        list = new ReorderableList(serializedObject, EventDelayPairs)
        {
            draggable = true,
            displayAdd = true,
            displayRemove = true,
            drawHeaderCallback = rect =>
            {
                EditorGUI.LabelField(rect, "DelayedEvents");
            },
            drawElementCallback = (rect, index, sel, act) =>
            {
                var element = EventDelayPairs.GetArrayElementAtIndex(index);

                var unityEvent = element.FindPropertyRelative("unityEvent");
                var delay = element.FindPropertyRelative("Delay");


                EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), delay);

                rect.y += EditorGUIUtility.singleLineHeight;

                EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUI.GetPropertyHeight(unityEvent)), unityEvent);


            },
            elementHeightCallback = index =>
            {
                var element = EventDelayPairs.GetArrayElementAtIndex(index);

                var unityEvent = element.FindPropertyRelative("unityEvent");

                var height = EditorGUI.GetPropertyHeight(unityEvent) + EditorGUIUtility.singleLineHeight;

                return height;
            }
        };
    }

    public override void OnInspectorGUI()
    {
        DrawScriptField();

        serializedObject.Update();

        list.DoLayoutList();

        serializedObject.ApplyModifiedProperties();
    }

    private void DrawScriptField()
    {
        // Disable editing
        EditorGUI.BeginDisabledGroup(true);
        EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour(_exampleScript), typeof(ExampleScript), false);
        EditorGUI.EndDisabledGroup();

        EditorGUILayout.Space();
    }
}
Run Code Online (Sandbox Code Playgroud)

例子

在此输入图像描述

或预览调试延迟

public class ExampleScript : MonoBehaviour
{
    public List<EventDelayPair> EventDelayPairs;

    private void Start()
    {
        foreach (var eventDelayPair in EventDelayPairs)
        {
            StartCoroutine(InvokeDelayed(eventDelayPair));
        }
    }

    private IEnumerator InvokeDelayed(EventDelayPair pair)
    {
        var timer = pair.Delay;

        do
        {
            timer -= Time.deltaTime * (1 / Time.timeScale);
            pair.Delay = timer;
            yield return null;
        } while (timer > 0);

        pair.Delay = 0;

        pair.unityEvent.Invoke();
    }

    [Serializable]
    public class EventDelayPair
    {
        public UnityEvent unityEvent;
        public float Delay;
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述


顺便说一句你的评论

//Time.deltaTime每秒将值增加1。

表述不正确。相反,它应该是:

Time.deltaTime将增加自上一帧渲染以来经过的时间(以秒为单位)的每一帧的值。


编辑 - 减少事后延迟

从你问的问题我就明白你想要speed up整个回放。

从评论中我现在了解到,相反,您希望减少事后的延迟。所以你不能使用Time.timescale.

为此,您可以使用稍作修改的第二个示例:

[Serializable]
public class EventDelayPair
{
    public UnityEvent unityEvent;
    public float Delay;
    // add a time multiplicator for each delay with default 1
    public float TimeMultiplicator = 1.0f;
}
Run Code Online (Sandbox Code Playgroud)

注意:如果您使用它,您还必须将其添加到 EditorScript - 我将其作为您的作业;)

private IEnumerator InvokeDelayed(EventDelayPair pair)
{
    var timer = pair.Delay;

    do
    {
        timer -= Time.deltaTime * pair.TimeMultiplicator;
        pair.Delay = timer;
        yield return null;
    } while (timer > 0);

    pair.Delay = 0;

    pair.unityEvent.Invoke();
}
Run Code Online (Sandbox Code Playgroud)

所以你可以在检查器中或者也可以通过脚本

exampleScriptReference.EventDelayPairs[0].TimeMultiplicator = 2;
Run Code Online (Sandbox Code Playgroud)

更快地减少延迟。

您可能还想添加一个整体乘数,例如

// Again you have to add it to the inspector if you use it
public float overallMultiplicator = 1.0f;

//...
    timer -= Time.deltaTime * pair.TimeMultiplicator * overallMultiplicator;
Run Code Online (Sandbox Code Playgroud)