C # - клон тетриса - не удается заставить блок правильно реагировать на комбинации клавиш со стрелками - PullRequest
4 голосов
/ 24 мая 2009

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

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

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

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

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

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

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

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

Ответы [ 3 ]

9 голосов
/ 24 мая 2009

Большинство игр не ждут событий. Они опрашивают устройство ввода, когда необходимо, и действуют соответственно. На самом деле, если вы когда-нибудь взгляните на 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 миллисекунд он проверяет состояние клавиатуры, а если нажать клавишу, он перемещает ее и делает недействительной, чтобы она могла перерисоваться.

1 голос
/ 24 мая 2009

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

0 голосов
/ 24 мая 2009

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

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

...