使用协程检测按键是否被按下或未被按下?

3

我对C#还比较新,正在尝试编写敌人AI脚本。我的目标是:如果按下"S"键,则敌人会离开(使用协程淡出然后淡入)。如果在一定时间内没有按键,则游戏将重置。但是,敌人Debug.Log重置在开始时出现了(这很合理,因为它在我的start函数中)。但是,如果我将其放在update下面,它会每帧更新。我应该使用FixedUpdate吗?我需要一个清晰的示例来说明应该如何进行。

public GameObject gameOverPanel;
public float TimeLimit = 10f;
private bool pressS;

private void Update() {
    if (Input.GetKeyDown("S")) {
        pressS = true;
        //Kills Owl
    }
    //Test
    if (!Input.GetKeyDown("S")) { //If key is not pressed within certian time
        pressS = false;
        //Reset screen Appears
        StopCoroutine(Reset());
        StartCoroutine(Reset());
    }
}

private IEnumerator Reset() {
    Debug.Log("Reset Taking place")
    yield return new WaitForSeconds(TimeLimit);

    //GameOverScreen Appears
    gameOverPanel.SetActive(true);
}

你在Update中没有调用Reset()。那么它应该如何再次淡入呢?在If的else分支中执行淡出操作是自然而然的情况,可以在其中添加淡入操作。 - Christopher
如果在一定时间内没有按键,则游戏将重置。 重置是什么意思?使用coroutine淡出然后淡入,这应该只做一次还是重复进行?它何时停止? - Programmer
2个回答

2
您所请求的是使用协程的基于状态的引擎。对于这种情况,有四个状态:
  1. 敌人淡入。
  2. 敌人等待玩家输入。
  3. 敌人淡出。
  4. 敌人获胜。游戏重新开始。
下面的代码是您发布的代码的修订版。我重构了Start()Update() Monobehaviours,使它们仅专注于启动状态引擎和监视来自玩家的输入。 Start()仅需要为将来在状态引擎中操作的敌人做准备,因此我使用该位置执行EnemyFadeIn()协程。
输入标志直到Update()才会被重置,因此建议所有输入调用都发生在Update循环中。这也是为什么从Start() Monobehaviour中删除了输入处理的原因。您可以在Unity Input文章中找到更多信息。但是,您发布的代码中显示的所有剩余函数都可以平稳地运行在协程中。
using System.Collections;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour {

    public float waitTime = 10.0f;
    public float fadeTime = 3.0f;
    public float betweenFadesTime = 2.0f;

    // Flag to determine whether or not the player may respond.
    public bool canRespond = false;

    // Flag to determine if the player has responded within the wait time.
    public bool hasResponded = false;

    public GameObject enemy;

    void Start()
    { StartCoroutine(EnemyFadeIn(fadeTime, betweenFadesTime, waitTime)); }

    void Update() {
        if ((Input.GetKeyDown(KeyCode.S)) && (canRespond))
        { hasResponded = true; }
    }

    IEnumerator EnemyFadeIn(float timeToFade, float timeBetweenFades, float timeToWait) {
        Debug.Log("An Enemy Is Fading In");
        // Simulating Fade In Time.
        yield return new WaitForSeconds(timeToFade);
        // Fade in
        // iTween.FadeTo(enemy, 1, 1);
        // Invoke("SetMaterialOpaque", 1f);
        Debug.Log("An Enemy Has Appeared");

        yield return ListenForInput(timeToFade, timeBetweenFades, timeToWait);
    }

    IEnumerator EnemyFadeOut(float timeToFade, float timeBetweenFades, float timeToWait) {
        Debug.Log("An Enemy Is Fading Away");
        // Simulating Fade Out Time.
        yield return new WaitForSeconds(timeToFade);
        // Fade out
        // SetMaterialTransparent();
        // iTween.FadeTo(enemy, 0, 1);
        Debug.Log("An Enemy Has Departed");
        // Simulating Time Between Fades.
        yield return new WaitForSeconds(timeBetweenFades);

        yield return EnemyFadeIn(timeToFade, timeBetweenFades, timeToWait);
    }

    // Responsible for reacting to the 'S' key input.
    IEnumerator ListenForInput(float timeToFade, float timeBetweenFades, float timeToWait) {
        canRespond = true;
        Debug.Log("Press the 'S' Key to Destroy the Enemy!");
        float startTime = Time.time;

        // Check every 0.25 seconds to see if the S key was pressed.
        while (Time.time < (startTime + timeToWait)) {
            if (hasResponded) {
                Debug.Log("The 'S' Key was Pressed!");
                hasResponded = false;
                canRespond = false;
                yield return EnemyFadeOut(timeToFade, timeBetweenFades, timeToWait);
            }
            yield return new WaitForSeconds(0.25f);
        }

        Debug.Log("The 'S' Key was not Pressed!");
        canRespond = false;
        yield return ResetGame();
    }

    IEnumerator ResetGame() {
        Debug.Log("Game is Performing a Reset");
        //Simulating Game Reset Time.
        yield return new WaitForSeconds(10);
        Debug.Log("Game has Restarted. End of Coroutine!");
    }
}

我创建了EnemyFadeOut()ListenForInput()方法来配合EnemyFadeIn()ResetGame()方法。这些是四个状态。行为流程如下:
首先,Start() Monobehaviour启动EnemyFadeIn()协程。
接下来,EnemyFadeIn()协程会在完全将敌人融入游戏世界后将控制权转移到ListenForInput()
接下来,如果在给定的等待时间内按下“S”键,则ListenForInput()协程将将控制权转移到EnemyFadeOut(),否则将转移到ResetGame()
如果控制权被传递给EnemyFadeOut(),则敌人将从世界中消失,然后控制权将传递给EnemyFadeIn(),循环将继续,直到玩家未能按下“S”键。
如果控制权被传递给ResetGame(),则表示玩家未能及时按下“S”键,游戏将执行所有必要的功能以重新开始。
控制台Debug.Log()yield return new WaitForSeconds()用于模拟目的。由于iTween不是本地库,因此一些淡入/淡出代码必须被注释掉。

很高兴能够帮助。不过需要提供一些建设性的批评。评论并非用于赞美,因为它们并没有为当前问题添加新的信息。相反,如果您发现某个回答有用,请简单地点赞并将其标记为已解决您的问题。如果可能的话,请帮助其他人。请参阅https://stackoverflow.com/help/privileges/comment了解详细信息。 - user9474008
快速问题:我注意到EnemyFadeIn和EnemyFadeOut函数使用相同的时间。有没有办法延迟淡入,这样它不会在我按下S键后立即出现? - user9540087
请记住 yield return new WaitForSeconds(timeToFade); 只是实际淡出过渡的占位符。应该使用你的 iTween 代码替换 WaitForSeconds()。一旦淡出完成,你可以实现一个 WaitForSeconds(float timeBetweenFades),其中 timeBetweenFades 是淡出和淡入之间的时间量。我已经更新了我的代码来考虑到这一点。 - user9474008

0

目前您正在Start()函数中启动Couroutine EnemyFadeIn(),这意味着它会在脚本所附加的对象被实例化时立即调用。除非您在对象被实例化的那一刻成功按下了S键(这几乎是不可能的),否则也会调用ResetEnemy()方法。因此,EnemyFadeIn()方法只会被调用一次。如果您想要在一定时间内没有按键按下时调用ResetEnemy()方法,您需要每次按下按键时设置一个时间戳,如下:

float lastTimePressedButton;

void Start() {
    lastTimePressedButton = Time.time;
}

void Update() {
    if(Input.GetKeyCode(KeyCode.S)) {
        lastTimePressedButton = Time.time;
    }
    if(Time.time >= lastTimePressedButton + 5f) {
         ResetEnemyOrWhatever();
         lastTimePressedButton = Time.time;
    }
}

FixedUpdate 只应在您想要处理刚体时使用,它与 Update 不同。

此外,GetKeyDown、GetKeyUp 和 GetKeyUp 被认为是不良实践,您应该改用 GetButtonDown、GetButtonUp 和 GetButton。

顺便说一下,在 Stackoverflow 上提问时,您应该写出您调用的函数实际上是做什么的。Itween 不是 .Net Framework 或 Unity 的一部分,我怀疑有多少人知道这个库(我想它是一个库)的作用。


谢谢你的建议,这是我第一次来这里。当我尝试了你的建议之后,Debug.Log("Reset Screen");仍然在update函数中不断重复输出。 - user9540087
我目前无法测试我的代码,再次查看后发现我错过了一些东西。你能再试一次吗?当没有按键按下时,它应该每5秒调用ResetEnemyOrWhatever()方法。如果你只想让它发生一次,那么你需要在它发生一次后立即将一个bool值设置为true,并比较这个bool值是否不为true。 - QuesterDesura
所以我写了一个布尔语句,但我仍然不确定如何以清晰的格式编写它。请帮我重新以更清晰的方式书写它。 - user9540087
当代码按预期工作并且您想要更清晰的代码时,您应该阅读有关SOLID的内容,并尝试创建有意义的方法以避免重复代码。此外,在使用Unity时,您应该遵循ECS的思想,避免在同一脚本中具有多个不同的功能。另外,您不应该像我一样编写"5f"这样的值。我只是为了让它更容易理解而这样做。相反,您应该声明一个变量,例如timeTillEnemyReset。 - QuesterDesura
我本意是链接到SOLID - QuesterDesura
而协程呢? - Alan Mattano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接