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

时间:2019-01-22 06:07:47

标签: c# unity3d events time

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

  • 经过2分钟后,启动TrainA。
  • 经过6分钟的训练后,火车B升起了星空
  • 在12分钟后开始火车C,依此类推

但是请记住,如果我想快速查看仿真,则计时器应该具有速度能力。

现在我正在考虑两种方法(也欢迎采用其他方法,这也是问题的原因)

每个对象(火车)脚本管理其时间:

void Start()
{
    StartCoroutine("MyEvent");
}

private IEnumerator MyEvent()
{

    yield return new WaitForSeconds(120f); // wait two minutes
    //Launch Train

}

就像这种脚本会在特定时间后附加到需要操作的每个对象上一样:

问题:

  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 
    }

} 

现在使用上述脚本,我可以在其他脚本中获取Timer变量,并可以根据更新方法中的时间执行任务。

现在,问题是如何管理它?是第一路,第二路还是第三路(由您)? 因为我也想加快时间,一旦注册,这在协同例程中似乎是不可能的。乡亲们需要您的帮助!

1 个答案:

答案 0 :(得分:3)

您可以使用两种方式中的任一种,如果要在示例中更快/更慢地查看Time.timescale,只需按 Space

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

对于协程,此方法无需更改任何内容,因为WaitForSecondsTime.timescale的影响:

  

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


Time.deltaTime afaik受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 
    }
}

一个Update或多个协程的性能更高,在很大程度上取决于特定的用例。 Afaik直到您可能要运行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();
    }
}

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

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

或通过检查器(例如在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();
    }
}

只需确保每个事件在列表中都有延迟。将来,您可能需要编写适当的CustomEditor,以便在检查器中对其进行更精美的编辑。

更新

或者您也可以选择: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();
    }
}

示例

enter image description here

或带有调试延迟预览

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

enter image description here


顺便说一句

  

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

注意:如果要使用它,还必须将其添加到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();
}

因此您可以在检查器中或通过脚本

exampleScriptReference.EventDelayPairs[0].TimeMultiplicator = 2;

更快地减少延迟。

您可能还想添加一个总体乘法器,例如

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

//...
    timer -= Time.deltaTime * pair.TimeMultiplicator * overallMultiplicator;