C# - 俄罗斯方块克隆 - 无法阻止对箭头键组合做出正确响应

时间:2022-06-24 17:23:41

I'm working on programming a Tetris game in Visual C# 2005. This is the most extensive program I have designed yet.

我正在使用Visual C#2005编写一个俄罗斯方块游戏。这是我设计的最广泛的程序。

I create a shape class and a block class to control the location, movement, and display of the different Tetris pieces. I have moveDown(), moveLeft(), and moveRight() functions for each shape (and corresponding canMoveDown(), canMoveLeft(), canMoveRight() boolean functions that verify it's ok to move). This is all working beautifully.

我创建了一个形状类和一个块类来控制不同俄罗斯方块的位置,移动和显示。我有每个形状的moveDown(),moveLeft()和moveRight()函数(和相应的canMoveDown(),canMoveLeft(),canMoveRight()布尔函数,验证它可以移动)。这一切都很美妙。

I want to use the down, right, and left arrow keys to let the user move the block around, in addition to using a timer to have the shape automatically fall one row every so many milliseconds.

我想使用向下,向右和向左箭头键让用户移动块,除了使用计时器使形状每隔很多毫秒自动下降一行。

I am using the KeyDown event handler to check when the user presses the down, left, and right arrow key. This isn't so hard. The problem is that I want to allow for diagonal motion, and I want it work as smoothly possible. I have tried a bunch of different ways of approaching this problem, with varying levels of success. But I can't get it quite right...

我正在使用KeyDown事件处理程序来检查用户何时按下向下,向左和向右箭头键。这不是那么难。问题是我想允许对角线运动,我希望它能够顺利运行。我已经尝试了一系列不同的方法来解决这个问题,并取得了不同程度的成功。但我不能说得对......

My most successful approach was to use three boolean variables to keep track of when the down, left, and right arrow keys are being held down. I would set the booleans to true in the KeyDown event, and to false in the KeyUp event. In the KeyDown event I would also tell the block how to move, using the boolean variables to check which combination was currently being pressed. It worked really well, except for one thing.

我最成功的方法是使用三个布尔变量来跟踪向下,向左和向右箭头键被按下的时间。我会在KeyDown事件中将布尔值设置为true,在KeyUp事件中将布尔值设置为false。在KeyDown事件中,我还会告诉块如何移动,使用布尔变量来检查当前正在按下哪个组合。除了一件事,它的效果非常好。

If I pressed one of the arrow keys and held, then pressed a second arrow key and then released the second key, the block would stop moving altogether, instead of continuing to move in the direction of the first arrow key which hasn't been released yet. I think this is because the second key triggered the KeyDown event, and upon its release the KeyUp event was fired, and the KeyDown event stopped firing completely, even though the first key is fired.

如果我按下其中一个箭头键并按住,然后按下第二个箭头键然后释放第二个键,该块将完全停止移动,而不是继续向第一个箭头键的方向移动,而第一个箭头键尚未释放然而。我认为这是因为第二个键触发了KeyDown事件,并且在释放时,KeyUp事件被触发,并且KeyDown事件完全停止触发,即使第一个键被触发。

I cannot for the life me of find a satisfactory solution to this problem.

我不能为我的生活找到一个满意的解决方案来解决这个问题。

Any help would be greatly appreciated =)

任何帮助将不胜感激=)

3 个解决方案

#1


Most games don't wait for events. They poll the input device when neccessary and act accodringly. In fact, if you ever take a look at XNA, you'll see that theyre's a Keyboard.GetState() method (or Gamepad.GetState()) that you'll call in your update routine, and update your game logic based on the results. When working with Windows.Forms, there's nothing out of the box to do this, however you can P/Invoke the GetKeyBoardState() function to take advantage of this. The good thing about this is, that you can poll multiple keys at once, and you can therefore react to more than one key press at a time. Here's a simple class I found online that helps with this:

大多数游戏都不等​​待事件。他们在必要时轮询输入设备并采取相应的行动。事实上,如果您看过XNA,您会发现它们是您在更新例程中调用的Keyboard.GetState()方法(或Gamepad.GetState()),并根据更新您的游戏逻辑结果。使用Windows.Forms时,没有什么是开箱即用的,但是你可以P / Invoke GetKeyBoardState()函数来利用它。这样做的好处是,您可以一次轮询多个键,因此您可以一次对多个按键做出反应。这是我在网上找到的一个简单的课程,有助于此:

http://sanity-free.org/17/obtaining_key_state_info_in_dotnet_csharp_getkeystate_implementation.html

To demonstrate, I wrote a simple windows app that basically moves a ball around based on keyboard input. It uses the class I linked you to, to poll the keyboard's state. You'll notice that if you hold down two keys at a time, it'll move diagonally.

为了演示,我写了一个简单的Windows应用程序,基本上根据键盘输入移动球。它使用我链接到的类来轮询键盘的状态。您会注意到,如果您一次按住两个键,它将对角移动。

First, Ball.cs:

    public class Ball
    {
        private Brush brush;

        public float X { get; set; }
        public float Y { get; set; }
        public float DX { get; set; }
        public float DY { get; set; }
        public Color Color { get; set; }
        public float Size { get; set; }

        public void Draw(Graphics g)
        {
            if (this.brush == null)
            {
                this.brush = new SolidBrush(this.Color);
            }
            g.FillEllipse(this.brush, X, Y, Size, Size);
        }

        public void MoveRight()
        {
            this.X += DX;
        }

        public void MoveLeft()
        {
            this.X -= this.DX;
        }

        public void MoveUp()
        {
            this.Y -= this.DY;
        }

        public void MoveDown()
        {
            this.Y += this.DY;
        }
    }

Really nothing fancy at all....

真的没什么特别的....

Then here's the Form1 code:

那么这是Form1代码:

    public partial class Form1 : Form
    {
        private Ball ball;
        private Timer timer;
        public Form1()
        {
            InitializeComponent();
            this.ball = new Ball
            {
                X = 10f,
                Y = 10f,
                DX = 2f,
                DY = 2f,
                Color = Color.Red,
                Size = 10f
            };
            this.timer = new Timer();
            timer.Interval = 20;
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            var left = KeyboardInfo.GetKeyState(Keys.Left);
            var right = KeyboardInfo.GetKeyState(Keys.Right);
            var up = KeyboardInfo.GetKeyState(Keys.Up);
            var down = KeyboardInfo.GetKeyState(Keys.Down);

            if (left.IsPressed)
            {
                ball.MoveLeft();
                this.Invalidate();
            }

            if (right.IsPressed)
            {
                ball.MoveRight();
                this.Invalidate();
            }

            if (up.IsPressed)
            {
                ball.MoveUp();
                this.Invalidate();
            }

            if (down.IsPressed)
            {
                ball.MoveDown();
                this.Invalidate();
            }


        }


        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (this.ball != null)
            {
                this.ball.Draw(e.Graphics);
            }
        }
    }

Simple little app. Just creates a ball and a timer. Every 20 milliseconds, it checks the keyboard state, and if a key is pressed it moves it and invalidates so that it can repaint.

简单的小应用。只需创建一个球和一个计时器。每20毫秒,它会检查键盘状态,如果按下某个键,它会移动它并使其无效,以便重新绘制。

#2


If you're relying on key repeat to repeatedly send key down events to make the block move, I don't think this is the way you want to do it. The block should move consistently independent of key repeat. Therefore you should not be moving the block during the key events. You should only be tracking the state of the keys during the keydown and keyup events, and handle movement elsewhere. The actual movement should take place either in some sort of timer event (a timer control fires events at regular intervals even if nothing is going on) or you should have a main loop constantly checking the state of everything and moving objects when appropriate. If you use the second option, you will need to look into "DoEvents" because if you have code that's constantly running without ever finishing the function, the program will not process any other events such as keyup and keydown events. So you would want to call DoEvents within each loop to process the key events (among other things like moving the window). You might also want to call System.Threading.Thread.Sleep if you don't need to be processing things quite so constantly. If you use a timer control, you shouldn't have to worry about any of that.

如果您依靠密钥重复来重复发送按键事件以使块移动,我认为这不是您想要的方式。该块应始终独立于键重复移动。因此,您不应该在关键事件期间移动块。您应该只在keydown和keyup事件期间跟踪键的状态,并处理其他地方的移动。实际的移动应该发生在某种定时器事件中(定时器控件会定期触发事件,即使没有任何事情发生),或者你应该有一个主循环不断地检查所有事物的状态并在适当时移动物体。如果您使用第二个选项,则需要查看“DoEvents”,因为如果您的代码一直在运行而没有完成该功能,程序将不会处理任何其他事件,如keyup和keydown事件。因此,您需要在每个循环中调用DoEvents来处理关键事件(以及移动窗口等)。如果你不需要经常处理事情,你可能还想调用System.Threading.Thread.Sleep。如果您使用计时器控件,则不必担心任何此类控件。

#3


Sorry, no specific help... it's saturday night after all... however:

对不起,没有具体的帮助......毕竟是星期六晚上...但是:

Each key needs a state. When you get a keydown event, you can tell which key went down, and modifiy the keystate you are maintaining. When the key comes back up, once again, from looking at the event, you can tell which it was. Only modify the state of the key that the event was fired against: i.e. don't reset all states with the keyup event, otherwise the state of keys you are holding in memory will be corrupted, as you are seeing.

每个密钥都需要一个状态。当你得到一个keydown事件时,你可以知道哪个键发生故障,并修改你正在维护的keystate。当钥匙重新启动时,再一次,通过查看事件,你可以知道它是什么。仅修改触发事件的键的状态:即不使用keyup事件重置所有状态,否则您在内存中保存的键的状态将被破坏,如您所见。

#1


Most games don't wait for events. They poll the input device when neccessary and act accodringly. In fact, if you ever take a look at XNA, you'll see that theyre's a Keyboard.GetState() method (or Gamepad.GetState()) that you'll call in your update routine, and update your game logic based on the results. When working with Windows.Forms, there's nothing out of the box to do this, however you can P/Invoke the GetKeyBoardState() function to take advantage of this. The good thing about this is, that you can poll multiple keys at once, and you can therefore react to more than one key press at a time. Here's a simple class I found online that helps with this:

大多数游戏都不等​​待事件。他们在必要时轮询输入设备并采取相应的行动。事实上,如果您看过XNA,您会发现它们是您在更新例程中调用的Keyboard.GetState()方法(或Gamepad.GetState()),并根据更新您的游戏逻辑结果。使用Windows.Forms时,没有什么是开箱即用的,但是你可以P / Invoke GetKeyBoardState()函数来利用它。这样做的好处是,您可以一次轮询多个键,因此您可以一次对多个按键做出反应。这是我在网上找到的一个简单的课程,有助于此:

http://sanity-free.org/17/obtaining_key_state_info_in_dotnet_csharp_getkeystate_implementation.html

To demonstrate, I wrote a simple windows app that basically moves a ball around based on keyboard input. It uses the class I linked you to, to poll the keyboard's state. You'll notice that if you hold down two keys at a time, it'll move diagonally.

为了演示,我写了一个简单的Windows应用程序,基本上根据键盘输入移动球。它使用我链接到的类来轮询键盘的状态。您会注意到,如果您一次按住两个键,它将对角移动。

First, Ball.cs:

    public class Ball
    {
        private Brush brush;

        public float X { get; set; }
        public float Y { get; set; }
        public float DX { get; set; }
        public float DY { get; set; }
        public Color Color { get; set; }
        public float Size { get; set; }

        public void Draw(Graphics g)
        {
            if (this.brush == null)
            {
                this.brush = new SolidBrush(this.Color);
            }
            g.FillEllipse(this.brush, X, Y, Size, Size);
        }

        public void MoveRight()
        {
            this.X += DX;
        }

        public void MoveLeft()
        {
            this.X -= this.DX;
        }

        public void MoveUp()
        {
            this.Y -= this.DY;
        }

        public void MoveDown()
        {
            this.Y += this.DY;
        }
    }

Really nothing fancy at all....

真的没什么特别的....

Then here's the Form1 code:

那么这是Form1代码:

    public partial class Form1 : Form
    {
        private Ball ball;
        private Timer timer;
        public Form1()
        {
            InitializeComponent();
            this.ball = new Ball
            {
                X = 10f,
                Y = 10f,
                DX = 2f,
                DY = 2f,
                Color = Color.Red,
                Size = 10f
            };
            this.timer = new Timer();
            timer.Interval = 20;
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            var left = KeyboardInfo.GetKeyState(Keys.Left);
            var right = KeyboardInfo.GetKeyState(Keys.Right);
            var up = KeyboardInfo.GetKeyState(Keys.Up);
            var down = KeyboardInfo.GetKeyState(Keys.Down);

            if (left.IsPressed)
            {
                ball.MoveLeft();
                this.Invalidate();
            }

            if (right.IsPressed)
            {
                ball.MoveRight();
                this.Invalidate();
            }

            if (up.IsPressed)
            {
                ball.MoveUp();
                this.Invalidate();
            }

            if (down.IsPressed)
            {
                ball.MoveDown();
                this.Invalidate();
            }


        }


        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (this.ball != null)
            {
                this.ball.Draw(e.Graphics);
            }
        }
    }

Simple little app. Just creates a ball and a timer. Every 20 milliseconds, it checks the keyboard state, and if a key is pressed it moves it and invalidates so that it can repaint.

简单的小应用。只需创建一个球和一个计时器。每20毫秒,它会检查键盘状态,如果按下某个键,它会移动它并使其无效,以便重新绘制。

#2


If you're relying on key repeat to repeatedly send key down events to make the block move, I don't think this is the way you want to do it. The block should move consistently independent of key repeat. Therefore you should not be moving the block during the key events. You should only be tracking the state of the keys during the keydown and keyup events, and handle movement elsewhere. The actual movement should take place either in some sort of timer event (a timer control fires events at regular intervals even if nothing is going on) or you should have a main loop constantly checking the state of everything and moving objects when appropriate. If you use the second option, you will need to look into "DoEvents" because if you have code that's constantly running without ever finishing the function, the program will not process any other events such as keyup and keydown events. So you would want to call DoEvents within each loop to process the key events (among other things like moving the window). You might also want to call System.Threading.Thread.Sleep if you don't need to be processing things quite so constantly. If you use a timer control, you shouldn't have to worry about any of that.

如果您依靠密钥重复来重复发送按键事件以使块移动,我认为这不是您想要的方式。该块应始终独立于键重复移动。因此,您不应该在关键事件期间移动块。您应该只在keydown和keyup事件期间跟踪键的状态,并处理其他地方的移动。实际的移动应该发生在某种定时器事件中(定时器控件会定期触发事件,即使没有任何事情发生),或者你应该有一个主循环不断地检查所有事物的状态并在适当时移动物体。如果您使用第二个选项,则需要查看“DoEvents”,因为如果您的代码一直在运行而没有完成该功能,程序将不会处理任何其他事件,如keyup和keydown事件。因此,您需要在每个循环中调用DoEvents来处理关键事件(以及移动窗口等)。如果你不需要经常处理事情,你可能还想调用System.Threading.Thread.Sleep。如果您使用计时器控件,则不必担心任何此类控件。

#3


Sorry, no specific help... it's saturday night after all... however:

对不起,没有具体的帮助......毕竟是星期六晚上...但是:

Each key needs a state. When you get a keydown event, you can tell which key went down, and modifiy the keystate you are maintaining. When the key comes back up, once again, from looking at the event, you can tell which it was. Only modify the state of the key that the event was fired against: i.e. don't reset all states with the keyup event, otherwise the state of keys you are holding in memory will be corrupted, as you are seeing.

每个密钥都需要一个状态。当你得到一个keydown事件时,你可以知道哪个键发生故障,并修改你正在维护的keystate。当钥匙重新启动时,再一次,通过查看事件,你可以知道它是什么。仅修改触发事件的键的状态:即不使用keyup事件重置所有状态,否则您在内存中保存的键的状态将被破坏,如您所见。