Unity中同一KeyCode实现点击单次移动与长按延迟连续移动方案
I get exactly what you're dealing with—when you tap D/A once, the block moves 2-3 times instead of just once, and the long hold doesn’t have that initial delay before repeating. The core issue is that we need to explicitly separate single tap behavior from delayed continuous hold behavior instead of relying solely on GetKeyDown and GetKey, which can overlap unexpectedly.
Here's how to refactor your Tetromino script to fix this:
Step 1: Add Input State Variables
First, add these variables at the top of your class to track the input state:
// Horizontal movement input tracking private float horizontalHoldTimer = 0f; private bool hasMovedOnce = false; private bool isHoldingHorizontal = false; public float horizontalHoldDelay = 0.5f; // Delay before continuous movement starts public float horizontalRepeatRate = 0.1f; // Speed of continuous movement after delay private KeyCode currentHorizontalKey; // Tracks which key (A/D) is being held
Step 2: Refactor the CheckUserInput Method
Replace your existing CheckUserInput method with this version that handles both single taps and delayed holds properly:
void CheckUserInput() { // Handle Horizontal Movement (Left/Right) HandleHorizontalInput(KeyCode.D, Vector3.right); HandleHorizontalInput(KeyCode.A, Vector3.left); // Handle Rotation (W) if (Input.GetKeyDown(KeyCode.W)) { if (allowRotation) { float rotationAmount = limitRotation ? (transform.rotation.eulerAngles.z >= 90 ? -90f : 90f) : 90f; transform.Rotate(0, 0, rotationAmount); if (!CheckIsValidPosition()) { // Undo rotation if invalid transform.Rotate(0, 0, -rotationAmount); } } } // Handle Soft Drop (S) if (Input.GetKeyDown(KeyCode.S)) { MoveTetromino(Vector3.down); } } // Helper method to handle left/right input logic void HandleHorizontalInput(KeyCode key, Vector3 direction) { // Key pressed down for the first time if (Input.GetKeyDown(key)) { // Move once immediately MoveTetromino(direction); // Initialize hold state hasMovedOnce = true; isHoldingHorizontal = true; currentHorizontalKey = key; horizontalHoldTimer = Time.time; } // Key is being held else if (Input.GetKey(key) && isHoldingHorizontal && currentHorizontalKey == key) { // Wait for the initial delay before repeating if (Time.time - horizontalHoldTimer >= horizontalHoldDelay) { // After delay, repeat movement at the set rate if (Time.time - horizontalHoldTimer >= horizontalHoldDelay + horizontalRepeatRate) { MoveTetromino(direction); horizontalHoldTimer = Time.time - horizontalHoldDelay; // Reset timer to maintain repeat rate } } } // Key released else if (Input.GetKeyUp(key) && currentHorizontalKey == key) { // Reset hold state isHoldingHorizontal = false; hasMovedOnce = false; currentHorizontalKey = KeyCode.None; } } // Helper method to handle tetromino movement and validation bool MoveTetromino(Vector3 direction) { transform.position += direction; if (CheckIsValidPosition()) { return true; } else { // Undo movement if invalid transform.position -= direction; // If moving right/left and we hit a boundary, reset hold state if (direction == Vector3.right || direction == Vector3.left) { isHoldingHorizontal = false; hasMovedOnce = false; currentHorizontalKey = KeyCode.None; } return false; } }
What This Does:
- Single Tap: When you press D/A once, it triggers an immediate single movement, then sets up the hold state but won’t repeat until the delay passes. If you release the key before the delay, it resets everything so no extra movement happens.
- Long Hold: Press and hold D/A—after 0.5 seconds (adjustable via
horizontalHoldDelay), the block starts moving continuously at thehorizontalRepeatRatespeed until you release the key or hit a boundary. - Boundary Handling: If the block hits a wall while holding, it stops moving immediately and resets the hold state so you don’t get stuck trying to move further.
Optional Tweaks:
- Adjust
horizontalHoldDelayto make the initial wait shorter/longer (0.5s is standard for most Tetris games). - Adjust
horizontalRepeatRateto make the continuous movement faster/slower. - The
MoveTetrominohelper cleans up duplicate code for movement validation, making the script easier to maintain.
Notes on Your Original Code:
I also cleaned up the rotation logic to avoid duplicate code, but kept your original allowRotation and limitRotation behavior intact. The soft drop (S key) works exactly as before.
内容的提问来源于stack exchange,提问作者Mathieu

