【问题标题】:Unity - GetKeyDown and GetKey using the same KeyCode to get a delay on input if held downUnity - GetKeyDown 和 GetKey 使用相同的 KeyCode 来获得输入延迟(如果按住)
【发布时间】:2018-05-28 16:47:49
【问题描述】:

我试图让用户输入使用相同的输入键执行两种不同的行为。

像这样:

 if (Input.GetKeyDown(KeyCode.D) || Input.GetKey(KeyCode.D)) 

制作俄罗斯方块游戏:目标是,点击“D”一次,我希望 tetromino 每次点击移动一个世界单位。并且当按住同一个键“D”时,我希望块连续向右移动,直到它到达游戏板的边缘,而不必点击。

这种方法适用于上面的代码,但是我遇到的问题是点击一次会移动 2 或 3 个世界单位而不是一次,因为在统一意识到我按住键之前没有延迟。

我希望 unity 在激活“Input.GetKey(KeyCode.D)”之前等待 0.5 秒,以便我可以保持行为“Input.GetKeyDown(KeyCode.D)”

底线,

  • 我希望能够点击“D”以每次点击移动一个世界单位
  • 如果我按住“D”,我希望方块连续向右移动直到它到达游戏板的边缘,但只有在按住它 0.5 秒后

我该怎么做?

Tetromino.cs 的完整代码:

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

public class Tetromino : MonoBehaviour {
    //#####################################################################################################
    //#####################################################################################################
    float fallTimer = 0f;                 // timer counting the seconds to check if mino needs to fall
    public float fallSpeed = 1f;          // variable to determine how fast the mino needs to fall
    public bool allowRotation = true;
    public bool limitRotation = false;
    //#####################################################################################################
    //#####################################################################################################

    // Use this for initialization
    void Start () {

    }
    //#####################################################################################################
    //#####################################################################################################
    // Update is called once per frame
    void Update ()
    {
        CheckUserInput();  // --------------------------- // Checks the user input every frames
        FallBehavior();   // checks if the block needs to fall and increments the timer 
    }
    //#####################################################################################################
    //#####################################################################################################
    void CheckUserInput()  
    {
        if (Input.GetKeyDown(KeyCode.D))                  // moves the mino to the right
        {
            transform.position += new Vector3(1,0,0);
            if (CheckIsValidPosition()) // if minos is not in a valid position, the transform pushes the minos
            {                           // back to the left, to keep it inside the grid

            }
            else
            {
                transform.position += new Vector3(-1, 0, 0); // this counters the first attempt to move
            }
        }
        else if (Input.GetKeyDown(KeyCode.A))             // moves the mino to the left
        {
            transform.position += new Vector3(-1, 0, 0);
            if (CheckIsValidPosition())
            {

            }
            else
            {
                transform.position += new Vector3(1, 0, 0);
            }
        }
        else if (Input.GetKeyDown(KeyCode.W))             // rotates the mino
        {
            if (allowRotation)
            {
                if (limitRotation)                                //limited rotation ON, to prevent rotating outside the grid
                {                                                 // after the tetromino landed at the bottom
                    if (transform.rotation.eulerAngles.z >= 90)
                    {
                        transform.Rotate(0, 0, -90);
                    }
                    else
                    {
                        transform.Rotate(0, 0, 90);
                    }
                }
                else
                {
                    transform.Rotate(0, 0, 90);                   // 90 degrees rotation on the mino
                }
                if (CheckIsValidPosition())
                {

                }
                else
                {
                    if (limitRotation)
                    {
                        if (transform.rotation.eulerAngles.z >= 90)
                        {
                            transform.Rotate(0, 0, -90);
                        }
                        else
                        {
                            transform.Rotate(0, 0, 90);
                        }
                    }
                    else
                    {
                        transform.Rotate(0, 0, -90);
                    }


                }

            }


        }
        else if (Input.GetKeyDown(KeyCode.S))
        {
            transform.position += new Vector3(0, -1, 0);  // makes the mino go down when pressing 
            if (CheckIsValidPosition())
            {

            }
            else
            {
                transform.position += new Vector3(0, 1, 0);
            }
        }
    }
    //#####################################################################################################
    //#####################################################################################################
    /// <summary>
    /// Makes the block fall by 1 unit and checks how fast it needs to fall
    /// </summary>
    void FallBehavior()
    {
        if (Time.time - fallTimer >= fallSpeed)  // on the first frame, Time.time = 0 & fallTimer = 0
                                                 // so 0 - 0 = 0, is it >= then fallSpeed = 1? no
                                                 // so the if statement does not exectute, block dont fall
                                                 // after 1 sec, Time.time = 1 & fallTimer = 0
                                                 // so 1 - 0 = 1, is it >= then fallSpeed = 1? yes
                                                 // so block falls after 1 sec, because we increment it
                                                 // in the if statment also
        {
            transform.position += new Vector3(0, -1, 0); // moves the mino down 

            fallTimer = Time.time;   // Time.time check the time since game started and is assigned
        }                            // to fallTimer so that the timer updates every frame 
                                     // when called in the Update method. fallTimer = 0, 1, 2, 3 ... 

        if (CheckIsValidPosition()) // also helps checking if the Y is invalid, which tells the game to spawn 
        {                            // the next tetromino when Y is less <= to the bottom of the grid 

        }
        else
        {
            transform.position += new Vector3(0, 1, 0);
            enabled = false; // disables the current piece, because it is at the bottom. So that the controls are not still
                             // attached to the current piece, after the next one spawned
            FindObjectOfType<Game>().SpawnNextTetromino(); // spawns the next tetromino after the last one reached the bottom
        }
    }

    //#####################################################################################################
    //#####################################################################################################
    /// <summary>
    /// check the position of the individual tiles of the minos (children of the prefab)
    /
    /// </summary>
    /// <returns></returns>
    bool CheckIsValidPosition()
    {
        foreach (Transform mino in transform)
        {
            Vector2 pos = FindObjectOfType<Game>().RoundingTheMinoPosition (mino.position);
            if (FindObjectOfType<Game>().CheckIsInsideGrid(pos) == false)
            {
                return false;
            }

        }
        return true;
    }
}

Game.cs的完整代码

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

public class Game : MonoBehaviour {

    public static int gridWidth = 10;  // fixed grid size varibles
    public static int gridHeight = 20; // for the blocks to fall in

    // the grid need to be in a 2d array and we want to store all the x and y values for each world unit of the grid
    // so that we can know which point on the grid are beind occupied by tetrominos that fell in.
    // 
    // the array is gonna store the transforms so we use "gridWidth" and "gridHeight" to define the size of the array.
    public static Transform[,] grid = new Transform[gridWidth, gridHeight];

    // Use this for initialization
    void Start () {
        SpawnNextTetromino();  // spawns the first tetromino in the game

    }

    // Update is called once per frame
    void Update () {

    }

    public void SpawnNextTetromino() // the Resources folder is included when the game compiles, we placed our prefabs  
    {                               // in "Assets\Resources\Prefabs" to allow instantiation in the code.

        // we cast a gameobject -> "(GameObject)" to let "Instantiate" know what we want to instantiate.
        GameObject nextTetromino = (GameObject)Instantiate(Resources.Load(GetRandomTetromino(), typeof(GameObject)), new Vector2(5.0f, 20.0f), Quaternion.identity);
    }


    //gonna pass in the mino position in this method to see 
    // if it is still in the grid
    public bool CheckIsInsideGrid(Vector2 pos) 
    {                                          
        return ((int)pos.x >= 0 && (int)pos.x < gridWidth && (int)pos.y >= 0);
    }

    public Vector2 RoundingTheMinoPosition(Vector2 pos)
    {
        return new Vector2(Mathf.Round(pos.x), Mathf.Round(pos.y));
    }

    /// <summary>
    /// Genreates a random int and assings a teromino prefab to the outcome 
    /// </summary>
    /// <returns></returns>
    string GetRandomTetromino()
    {
        int randomTetromino = Random.Range(1, 8); //
        string randomTetrominoName = null;
        switch (randomTetromino)
        {
            case 1:
                randomTetrominoName = "Prefabs/Tetromino_T";
                break;

            case 2:
                randomTetrominoName = "Prefabs/Tetromino_Long";
                break;

            case 3:
                randomTetrominoName = "Prefabs/Tetromino_Square";
                break;

            case 4:
                randomTetrominoName = "Prefabs/Tetromino_J";
                break;

            case 5:
                randomTetrominoName = "Prefabs/Tetromino_L";
                break;

            case 6:
                randomTetrominoName = "Prefabs/Tetromino_S";
                break;

            case 7:
                randomTetrominoName = "Prefabs/Tetromino_Z";
                break;
        }
        return randomTetrominoName;
    }

}

【问题讨论】:

  • 发布你在那个 if 语句中调用的移动代码!
  • 我编辑了我的帖子!

标签: unity3d


【解决方案1】:

看来我误解了原来的问题。您只想按“D”键移动,但在按住“D”键时移动直到到达边缘。按住键时需要一个计时器,这可以通过@987654321 完成@。使用Input.GetKey 检查按键是否被按住,如果计时器达到您认为使其被按住的值的数量,那么您就知道按键被按住了。

另外,使用Input.GetKeyUp(KeyCode.D) 检查密钥何时释放。如果键被释放但计时器没有达到你认为让它按下的值,那么它只是一个按键。值得在协程函数而不是 Update 函数中执行此操作,以简化它并减少执行此操作所需的变量数量。

const float timeToCountAsHeldDown = 0.3f;
float pressTimer = 0;

IEnumerator moveChecker()
{
    while (true)
    {
        //Check when the D key is pressed
        if (Input.GetKeyDown(KeyCode.D))
        {
            //Continue to check if it is still heldown and keep counting the how long
            while (Input.GetKey(KeyCode.D))
            {
                //Start incrementing timer
                pressTimer += Time.deltaTime;

                //Check if this counts as being "Held Down"
                if (pressTimer > timeToCountAsHeldDown)
                {
                    //It a "key held down", call the OnKeyHeldDown function and wait for it to return
                    yield return OnKeyHeldDown();
                    //No need to continue checking for Input.GetKey(KeyCode.D). Break out of this whule loop
                    break;
                }

                //Wait for a frame
                yield return null;
            }
        }


        //Check if "D" key is released 
        if (Input.GetKeyUp(KeyCode.D))
        {
            //Check if we have not not reached the timer then it is only a key press
            if (pressTimer < timeToCountAsHeldDown)
            {
                //It just a key press, call the OnKeyPressedOnly function and wait for it to return
                yield return OnKeyPressedOnly();
            }

            //Reset timer to 0 for the next key press
            pressTimer = 0f;
        }

        //Wait for a frame
        yield return null;
    }
}

IEnumerator OnKeyPressedOnly()
{
    Debug.Log("D key was only Pressed");

    //Move 1 unit only
    transform.position += new Vector3(1, 0, 0);
    yield return null;
}


IEnumerator OnKeyHeldDown()
{
    Debug.LogWarning("D key is Held Down");

    //Don't move for 0.5 seconds 
    yield return new WaitForSeconds(0.5f);

    //Move 1 unit every frame until edge detection is reached!
    while (!CheckIsValidPosition())
    {
        transform.position += new Vector3(1, 0, 0);

        //Wait for a frame
        yield return null;
    }
}

【讨论】:

  • 当然,您可能仍然希望对“按住”动作设置某种时间限制,因为“1 帧”通常是 1/60 秒,大多数人的反应时间更接近1/4.
  • 谢谢,我会试试这个
  • 按照您的建议进行操作并在 IF 和 ELSE IF 中将 GetKey 和 GetKeyDown 分开会导致与我最初在原始帖子中出现的行为相同。所以你提到的时间限制器是我唯一需要的东西,或者看起来是这样。但这是我不知道该怎么做。
  • @MathieuGagne 我可能误解了这个问题。您只想在按下 D 时移动Vector3(1, 0, 0);。如果按住不放,继续移动吗?
  • 我想他想要的是:a) Single Tab => move 1 Unit; b)按住按钮=>不要移动0.5秒,然后无限移动直到到达边缘。还缺少什么:开始按住按钮时,您还想将其移动 1 个单位吗?
猜你喜欢
  • 2018-08-04
  • 1970-01-01
  • 1970-01-01
  • 2018-07-19
  • 1970-01-01
  • 2013-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多