当倒数Deltatime达到一定数值时,使打印仅打印一次(统一)

时间:2019-06-12 07:32:38

标签: c# unity3d time

我有一个10秒钟的倒计时脚本,该脚本在DeltaTime上运行。

在我的Update函数中,我尝试使其仅打印一次,每当它到达第二个8时就打“ Hello”。

问题在于,deltaTime会在挂起8秒时重复打印“ Hello”,而不是仅打印一次,而且我不知道如何停止这种行为。

我一直试图在进入该块的if块中立即引入触发器,将其设置为0,但是只要计时器在第二个8上,它仍会持续打印“ Hello”。

倒数计时器

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

public class CountdownTimer : MonoBehaviour
{
    float currentTime = 0f;
    float startingTime = 10f;
    public int n = 0;
    public int switcher = 0;


    // Start is called before the first frame update
    void Start()
    {
        currentTime = startingTime;
    }

    // Update is called once per frame
    void Update()
    {        
        currentTime -= 1 * Time.deltaTime; //does it each frame
        n = Convert.ToInt32(currentTime);
        if (n == 8)
        {
            switcher = 1;
        }
    }
}

不同类中的更新方法

if (CountdownTimer.switcher == 1)
{
    CountdownTimer.switcher = 0;
    print("hey");
}

关于如何进行print(“ hey”)的任何想法只会发生一次?这很重要,因为以后我将用一种重要的方法替换打印代码,并且需要确保该方法仅发生一次。

3 个答案:

答案 0 :(得分:2)

这是您要实现具有事件/侦听器的订户系统的地方。

向倒数计时添加一个事件,如果倒数计时具有唯一性,您甚至可以将其设为静态。另外,如果在将switcher设置为1之后不再需要更新,则可以将其转换为协程

public class CountdownTimer : MonoBehaviour
{
    float currentTime = 0f;
    float startingTime = 10f;
    public static event Action RaiseReady;

    // Start is called before the first frame update
    void Start()
    {
        currentTime = startingTime;
        StartCoroutine(UpdateCoroutine());
    }

    // Update is called once per frame
    IEnumerator UpdateCoroutine()
    { 
        while(true)
        {       
            currentTime -= 1 * Time.deltaTime; //does it each frame
            int n = Convert.ToInt32(currentTime);
            if (n == 8)
            {
                RaiseReady?.Invoke();
                RaiseReady = null; // clean the event
                yield break; // Kills the coroutine
            }
            yield return null;
        }
    }
}

任何需要了解的组件:

public class MyClass : MonoBehaviour
{
     void Start()
     {
         CountdownTimer.RaiseReady += CountdownTimer_RaiseReady;
     }
     private void CountdownTimer_RaiseReady()
     {
          Debug.Log("Done");
          // Remove listener though the other class is already clearing it
          CountdownTimer.RaiseReady -= CountdownTimer_RaiseReady;
     }
}

答案 1 :(得分:1)

由于Updtade()每帧被调用一次,因此切换器在第8秒内被设置为每帧一次(并且在1秒内有很多帧)。

答案可能是这样的,以防止再次打印:

if (CountdownTimer.switcher == 1)
{
    if (!AlreadyDisplayed)
    {
        print("hey");
        AlreadyDisplayed = true;
    }
}

AlreadyDisplayed是声明时设置为false的布尔值。
这应该做您想要实现的。 :)

答案 2 :(得分:1)

@Everts提供的解决方案非常好,但是当您使用unity时,我建议您进行一些调整以更好地使用编辑器。我建议使用event名称空间中的UnityEvent而不是通用的UnityEngine.Events。由于统一如何跨场景序列化它们,我也建议不要使用静态。如果您不熟悉unity如何处理其序列化,可能会遇到一些奇怪的情况。如果您只需要向同一场景中的另一个对象发送消息,我实际上会推荐一名游戏管理员。您可以安全地在GameObject.Find()中执行onvalidate()并链接变量,以避免在运行时执行查找会影响性能。如果该消息需要将数据传送到其他场景,请改用ScriptableObject。如下图所示。

将此组件放置在场景的“游戏管理器” GameObject


    public class CountingPassthrough : MonoBehaviour
    {
        public CountdownTimer countdownTimer;
    }

将此组件放在场景的“计时器” GameObject

    public class CountdownTimer : MonoBehaviour
    {
        public float startingTime = 10f;
        public UnityEvent timedOut = new UnityEvent();

        private void OnValidate()
        {
            if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
                FindObjectOfType<CountingPassthrough>().countdownTimer = this;
        }

        private void Start()
        {
            StartCoroutine(TimerCoroutine());
        }

        // Coroutine is called once per frame
        private IEnumerator TimerCoroutine()
        {
            float currentTime = 0f;
            while (currentTime != 0)
            {
                currentTime = Mathf.Max(0, currentTime - Time.deltaTime);
                yield return null;//wait for next frame
            }
            timedOut.Invoke();
        }
    }

将此组件放在要使用计时器的GameObject

    public class user : MonoBehaviour
    {
        [SerializeField, HideInInspector]
        private CountingPassthrough timerObject;

        private void OnValidate()
        {
            if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
                timerObject = FindObjectOfType<CountingPassthrough>();
        }

        private void OnEnable()
        {
            timerObject.countdownTimer.timedOut.AddListener(DoSomething);
        }

        private void OnDisable()
        {
            timerObject.countdownTimer.timedOut.RemoveListener(DoSomething);
        }

        private void DoSomething()
        {
            //do stuff here...
        }
    }

该工作流程对预制件也很友好,因为您可以将find()中的onvalidate()if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene)包装起来,以防止从其他加载的场景中抓取错误的资产。再一次,如果您需要它来跨场景传输数据,则从CountingPassthrough继承ScriptableObject而不是MonoBehaviour,将新的ScriptableObject创建到项目文件夹中的某个位置,然后忽略如果检查以约束场景匹配,那么这是额外的。然后,如果使用跨场景ScriptableObject方法,则只需确保使用函数来查找包含资产的函数即可。

编辑:忘记了统一的2018+版本中的嵌套预制件边条。您需要为此添加帐户:&& FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene()我已经更新了上面的代码段。抱歉。