C# - 俄罗斯方块克隆 - 无法让块正确响应箭头键组合

发布于 2024-07-21 06:55:12 字数 813 浏览 7 评论 0原文

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

我创建了一个形状类和一个块类来控制不同俄罗斯方块块的位置、移动和显示。 我为每个形状提供了 moveDown()、moveLeft() 和 moveRight() 函数(以及相应的 canMoveDown()、canMoveLeft()、canMoveRight() 布尔函数,用于验证是否可以移动)。 这一切都很顺利。

我想使用向下、向右和向左箭头键让用户移动块,此外还使用计时器让形状每隔几毫秒自动下降一行。

我正在使用 KeyDown 事件处理程序来检查用户何时按下向下、向左和向右箭头键。 这并不难。 问题是我想允许对角线运动,并且我希望它尽可能顺利地工作。 我尝试了多种不同的方法来解决这个问题,并取得了不同程度的成功。 但我不能完全正确...

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

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

我一生都无法找到一个令人满意的解决方案来解决这个问题。

任何帮助将不胜感激=)

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

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.

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...

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.

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.

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

Any help would be greatly appreciated =)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

给妤﹃绝世温柔 2024-07-28 06:55:12

大多数游戏不会等待事件。 他们在必要时轮询输入设备并采取相应的行动。 事实上,如果您查看过 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

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

首先,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;
        }
    }

真的没什么花哨的......

然后是 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);
            }
        }
    }

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

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 there'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:

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.

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:

    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.

雾里花 2024-07-28 06:55:12

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

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.

千纸鹤 2024-07-28 06:55:12

抱歉,没有具体的帮助...毕竟是星期六晚上...但是:

每个键都需要一个状态。 当您收到按键事件时,您可以知道哪个键被按下,并修改您正在维护的按键状态。 当钥匙再次出现时,通过查看事件,您可以辨别出它是什么。 仅修改触发事件的键的状态:即不要使用 keyup 事件重置所有状态,否则您在内存中保存的键的状态将被损坏,正如您所看到的。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文