Как одновременно нажимать несколько клавиш, используя события в реальном времени? (Аллегро 5)

Это проблема, которая преследует меня годами.

Вот мои файлы game.h и game.cpp:

game.h

#ifndef GAME_H_INCLUDED
#define GAME_H_INCLUDED

#include "init.h"

ALLEGRO_BITMAP *load_bmp(path *s);

struct Actor {
    const char *path;
    ALLEGRO_BITMAP *bmp;
    int x;
    int y;
    int speed;
};

void init_game_bitmaps();
void draw_game_bitmaps();

extern map<string, bool> key_states;
void init_key_states();
void check_states();

void control_actor(Actor *target, int speed);

extern Actor player;

#endif // GAME_H_INCLUDED

game.cpp

#include "game.h"

ALLEGRO_BITMAP *load_bmp(path *s) {
    ALLEGRO_BITMAP *bmp = nullptr;
    bmp = al_load_bitmap(s);
    if (!bmp) {

        al_show_native_message_box(display,
            "Fatal Error!",
            "Failed to load: " ,
            s,
            NULL,
            ALLEGRO_MESSAGEBOX_ERROR);

        al_destroy_display(display);
        return nullptr;

    }

    return bmp;
}

map<string, bool> key_states;
void init_key_states() {

    key_states["UP"] = false;
    key_states["DOWN"] = false;
    key_states["LEFT"] = false;
    key_states["RIGHT"] = false;

}

void check_states() {
    auto key = e.keyboard.keycode;
    for (auto it = key_states.begin(); it != key_states.end(); ++it) {
        if (e.type == ALLEGRO_EVENT_KEY_DOWN) {
            if (key == ALLEGRO_KEY_UP) {
                if (it->first == "UP") {
                    it->second = true;
                }
            }
            if (key == ALLEGRO_KEY_DOWN) {
                if (it->first == "DOWN") {
                    it->second = true;
                }
            }
            if (key == ALLEGRO_KEY_LEFT) {
                if (it->first == "LEFT") {
                    it->second = true;
                }
            }
            if (key == ALLEGRO_KEY_RIGHT) {
                if (it->first == "RIGHT") {
                    it->second = true;
                }
            }
        } else if (e.type == ALLEGRO_EVENT_KEY_UP) {
            if (key == ALLEGRO_KEY_UP) {
                if (it->first == "UP") {
                    it->second = false;
                }
            }
            if (key == ALLEGRO_KEY_DOWN) {
                if (it->first == "DOWN") {
                    it->second = false;
                }
            }
            if (key == ALLEGRO_KEY_LEFT) {
                if (it->first == "LEFT") {
                    it->second = false;
                }
            }
            if (key == ALLEGRO_KEY_RIGHT) {
                if (it->first == "RIGHT") {
                    it->second = false;
                }
            }
        }
        cout << it->first << " : " << it->second << endl;
    }
}

void control_actor(Actor *target, int speed) {
    if (key_states["UP"]) {
        target->y -= speed;
    }
    if (key_states["DOWN"]) {
        target->y += speed;
    }
    if (key_states["LEFT"]) {
        target->x -= speed;
    }
    if (key_states["RIGHT"]) {
        target->x += speed;
    }
}

Actor player = {
    "GFX\\player_up.png",
    nullptr,
    (SCREEN_WIDTH / 2) - (ACTOR_SIZE / 2),
    (SCREEN_HEIGHT / 2) - (ACTOR_SIZE / 2),
    8};

void init_game_bitmaps() {
   player.bmp = load_bmp(player.path);
}

void draw_game_bitmaps() {
    al_draw_bitmap(player.bmp, player.x, player.y, 0);
    al_flip_display();
}

Теперь вот мой основной файл:

main.cpp

#include "init.h"
#include "game.h"

int main(int argc, char **argv){

    init_all();
    register_all();
    init_game_bitmaps();
    init_key_states();

    while (running) {

        draw_game_bitmaps();
        al_wait_for_event(event_queue, &e);

        if (e.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
            running = false;
        }

        check_states();
        control_actor(&player, player.speed);

        if (e.type == ALLEGRO_EVENT_KEY_DOWN) {
            if (e.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
                running = false;
            }
            if (e.keyboard.keycode == ALLEGRO_KEY_ENTER) {
                cout << "It works!";
            }
        }

    }

    destroy_all();

    return 0;
}

Как видите, у меня есть std::map, в котором хранятся состояния клавиш (по одному для каждой стрелки клавиатуры), а затем у меня есть процедура с именем check_states(), которая перебирает все состояния в каждом основном цикле и устанавливает их значение true, если соответствующие стрелки нажаты (вниз), и значение false, когда они отпущены.

Проблема:

Если я нажму ВВЕРХ и буду держать, а затем нажму ВЛЕВО (не отпуская клавишу ВВЕРХ), игрок будет двигаться по диагонали. Тем не менее, если я отпущу ЛЕВУЮ, все еще удерживая клавишу ВВЕРХ, проигрыватель остановится, и состояние для ВВЕРХ будет истинным (и я вижу это, потому что я m отмечая это).

Теория

al_wait_for_event() ожидает, пока указанная очередь событий не станет пустой (что означает, что когда Я нажимаю клавишу ВВЕРХ, она копирует событие в e, а затем, когда я нажимаю ВЛЕВО, она должна отменить ВВЕРХ и назначить новое событие на e, тем самым создавая неприятный LAG, когда я нажимаю более одной клавиши одновременно). Имея это в виду, я пришел к выводу: ну, я мог бы иметь как минимум ПЯТЬ отдельных event_queues и ПЯТЬ различных «объектов событий». Мне удалось создать вектор event_queues и событий и выполнить итерацию по ним обоим в каждом основном цикле, передавая каждое событие в соответствующую event_queue И ПРОБЛЕМА РЕШАЕТСЯ.

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

ИЗМЕНИТЬ:

Я изменил свою процедуру control_actor(), чтобы использовать al_key_down() вместо проверки событий, вот ее код:

void control_actor(ALLEGRO_KEYBOARD_STATE *key, Actor *target, int speed) {
    if (al_key_down(key, ALLEGRO_KEY_UP)) {
        target->y -= speed;
        cout << "UP" << endl;
    }
    if (al_key_down(key, ALLEGRO_KEY_DOWN)) {
        target->y += speed;
        cout << "DOWN" << endl;
    }
    if (al_key_down(key, ALLEGRO_KEY_LEFT)) {
        target->x -= speed;
        cout << "LEFT" << endl;
    }
    if (al_key_down(key, ALLEGRO_KEY_RIGHT)) {
        target->x += speed;
        cout << "RIGHT" << endl;
    }
}

И новый main.cpp:

#include "init.h"
#include "game.h"

int main(int argc, char **argv){

    init_all();
    register_all();
    init_game_bitmaps();
    ALLEGRO_KEYBOARD_STATE key;

    while (running) {

        draw_game_bitmaps();
        al_wait_for_event(event_queue, &e);

        al_get_keyboard_state(&key);

        control_actor(&key, &player, player.speed);

        if (e.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
            running = false;
        }

        if (e.type == ALLEGRO_EVENT_KEY_DOWN) {
            if (e.keyboard.keycode == ALLEGRO_KEY_ESCAPE) {
                running = false;
            }
            if (e.keyboard.keycode == ALLEGRO_KEY_ENTER) {
                cout << "It works!";
            }
        }

    }

    destroy_all();

    return 0;
}

Сообщение на форумах allegro, ссылка на которое содержится в комментарии, относится к 2002 году, и этот код не больше не работает на Allegro 5. Поэтому я проверил документацию и Я скажу вам: ПРОБЛЕМА РЕШАЕТСЯ. Происходит ТОЧНО то же самое. Одна стрелка отменяет другую и игрок перестает двигаться на какое-то время, как только я одновременно нажимаю другую стрелку.


person Ericson Willians    schedule 06.05.2015    source источник
comment
Нельзя ли создать две переменные события, такие как e и f. Затем вы можете опросить, чтобы узнать, подхватило ли событие e нажатие клавиши, а затем посмотреть, подхватило ли событие f нажатие клавиши. Однако кажется, что для этого потребуется много случаев if-else. В противном случае это, похоже, решит вашу проблему.   -  person mas4    schedule 06.05.2015
comment
Этот пост от 2002 года, и код больше не работает. Тем не менее, я узнал в документах, как использовать его таким образом, и я отредактировал сообщение с обновленной проблемой.   -  person Ericson Willians    schedule 06.05.2015


Ответы (1)


Пример базовой клавиатуры на вики allegro может оказаться более полезным, чем этот старый пост. .

Здесь нет необходимости управлять несколькими очередями событий. Каждое нажатие и отпускание клавиши должно помещаться в очередь — вам просто нужно убедиться, что вы обрабатываете каждое событие в очереди.

По сути, вам нужен основной цикл, который:

  1. Обрабатывает каждое событие в очереди.
  2. Обновляет состояние игры
  3. Перерисовывает экран

Вот пример, который я набросал:

#include <stdio.h>
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>

const int SPEED = 5;
const float FPS = 60;

int main(int argc, char **argv) {
  ALLEGRO_DISPLAY *display = NULL;
  ALLEGRO_EVENT_QUEUE *event_queue = NULL;
  ALLEGRO_TIMER *timer = NULL;
  int x = 0, y = 0;   // position
  int vx = 0, vy = 0; // velocity

  // initialize everything we need -- error checking omitted for brevity
  al_init();
  al_install_keyboard();
  al_init_primitives_addon();
  display = al_create_display(640, 480);
  event_queue = al_create_event_queue();
  timer = al_create_timer(1.0 / FPS);
  al_register_event_source(event_queue, al_get_keyboard_event_source());
  al_register_event_source(event_queue, al_get_timer_event_source(timer));
  al_start_timer(timer);

  bool done = false;
  while(!done) {
    bool redraw = false;

    // process events until queue is empty
    while(!al_is_event_queue_empty(event_queue)) {
      ALLEGRO_EVENT ev;
      al_wait_for_event(event_queue, &ev);

      switch(ev.type) {
        case ALLEGRO_EVENT_KEY_DOWN:
          switch(ev.keyboard.keycode) {
            case ALLEGRO_KEY_W:
              vy -= SPEED; // add upward velocity
              break;
            case ALLEGRO_KEY_S:
              vy += SPEED; // add downward velocity
              break;
            case ALLEGRO_KEY_A:
              vx -= SPEED; // add leftward velocity
              break;
            case ALLEGRO_KEY_D:
              vx += SPEED; // add leftward velocity
              break;
            case ALLEGRO_KEY_ESCAPE:
              done = true;
              break;
          }
          break;
        case ALLEGRO_EVENT_KEY_UP:
          switch(ev.keyboard.keycode) {
            case ALLEGRO_KEY_W:
              vy += SPEED; // remove upward velocity
              break;
            case ALLEGRO_KEY_S:
              vy -= SPEED; // remove downward velocity
              break;
            case ALLEGRO_KEY_A:
              vx += SPEED; // remove leftward velocity
              break;
            case ALLEGRO_KEY_D:
              vx -= SPEED; // remove leftward velocity
              break;
          }
          break;
        case ALLEGRO_EVENT_TIMER:
          redraw = true; // time for next frame
          break;
      }
    }

    // got through all the events this loop -- redraw if necessary
    if (redraw) {
      // move circle
      x += vx;
      y += vy;

      // draw circle
      al_clear_to_color(al_map_rgb(0, 0, 0));
      al_draw_filled_circle(x, y, 20, al_map_rgb(0, 0, 255));
      al_flip_display();
    }
  }

  al_destroy_display(display);

  return 0;
}

Обратите внимание на while(!al_is_event_queue_empty(event_queue)). Это гарантирует, что мы не пропустим ни одного события, прежде чем перейти к циклу обновления.

Если вы попытаетесь запустить пример, круг должен соответствующим образом реагировать на любую комбинацию клавиш WASD. Если вы удерживаете S+D, он будет двигаться по диагонали вправо и вниз. Отпустите S, и он продолжит двигаться вправо, но не вниз.

Надеюсь это поможет!

person rcorre    schedule 06.05.2015
comment
Теперь я понимаю, что надо использовать таймер и перерисовывать в ALLEGRO_EVENT_TIMER. Большое спасибо! Кстати, мне пришлось немного подправить ваш код, потому что скорость в моем случае мне не нужна. Я имею в виду, что ваш код идеально подходит для игры Asteroids :p. - person Ericson Willians; 06.05.2015