Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm developing a Tetris clone and working on the input at the moment.

When I was prototyping, movement was triggered by releasing a directional key. However, in most Tetris games I've played the movement is a bit more complex.

When a directional key is pressed, the shape moves one space in that direction. After a short interval, if the key is still held down, the shape starts moving in the direction continuously until the key is released.

In the case of the down key being pressed, there is no pause between the initial movement and the subsequent continuous movement.

I've come up with a solution, and it works well, but it's totally over-engineered. Hey, at least I can recognize when things are getting silly, right? :)


    public class TetrisMover
    {
        List registeredKeys;
        Dictionary continuousPressedTime;
        Dictionary totalPressedTime;
        Dictionary initialIntervals;
        Dictionary continousIntervals;
        Dictionary keyActions;
        Dictionary initialActionDone;
        KeyboardState currentKeyboardState;

        public TetrisMover()
        {
            *snip*
        }

        public void Update(GameTime gameTime)
        {
            currentKeyboardState = Keyboard.GetState();

            foreach (Keys currentKey in registeredKeys)
            {
                if (currentKeyboardState.IsKeyUp(currentKey))
                {
                    continuousPressedTime[currentKey] = TimeSpan.Zero;
                    totalPressedTime[currentKey] = TimeSpan.Zero;
                    initialActionDone[currentKey] = false;
                }
                else
                {
                    if (initialActionDone[currentKey] == false)
                    {
                        keyActions[currentKey]();
                        initialActionDone[currentKey] = true;
                    }

                    totalPressedTime[currentKey] += gameTime.ElapsedGameTime;

                    if (totalPressedTime[currentKey] >= initialIntervals[currentKey])
                    {
                        continuousPressedTime[currentKey] += gameTime.ElapsedGameTime;
                        if (continuousPressedTime[currentKey] >= continousIntervals[currentKey])
                        {
                            keyActions[currentKey]();
                            continuousPressedTime[currentKey] = TimeSpan.Zero;
                        }
                    }
                }
            }
        }

        public void RegisterKey(Keys key, TimeSpan initialInterval, TimeSpan continuousInterval, Action keyAction)
        {
            if (registeredKeys.Contains(key))
                throw new InvalidOperationException(
                    string.Format("The key %s is already registered.", key));

            registeredKeys.Add(key);
            continuousPressedTime.Add(key, TimeSpan.Zero);
            totalPressedTime.Add(key, TimeSpan.Zero);
            initialIntervals.Add(key, initialInterval);
            continousIntervals.Add(key, continuousInterval);
            keyActions.Add(key, keyAction);
            initialActionDone.Add(key, false);
        }

        public void UnregisterKey(Keys key)
        {
            *snip*
        }
    }

I'm updating it every frame, and this is how I'm registering keys for movement:


            tetrisMover.RegisterKey(
                Keys.Left, keyHoldStartSpecialInterval, keyHoldMovementInterval,
                () => { Move(Direction.Left); });

            tetrisMover.RegisterKey(
                Keys.Right, keyHoldStartSpecialInterval, keyHoldMovementInterval,
                () => { Move(Direction.Right); });

            tetrisMover.RegisterKey(
                Keys.Down, TimeSpan.Zero, keyHoldMovementInterval,
                () => { PerformGravity(); });

Issues that this doesn't address:

  • If both left and right are held down, the shape moves back and forth really quick.
  • If a directional key is held down and the turn finishes and the shape is replaced by a new one, the new one will move quickly in that direction instead of the little pause it is supposed to have.

I could fix the issues, but I think it will make the solution even worse. How would you implement this?

share|improve this question
add comment

1 Answer

up vote 2 down vote accepted

What you need is a simple key repeating algorithm. This is how I usually do it.

FRAMES_FOR_FIRST_PRESS = 4;
FRAMES_FOR_OTHER_PRESS = 1;

key_pending_frames = -1;

now, for every frame:

function OnFrame()
{
    if (IsKeyPressed())
    {
        if (key_pending_frames < 0) // not pressed
        {
            key_pending_frames = FRAMES_FOR_FIRST_PRESS;
        }
        key_pending_frames--;
        if (key_pending_frames == 0)
        {
            DoYourAction();
            key_pending_frames = FRAMES_FOR_OTHER_PRESS;
        }
    }
    else
    {
        key_pending_frames = -1;
    }
}

You'd need a key_pending_frames for each key you want, and you can reset any key's counter just by setting its variable to -1.

Note that for non-fixed frame lengths, you can use clock ticks, or another time interval structure you have. You just have to make sure your non-pressed value is unreachable by simply decrementing the counter. To be on the safe side, you can simply add a bool key_pressed for each key.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.