如何使用协程来检测玩家是否仍然停留足够长的时间
How to use coroutines to detect if the player is still for long enough
我不太擅长协程,我想在我的游戏中做点什么。我的目标是让它在玩家静止不动一定秒数(在本例中为 3 秒)时调用一个函数。我正在尝试使用协程,但无法使它们工作。到目前为止,这是我的代码:
...
public float waitTime;
void Update()
{
if (Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
StartCoroutine(coroutine());
}
else
{
StopCoroutine(coroutine());
}
}
IEnumerator coroutine()
{
yield return new WaitForSeconds(waitTime);
method();
}
void method()
{
Debug.Log("It all worked");
}
我确实设法在我的日志中显示“一切正常”,但不是我想要的那样。即使我在 waitTime 过去之前放开了 E,它也会打印它。速度检查工作正常。
现在,我可能应该说出我想让脚本做什么。我想让脚本检测玩家是否静止并且他们按住 E 键 waitTime 秒。这将如何应用于实际游戏是这样的:我正在制作一个玩家可以提高检查点的系统。从那时起,这个检查点就是生成点,直到创建一个新的检查点。我可以自己做 checkpoint/spawnpoint 事情,只需要协程方面的帮助。
我可能对使用协程的想法有误。我选择在这种情况下使用它们,因为我听说当我的代码随时间运行时我应该使用它们。我也认为我需要更多地练习使用它们以更好地理解它们。
这是我的问题:
- 我在这里使用协程对吗?
- 如果是这样,我将如何使用协程?
- 如果没有,我应该什么时候使用它们?
(请在你的答案中使用协程,除非绝对不可能没有协程。我更喜欢这个,因为我可以在没有协程的情况下做到这一点,但是,作为上面说了,我需要练习。)
Coroutine
可以在这里工作,但由于您只是想在一定时间后调用函数,Involke
可能会更好。每当我只需要在 X 秒后调用一个函数时,我倾向于使用 Involke
,但是当我需要创建一个函数时,我会使用 Coroutine
,该函数 运行s 基于一系列先决条件特定方式或特定时间后。
我目前在您的代码中看到的一个问题是您不断调用 StartCoroutine
而没有检查 Coroutine
是否处于活动状态。每当我需要在实际 Coroutine
的生命周期中多次出现的条件下使用 Coroutine
时,我将存储一个引用并检查该引用是否为空。
...
public float waitTime;
private Coroutine YourCoroutineReference = null;
void Update()
{
if (Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
if(YourCoroutineReference == null)
YourCoroutineReference = StartCoroutine(coroutine());
}
else
{
if(YourCoroutineReference != null)
StopCoroutine(YourCoroutineReference);
}
}
IEnumerator coroutine()
{
yield return new WaitForSeconds(waitTime);
method();
YourCoroutineReference = null;
}
void method()
{
Debug.Log("It all worked");
}
通过这个额外的条件检查,打印输出应该按预期工作。但是,我会在这里使用调用,因为您的用例在固定时间后触发单一方法。
这里是使用 Invoke
的例子
...
public float waitTime;
void Update()
{
if (!IsInvoking("method") && Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
Invoke("method", waitTime);
}
else
{
if(IsInvoking("method"))
{
CancelInvoke("method");
}
}
}
private void method()
{
Debug.Log("It all worked");
}
两个片段都未经测试,但大体方向已经确定。如果您 运行 遇到问题请告诉我,我可以更新代码片段。同样,如果您发现自己只是使用 Coroutine
在特定时间后调用函数,请使用 Invoke
。当你在连续布置多个函数调用或 Lerps 时做任何更复杂的事情时,Coroutine
是一个非常有用的工具。
我还要补充一点 Coroutines
非常灵活,您可以出于任何原因使用 yields
暂停它们然后继续它们,而 Invoke
通常在 X 之后调用一次秒,或者如果使用 InvokeRepeating
,则在 X 秒后每 Y 秒调用一次。使用 Coroutines
的另一个有用部分是您可以将参数传递给它们,而使用 Invoke
则不能。我想一个过于简单的思考方式是 Invoke
是一个非常简单的一般情况,用于一次性使用 Coroutines
或非常固定的时间重复方法。
你可以修改协程,而不是等待,不断检查玩家是否静止,并在 3 秒后调用,否则中断。
IEnumerator coroutine()
{
float limit = 3;
float elapsed = 0;
while(elapsed < limit)
{
if(!IsPlayerStill())
{
yield break;
}
elapsed += Time.deltaTime;
yield return null;
}
method();
}
如果你有一个 Update
检查,实际上我不会在这里使用协程,而是像
这样的简单计数器
float timer;
bool alreadyFired;
void Update()
{
if (Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
timer -= Time.deltaTime;
if(!alreadyFired && timer <= 0)
{
alreadyFired = true;
method();
}
}
else
{
alreadyFired = false;
timer = waitTime;
}
}
在这个用例中,我认为这比协程更容易维护。
如果你真的想使用协程,我会这样做
private Coroutine _routine;
void Update()
{
if (Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
if(_routine == null) _routine = StartCoroutine(DoAfterSeconds(waitTime, method));
}
else
{
if(_routine != null) StopCoroutine(_routine);
_routine = null;
}
}
private IEnumerator DoAfterSeconds (float duration, Action callback)
{
yield return new WaitForSeconds (duration);
_routine = null;
callback?.Invoke();
}
我不太擅长协程,我想在我的游戏中做点什么。我的目标是让它在玩家静止不动一定秒数(在本例中为 3 秒)时调用一个函数。我正在尝试使用协程,但无法使它们工作。到目前为止,这是我的代码:
...
public float waitTime;
void Update()
{
if (Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
StartCoroutine(coroutine());
}
else
{
StopCoroutine(coroutine());
}
}
IEnumerator coroutine()
{
yield return new WaitForSeconds(waitTime);
method();
}
void method()
{
Debug.Log("It all worked");
}
我确实设法在我的日志中显示“一切正常”,但不是我想要的那样。即使我在 waitTime 过去之前放开了 E,它也会打印它。速度检查工作正常。
现在,我可能应该说出我想让脚本做什么。我想让脚本检测玩家是否静止并且他们按住 E 键 waitTime 秒。这将如何应用于实际游戏是这样的:我正在制作一个玩家可以提高检查点的系统。从那时起,这个检查点就是生成点,直到创建一个新的检查点。我可以自己做 checkpoint/spawnpoint 事情,只需要协程方面的帮助。
我可能对使用协程的想法有误。我选择在这种情况下使用它们,因为我听说当我的代码随时间运行时我应该使用它们。我也认为我需要更多地练习使用它们以更好地理解它们。
这是我的问题:
- 我在这里使用协程对吗?
- 如果是这样,我将如何使用协程?
- 如果没有,我应该什么时候使用它们?
(请在你的答案中使用协程,除非绝对不可能没有协程。我更喜欢这个,因为我可以在没有协程的情况下做到这一点,但是,作为上面说了,我需要练习。)
Coroutine
可以在这里工作,但由于您只是想在一定时间后调用函数,Involke
可能会更好。每当我只需要在 X 秒后调用一个函数时,我倾向于使用 Involke
,但是当我需要创建一个函数时,我会使用 Coroutine
,该函数 运行s 基于一系列先决条件特定方式或特定时间后。
我目前在您的代码中看到的一个问题是您不断调用 StartCoroutine
而没有检查 Coroutine
是否处于活动状态。每当我需要在实际 Coroutine
的生命周期中多次出现的条件下使用 Coroutine
时,我将存储一个引用并检查该引用是否为空。
...
public float waitTime;
private Coroutine YourCoroutineReference = null;
void Update()
{
if (Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
if(YourCoroutineReference == null)
YourCoroutineReference = StartCoroutine(coroutine());
}
else
{
if(YourCoroutineReference != null)
StopCoroutine(YourCoroutineReference);
}
}
IEnumerator coroutine()
{
yield return new WaitForSeconds(waitTime);
method();
YourCoroutineReference = null;
}
void method()
{
Debug.Log("It all worked");
}
通过这个额外的条件检查,打印输出应该按预期工作。但是,我会在这里使用调用,因为您的用例在固定时间后触发单一方法。
这里是使用 Invoke
...
public float waitTime;
void Update()
{
if (!IsInvoking("method") && Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
Invoke("method", waitTime);
}
else
{
if(IsInvoking("method"))
{
CancelInvoke("method");
}
}
}
private void method()
{
Debug.Log("It all worked");
}
两个片段都未经测试,但大体方向已经确定。如果您 运行 遇到问题请告诉我,我可以更新代码片段。同样,如果您发现自己只是使用 Coroutine
在特定时间后调用函数,请使用 Invoke
。当你在连续布置多个函数调用或 Lerps 时做任何更复杂的事情时,Coroutine
是一个非常有用的工具。
我还要补充一点 Coroutines
非常灵活,您可以出于任何原因使用 yields
暂停它们然后继续它们,而 Invoke
通常在 X 之后调用一次秒,或者如果使用 InvokeRepeating
,则在 X 秒后每 Y 秒调用一次。使用 Coroutines
的另一个有用部分是您可以将参数传递给它们,而使用 Invoke
则不能。我想一个过于简单的思考方式是 Invoke
是一个非常简单的一般情况,用于一次性使用 Coroutines
或非常固定的时间重复方法。
你可以修改协程,而不是等待,不断检查玩家是否静止,并在 3 秒后调用,否则中断。
IEnumerator coroutine()
{
float limit = 3;
float elapsed = 0;
while(elapsed < limit)
{
if(!IsPlayerStill())
{
yield break;
}
elapsed += Time.deltaTime;
yield return null;
}
method();
}
如果你有一个 Update
检查,实际上我不会在这里使用协程,而是像
float timer;
bool alreadyFired;
void Update()
{
if (Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
timer -= Time.deltaTime;
if(!alreadyFired && timer <= 0)
{
alreadyFired = true;
method();
}
}
else
{
alreadyFired = false;
timer = waitTime;
}
}
在这个用例中,我认为这比协程更容易维护。
如果你真的想使用协程,我会这样做
private Coroutine _routine;
void Update()
{
if (Input.GetKey(KeyCode.E) && rb.velocity.x <= stillVelocity.x && rb.velocity.y <= stillVelocity.y)
{
if(_routine == null) _routine = StartCoroutine(DoAfterSeconds(waitTime, method));
}
else
{
if(_routine != null) StopCoroutine(_routine);
_routine = null;
}
}
private IEnumerator DoAfterSeconds (float duration, Action callback)
{
yield return new WaitForSeconds (duration);
_routine = null;
callback?.Invoke();
}