【问题标题】:Question about pausing an if statement through a coroutine关于通过协程暂停 if 语句的问题
【发布时间】:2021-02-01 17:22:35
【问题描述】:

我刚刚开始学习如何在 Unity 中编程。在过去的两三周里,每天都在尝试编写一些代码。我有点卡在一个简单的脚本上。该脚本如下:

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

public class PlayerControllerX : MonoBehaviour
{
    public GameObject dogPrefab;
    public float timeWaited = 0.5f;


    public void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        // On spacebar press, send dog
        if (Input.GetKeyDown(KeyCode.Space))
        { 
            StartCoroutine(MyCoroutine());
        }
        

    }
    private IEnumerator MyCoroutine()
    {
        Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
        yield return new WaitForSeconds(1f);
        Debug.Log("Waited 0.5 seconds!");
        
       
    }
} `

我希望在按空格键之间有 0.5 秒的休息时间(这会产生一条狗)。我认为这不起作用,因为协程只是在按空格键时从头开始播放。但是,我想不出解决此问题的方法。任何建议将不胜感激!

【问题讨论】:

  • 每次按下空格键时,您的代码都会启动一个新的协程。即使从技术上讲,您在 每个协程实例 内等待 1 秒(即使您记录 0.5 秒,但您的代码等待 1 秒,而不是 0.5),它不会阻止新的协程开始。所以,你几乎是正确的 - 协程从顶部重新启动 - 它的新实例 - 不是同一个。您可以改为保留某种TimeSpan 变量来跟踪按键之间的时间,并完全消除协程。
  • 另外,if (Time.time >= timeWaited) 语句在前几帧之后总是true - 它返回自游戏开始后的总秒数docs.unity3d.com/ScriptReference/Time-time.html 考虑改用Time.deltaTime,并将其累积到某个变量中以确定是否该生成下一个项目(在您的情况下为狗)。 docs.unity3d.com/ScriptReference/Time-deltaTime.html
  • @CoolBots 我会看看我能不能完成这项工作。出于兴趣,协程是否有任何可能的方式在这里工作?我也忘了删除那条线哈哈,正在试验。谢谢!
  • 当然,您可以为此目的制作协程。假设您想做一些耗时的任务(例如,狗生成的动画大约需要半秒,这将是生成之间延迟的一个很好的原因) - 您仍然可以启动协程你现在就做,但我会让主代码负责检查是否是时候开始一个新的协程。或者,您可以“永远”运行协程,并检查该协程中的按键,但这不是 Unity 协程的典型用例。
  • @CoolBots Time.deltaTime 是完全不同的东西。他需要实时等待 0.5 秒,而Time.deltaTime 仅返回自上一帧以来的秒数。如果不知何故距离上一帧的时间小于 0.5 秒,则协程将永远不会触发。

标签: c# unity3d scripting coroutine


【解决方案1】:

首先,我不相信Time.time 会这样工作。 Unity documentation 内容如下:

此帧开始的时间(只读)。这是游戏开始后的秒数。

Time.time 是应用程序已运行的时间量(以秒为单位)。它是只读的。

协程中的 if 语句正在检查Time.time,所以它总是在 0.5 以上,并且总是生成狗。

虽然这是一种技术含量相当低的方法,但为了解决这个问题,我会在协程的开始和结束处设置一个变量,这样你就可以检查另一个 if 语句来对照这个变量。这也意味着您可以将此检查放在Input.GetKeyDown() 检查旁边,而不是同时运行多个协程。

这意味着您的脚本主体看起来像这样:

public GameObject dogPrefab;
bool Waiting = false;

void Update() 
{
     if (Input.GetKeyDown(KeyCode.Space) && !Waiting)
     {
         StartCoroutine(MyCoroutine());
     } else
     {
         Debug.Log("Waiting");
     }
}

IEnumerator MyCoroutine()
{
    Waiting = true;

    Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
    yield return new WaitForSeconds(0.5f);
    Debug.Log("Waited 0.5 seconds!");

    Waiting = false;
}

(CoolBots 已经在 cmets 中说了大部分,但我无法发表评论,所以我只是想把这个放在里面。我也有使用大量协程的坏习惯,所以正如他们所说这可能没有必要,但我个人会这样做。)

【讨论】:

    【解决方案2】:

    您不需要协程来执行此操作。您可以简单地使用 timeWaited 变量,每次按下按钮时都会生成狗,并将变量与自游戏开始以来经过的时间和等待时间相加。

    类似这样的:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class PlayerControllerX : MonoBehaviour
    {
        public GameObject dogPrefab;
        public float timeWaited = 0.5f;
    
    
        public void Start()
        {
            
        }
    
        // Update is called once per frame
        void Update()
        {
            // On spacebar press, send dog
            if (Input.GetKeyDown(KeyCode.Space) && Time.time >= timeWaited)
            { 
                SpawnDog();
                timeWaited = Time.time + timeWaited;
            }
            
    
        }
        private void SpawnDog()
        {
           
             Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
            Debug.Log("Waited 0.5 seconds!");
           
        }
    } `
    

    【讨论】:

      【解决方案3】:

      每次按下空格键时,您的代码都会启动一个新的协程。即使从技术上讲,您在每个协程实例中等待 0.5 秒,它也不会阻止新的协程启动。所以,你几乎是正确的——协程从顶部重新启动——它的一个新实例——不是同一个。此外,语句if (Time.time >= timeWaited) 在前几帧之后始终为true - 它返回游戏开始后的总秒数。 (https://docs.unity3d.com/ScriptReference/Time-time.html)

      相反,让我们使用标准方法来解决这个问题 - 累加器值,每帧递增Time.deltaTime。 (https://docs.unity3d.com/ScriptReference/Time-deltaTime.html)

      步骤 0:定义等待时间

      您的代码中已经有了这个 - 它被称为timeWaited。为了清楚起见,我将在这里重命名它。这个值应该是脚本的全局值(在 Unity 中) - 这是您的 MonoBehaviour 派生类中的 类字段

      float minimumTimeBetweenDogSpawns = 0.5f;
      

      第一步:累加器变量

      你的代码中没有这个变量,所以我在这里介绍它。这个值应该是脚本的全局值(在 Unity 中) - 这是您的 MonoBehaviour 派生类中的 类字段

      float elapsedTimeSinceSpacebarLastPressed = 0;  // feel free to make the name shorter, lol!
      

      第二步:每帧累加Time.deltaTime

      现在,Unity 游戏引擎(实际上是所有游戏引擎)会跟踪每帧之间经过的时间。这在游戏开发中是一个非常重要的概念,因为它允许对象的平滑移动,而不管运行一帧需要更长或更短的时间 - 因为我们知道自上一帧以来经过的时间,我们可以使用它来计算对象位置,一个单位应该前进多少等。在我们的例子中,我们只是使用这个值来到达一个期望的点 - 在你的例子中是 0.5 秒,由 minimumTimeBetweenDogSpawns 变量表示。

      那么,我们如何以及在哪里累积经过的时间?很简单 - 只需将其添加到 Update:

      void Update()
      {
         elapsedTimeSinceSpacebarLastPressed += Time.deltaTime;
      }
      

      第 3 步:检查是否该产卵了!

      那么,我们如何处理这个elapsedTimeSinceSpacebarLastPressed?到目前为止,它一直在增长……我们需要检查它是否达到了感兴趣的值——在我们的例子中,minimumTimeBetweenDogSpawns 已经达到了。但前提是按下了空格键。让我们在Update 中添加一个if 语句来做到这一点。我将从第 2 步开始扩展 Update

      void Update()
      {
         elapsedTimeSinceSpacebarLastPressed += Time.deltaTime;
      
         if(Input.GetKeyDown(KeyCode.Space) 
            && elapsedTimeSinceSpacebarLastPressed >= minimumTimeBetweenDogSpawns)
         {
            // Do stuff! Spawn a dog! Run a coroutine! Have a ball!
          
            elapsedTimeSinceSpacebarLastPressed = 0;   // ...but don't forget to reset the accumulator!
         }
      }
      

      你听懂最后一行了吗?非常重要 - 重置累加器!这会重新启动过程,允许我们生成更多狗,但不会在达到minimumTimeBetweenDogSpawns 指定的值之前。

      注意事项

      • 从技术上讲,协程不是必需的,但可能很有用 - 假设您想做一些耗时的任务 - 比如说,狗生成的动画大约需要 .半秒,这将是产生之间延迟的一个很好的理由。

      • 与原帖中的一些 cmets 不同,机器之间在速度方面的差异无关紧要对于这种方法!时间就是时间 - 机器之间可能发生的所有变化是在下一次生成之前运行了多少帧 - 但不是经过的时间!在一个正确编写的游戏中,我们从不关心过去多少帧 - 我们依赖于时间,这就是为什么 每个游戏引擎和框架都提供某些版本的 deltaTime

      【讨论】:

      • 优秀的答案!我唯一要改变的是不要制作自己的累加器,因为Time.time 已经提供了一个累加器。相反,请跟踪AvailableAfterTime。例如,if (ShouldSpawn && Time.time >= AvailableAfterTime ) { AvailableAfterTime = Time.time + Delay; /* Do Work */ }
      • @Immersive 虽然这可行,但Time.time 最终会变得相当大,因为它会跟踪游戏开始后的时间。它也是不可重置的。制作我们自己的累加器(基本上只是一个float - 所以那里没有真正的开销)可以轻松重置,并轻松跟踪多个“计时器” - 我们可以声明几个为不同动作计时的累加器(例如护盾充电时间计时器、敌人生成计时器等) - 它们可以单独重置,并且易于调试,因为每个计时器只会计数到各自的目标值。
      • 大数字是一个公平的论点,但其余的只是绒毛,因为所有计时器都只是时间戳/累加器的变量存储。在一天结束时,您可以设置倒计时、倒计时或时间戳。我使用时间戳,因为它可以节省每帧写入。
      • @Immersive 是的,有很多方法可以解决这个问题。我使用了多个引擎/框架(SFML、MonoGame),所以我更喜欢可移植的方法。 deltaTime 以某种形式存在于所有引擎/框架中,因此我在回答中描述的方法在任何框架中都适用。我最初只是对 OP 的问题发表了评论,但与一位用户进行了讨论,该用户建议我的方法不可行,要求解释它是如何工作的 - 因此我写了这个答案。
      • 便携性也是一个伟大的原因。我经常忘记 Unity 在其他人并不总是拥有这些功能的地方是多么舒适。都好。 =)
      猜你喜欢
      • 2021-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多