C # - Tetris clone - Не удается заставить блок правильно реагировать на комбинации клавиш со стрелками

Я работаю над программированием игры Тетрис на Visual C # 2005. Это самая обширная программа, которую я когда-либо создавал.

Я создаю класс формы и класс блока для управления расположением, перемещением и отображением различных элементов тетриса. У меня есть функции moveDown (), moveLeft () и moveRight () для каждой формы (и соответствующие логические функции canMoveDown (), canMoveLeft (), canMoveRight (), которые проверяют, можно ли двигаться). Все это прекрасно работает.

Я хочу использовать клавиши со стрелками вниз, вправо и влево, чтобы позволить пользователю перемещать блок, в дополнение к использованию таймера, чтобы форма автоматически опускалась на одну строку каждые столько миллисекунд.

Я использую обработчик событий KeyDown, чтобы проверить, когда пользователь нажимает клавишу со стрелкой вниз, влево и вправо. Это не так уж и сложно. Проблема в том, что я хочу разрешить диагональное движение, и я хочу, чтобы оно работало как можно более плавно. Я пробовал кучу разных способов решения этой проблемы с разным успехом. Но я не могу это понять ...

Мой самый успешный подход заключался в использовании трех логических переменных, чтобы отслеживать, когда удерживаются клавиши со стрелками вниз, влево и вправо. Я бы установил для логических значений true в событии KeyDown и false в событии KeyUp. В событии KeyDown я бы также сказал блоку, как перемещаться, используя логические переменные, чтобы проверить, какая комбинация была нажата в данный момент. Это сработало очень хорошо, за исключением одного момента.

Если бы я нажал одну из клавиш со стрелками и удерживал, затем нажал вторую клавишу со стрелкой, а затем отпустил вторую клавишу, блок полностью перестал бы двигаться, вместо того, чтобы продолжать движение в направлении первой клавиши со стрелкой, которая не была отпущена. пока что. Я думаю, это связано с тем, что второй ключ вызвал событие KeyDown, и после его выпуска событие KeyUp было запущено, а событие KeyDown перестало срабатывать полностью, даже если первый ключ запущен.

Я всю жизнь не могу найти удовлетворительного решения этой проблемы.

Любая помощь будет принята с благодарностью =)


person Daniel Waltrip    schedule 24.05.2009    source источник


Ответы (3)


Большинство игр не ждут событий. При необходимости они опрашивают устройство ввода и действуют соответствующим образом. Фактически, если вы когда-нибудь взглянете на XNA, вы увидите, что есть метод Keyboard.GetState () (или Gamepad.GetState ()), который вы вызовете в своей процедуре обновления и обновите свою игровую логику на основе результаты, достижения. При работе с Windows.Forms нет ничего стандартного для этого, однако вы можете воспользоваться функцией P / Invoke GetKeyBoardState (), чтобы воспользоваться этим. Хорошая вещь в этом заключается в том, что вы можете опрашивать несколько клавиш одновременно, и, следовательно, вы можете реагировать на более чем одно нажатие одновременно. Вот простой класс, который я нашел в Интернете, который помогает в этом:

http://sanity-free.org/17/obading_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 миллисекунд он проверяет состояние клавиатуры и, если нажата клавиша, перемещает ее и аннулирует, чтобы ее можно было перерисовать.

person BFree    schedule 24.05.2009
comment
Привет, большое спасибо, я попробовал ваш код и класс из ссылки, и он работал довольно хорошо. Мне пришлось немало изменить его, прежде чем он скомпилирован, но я думаю, что в основном это произошло из-за того, что я использую Visual Studio 2005, а не 2008. Я бесконечно искал в Интернете и не смог найти ничего, что работало бы так же хорошо, как это. Я надеюсь, что это поможет и другим. - person Daniel Waltrip; 24.05.2009
comment
Ой, это у меня плохо. Вы упомянули, что работаете с VS2005, я этого не заметил. Да, я так привык использовать автоматические свойства, а также инициализаторы объектов и ключевое слово var. Извините! Рад, что это сработало для вас. Вам следует изучить XNA для игр, в нем МНОГО замечательных вещей прямо из коробки. - person BFree; 24.05.2009

Если вы полагаетесь на повторение клавиши для многократной отправки событий нажатия клавиши, чтобы заставить блок двигаться, я не думаю, что вы хотите это делать именно так. Блок должен двигаться последовательно независимо от повтора клавиш. Поэтому не следует перемещать блок во время ключевых событий. Вы должны только отслеживать состояние клавиш во время событий keydown и keyup и обрабатывать перемещение в другом месте. Фактическое движение должно происходить либо в каком-то событии таймера (элемент управления таймером запускает события через регулярные промежутки времени, даже если ничего не происходит), либо у вас должен быть основной цикл, постоянно проверяющий состояние всего и движущихся объектов, когда это необходимо. Если вы используете второй вариант, вам нужно будет изучить «DoEvents», потому что, если у вас есть код, который постоянно выполняется без завершения функции, программа не будет обрабатывать никакие другие события, такие как события нажатия и нажатия клавиши. Таким образом, вы можете вызывать DoEvents в каждом цикле для обработки ключевых событий (среди прочего, например, перемещения окна). Вы также можете вызвать System.Threading.Thread.Sleep, если вам не нужно так постоянно обрабатывать вещи. Если вы используете таймер, вам не о чем беспокоиться.

person BlueMonkMN    schedule 24.05.2009

Извините, особой помощи нет ... все-таки субботняя ночь ... однако:

Каждому ключу нужно состояние. Когда вы получаете событие нажатия клавиши, вы можете определить, какая клавиша вышла из строя, и изменить состояние клавиши, которое вы поддерживаете. Когда ключ снова поднимется, взглянув на событие, вы поймете, какое это было. Изменяйте только состояние ключа, против которого было запущено событие: т.е. не сбрасывайте все состояния с событием keyup, иначе состояние ключей, которые вы держите в памяти, будет повреждено, как вы видите.

person spender    schedule 24.05.2009