Получение печати только один раз, когда обратный отсчет Deltatime достигает определенного числа (Unity) - PullRequest
0 голосов
/ 12 июня 2019

У меня есть 10-секундный скрипт обратного отсчета, который работает на DeltaTime.

В моей функции обновления я пытаюсь заставить ее печататься, только один раз, "Hello", когда она достигает секунды 8.

Проблема в том, что deltaTime постоянно печатает «Hello», в то время как он зависает на 8-й секунде, а не только один раз, и я не знаю, как остановить это поведение.

Я продолжал пытаться ввести триггеры в блоке if, которые установлены в 0, как только блок введен, но он продолжает непрерывно печатать «Hello», пока таймер находится на секунде 8.

Таймер обратного отсчета

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

Любые идеи о том, как сделать печать ("эй"), происходят только один раз? Это важно, потому что позже я заменил бы код печати на важный метод, и мне нужно убедиться, что метод происходит только один раз.

Ответы [ 3 ]

2 голосов
/ 12 июня 2019

Здесь вы хотите реализовать систему подписчика с событием / слушателем.

Добавьте событие к обратному отсчету, если обратный отсчет должен быть уникальным, вы даже можете сделать его статическим.Кроме того, если обновление больше не требуется после установки переключателя в 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 голос
/ 12 июня 2019

Решение, которое предоставляет @Everts, довольно хорошее, но, поскольку вы используете Unity, я бы порекомендовал несколько настроек, чтобы лучше работать с редактором. Вместо общего event я рекомендую использовать UnityEvent из пространства имен UnityEngine.Events. Я бы также посоветовал против статики из-за того, как единство идет о сериализации их между сценами. Есть несколько странных крайних случаев, в которые вы можете попасть, если вы не знакомы с тем, как Unity управляет их сериализацией. Если вам просто нужно отправить сообщение другому объекту в той же сцене, я бы порекомендовал игровой менеджер. Вы можете безопасно выполнить GameObject.Find() in onvalidate() и связать свои переменные, чтобы избежать снижения производительности во время выполнения поиска. Если эти данные нужно перенести в другую сцену для этого сообщения, вместо этого используйте ScriptableObject. Это будет выглядеть примерно так:

Поместите этот компонент в "Game Manager" сцены 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 в папке вашего проекта где-нибудь и игнорируйте этот дополнительный параметр if, чтобы ограничить совпадение сцен , Затем просто убедитесь, что вы используете функцию, чтобы найти ее, которая включает активы, если вы используете межсценовый подход ScriptableObject.

РЕДАКТИРОВАТЬ: Забытые вложенные префабы edgecase в версиях Unity 2018+. Вам нужно добавить это в аккаунт: && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene() Я обновил фрагмент кода выше. Извините за это.

1 голос
/ 12 июня 2019

Так как Updtade () вызывается один раз за кадр, переключатель устанавливается на 1 раз за кадр в течение 8-й секунды (и в течение 1 секунды много кадров).

Ответ может быть примерно таким, чтобы он не печатался снова:

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

Где AlreadyDisplayed - логическое значение, установленное в false при объявлении.
Это должно делать то, что вы хотите достичь. :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...