Change game controls in game - c++

I'm programming game in C++ and Allegro 5, and I wanted to make controls settings. So if player wants, he can change controls in settings menu (in game).
I have this code:
while(!exit)
{
ALLEGRO_EVENT ev;
al_wait_for_event(e_queue, &ev);
if (ev.type == ALLEGRO_EVENT_KEY_DOWN)
{
switch(ev.keyboard.keycode)
{
case ALLEGRO_KEY_DOWN:
//player goes down...
break;
case ALLEGRO_KEY_UP:
//player goes up...
break;
case ALLEGRO_KEY_LEFT:
//player goes left...
break;
case ALLEGRO_KEY_RIGHT:
//player goes right...
break;
case ALLEGRO_KEY_SPACE:
break;
default:
break;
}
}
...
}
I thought, that I can make variable of ALLEGRO KEY CODE (i dont know if type like this exists) type, which has key code (for example ALLEGRO_KEY_LEFT), and than replace this code with:
...
allegro key code keyUP;
allegro key code keyDOWN;
...
while(!exit)
{
ALLEGRO_EVENT ev;
al_wait_for_event(e_queue, &ev);
if (ev.type == ALLEGRO_EVENT_KEY_DOWN)
{
switch(ev.keyboard.keycode)
{
case keyDOWN:
break;
case keyUP:
break;
case keyLEFT:
break;
case keyRIGHT:
break;
case keySPACE:
break;
default:
break;
}
}
}
and add in settings something like this:
//set 'UP' key
switch(ev.keyboard.keycode)
{
case ALLEGRO_KEY_A:
keyUP = ALLEGRO_KEY_A;
break;
case ALLEGRO_KEY_B:
keyUP = ALLEGRO_KEY_B;
break;
case ALLEGRO_KEY_UP:
keyUP = ALLEGRO_KEY_UP;
break;
...
}
}
The point is, I don't know what type store things like ALLEGRO_KEY_UP, DOWN ...
Thanks!

I would have two maps, one that maps a function name to a function, and one that maps a key to either the function name or the function. When the user want to remap keys, change the second map.
It could be something like
std::unordered_map<std::string, std::function<void()>> function_map;
function_map["up"] = std::bind(&functionForUp);
function_map["down"] = std::bind(&functionForDown);
function_map["jump"] = std::bind(&functionForJump);
// etc.
std::unordered_map<int, std::string> key_map;
key_map[ALLEGRO_KEY_UP] = "up";
key_map[ALLEGRO_KEY_DOWN] = "down";
key_map[ALLEGRO_KEY_SPACE] = "jump";
// etc.
To call the function for a specific key, use e.g.
function_map[key_map[ev.keyboard.keycode]]();
This allows you to change the key_map at will:
key_map[ALLEGRO_KEY_J] = "jump";
References:
std::unordered_map
std::function
std::bind

All these Key Codes should be defined numbers. So maybe try using an integer.
int key = ALLEGRO_KEY_DOWN;
This should work.

Related

Why does XGrabKeyboard() not register some keyboard inputs?

I'm a beginner in programmation with C++ and for two weeks I'm writting a script to play a space-shooter for 2 players on a same keyboard.
For game display, I'm using NCURSES and for keyboard inputs, I'm using X11. For the start, I have a persistent problem with keyboard inputs.
With a friend, we wrote this function :
//earlier in the script :
Display *dpy;
if (NULL==(dpy=XOpenDisplay(NULL))) {
perror("Connexion problem");
exit(1);
}
XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync, CurrentTime);
//the function (keysPressed is just an array to store keyboard inputs) :
void getKBinput(bool (&keysPressed)[128], Display* dpy){
XEvent ev;
int kc;
if (XPending(dpy)){
XNextEvent(dpy, &ev);
switch (ev.type) {
case KeyPress:
kc = ((XKeyPressedEvent*)&ev)->keycode;
keysPressed[kc] = true;
break;
case KeyRelease:
kc = ((XKeyReleasedEvent*)&ev)->keycode;
keysPressed[kc] = false;
break;
case Expose:
while (XCheckTypedEvent(dpy, Expose, &ev));
break;
case ButtonPress:
case ButtonRelease:
case MotionNotify:
case ConfigureNotify:
default:
break;
}
}
}
}
//later in the script :
XUngrabKeyboard(dpy, CurrentTime);
if (XCloseDisplay(dpy)) {
perror("You can't deconnect to the server");
exit(1);
}
I'm using this function inside a loop-while to move each spaceship depending of what players are pressing each frame.
However, when I'm using specific keys' mixes, the program doesn't take care of some keys (so he doesn't register them in the boolean array keysPressed).
For exemple, when the player 1 is pressing the key "D" and the key "C" at the same moment, the player 2 can't use the key "up" or the key "!" (but the player 2 can use the key "left" and the key "down" at the same time without problem).
To get a better view of what this script is doing I rewritted this part in an external file to print the keys pressed and the keys released:
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv){
Display *dpy;
XEvent ev;
char * s;
unsigned int kc;
int quit = 0;
if (NULL==(dpy=XOpenDisplay(NULL))) {
perror(argv[0]);
exit(1);
}
XAutoRepeatOff(dpy);
XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync, CurrentTime);
for (;!quit;) {
XNextEvent(dpy, &ev);
switch (ev.type) {
case KeyPress:
kc = ((XKeyPressedEvent*)&ev)->keycode;
s = XKeysymToString(XKeycodeToKeysym(dpy, kc, 0));
if (s) printf("KEY PRESS:%s\n", s);
if (!strcmp(s, "q")) quit=~0;
break;
case KeyRelease:
kc = ((XKeyReleasedEvent*)&ev)->keycode;
s = XKeysymToString(XKeycodeToKeysym(dpy, kc, 0));
if (s) printf("KEY RELEASE:%s\n", s);
case Expose:
while (XCheckTypedEvent(dpy, Expose, &ev));
break;
case ButtonPress:
case ButtonRelease:
case MotionNotify:
case ConfigureNotify:
default:
break;
}
}
XUngrabKeyboard(dpy, CurrentTime);
XAutoRepeatOn(dpy);
if (XCloseDisplay(dpy)) {
perror(argv[0]);
exit(1);
}
return 0;
}
This confirmed my problem. Because my computer is old (and some keys work horribly) I tested this script on other computers to fall on same outputs.
I searched in x11 documentation (maybe not correctly ?), a function to unlock every mixes between keys or an other way to get around this limitation. I found nothing. Should I use an other library for keyboard inputs ?
Sorry for my poor english (and the potential mistakes) I made my best to be understandable. Thank you for your reading.

Open GL SDL Keypresses and behaviours mismatched on program start

I'm developing a simple game in OpenGL and using the SDL library to handle my input.
The game uses several keyboard buttons for input. Two arrow keys, the space bar, the 'r' button to reset the game and escape to close it.
What's currently happening is that the output or the methods being called when these buttons are pressed when I first launch the game are mismatched. (i.e left arrow performs the right arrow's method). This random behaviour ends once I press each of the buttons the game uses apart from the escape button once.
I'm wondering if there is something I'm missing as it feels like I have to 'assign' each button on each launch of the game. I've provided my input handling code below.
if (e.type == SDL_KEYDOWN) {
switch (e.key.keysym.sym) {
case SDLK_ESCAPE:
m_isClosed = true;
break;
case SDLK_SPACE:
m_selection = true;
break;
case SDLK_LEFT:
m_leftPressed = true;
break;
case SDLK_RIGHT:
m_rightPressed = true;
break;
case SDLK_r:
m_reset = true;
break;
}
}
if(e.type == SDL_KEYUP)
switch (e.key.keysym.sym)
{
case SDLK_LEFT:
m_leftPressed = false;
m_keydown = false;
break;
case SDLK_RIGHT:
m_rightPressed = false;
m_keydown = false;
break;
case SDLK_SPACE:
m_selection = false;
m_keydown = false;
break;
case SDLK_r:
m_reset = false;
m_keydown = false;
break;
}
}
Each event handler simply sets a boolean used for the game logic in another class. If you think you need to see anymore code in order to help me let me know.
It turns out that I had not initialised the booleans corresponding to the button listeners to false. A silly mistake but I thought I should provide the solution nonetheless.

SDL Smoother movement in game

I have got a small problem with my game.
It is not a big deal but I'd love to sort this out.
So this is my input processing function:
void MainGame::processInput()
{
SDL_Event evnt;
while (SDL_PollEvent(&evnt)) {
switch (evnt.type) {
case SDL_QUIT:
_gameState = GameState::EXIT;
break;
case SDL_MOUSEMOTION:
break;
case SDL_KEYDOWN:
switch (evnt.key.keysym.sym) {
case SDLK_RIGHT:
player.movePlayer(1);
break;
case SDLK_LEFT:
player.movePlayer(2);
break;
}
}
}
}
and it works just fine, but as you can probably imagine, when I press an arrow key, it moves once (calls the player.movePlayer(); function once) then pauses for a fraction of a second and then continues to read input.
I don't really know how to word it but I hope you know what I mean.
You can also see this effect when you hold any letter in word.
Let's say you press W, it will display the first W instantly and then remaining ones after a fraction of a second something like that:
w wwwwwwwwwwwwwwwwwwwww
I really don't know how to explain it.
So the question is, is there a way to read the raw input from the keyboard so the function will be called continuously straight away?
I often use code like this:
bool keyboard[1<<30]; // EDIT: as noted in the comments this code might not be the most efficient
and in the event loop
while (SDL_PollEvent(&evnt)) {
switch (evnt.type) {
case SDL_QUIT:
_gameState = GameState::EXIT;
break;
case SDL_MOUSEMOTION:
break;
case SDL_KEYDOWN:
keyboard[evnt.key.keysym.sym] = true;
break;
case SDL_KEYUP:
keyboard[evnt.key.keysym.sym] = false;
break;
}
step();
}
and the step function:
void step() {
if(keyboard[SDLK_RIGHT]) player.movePlayer(1);
else if(keyboard[SDLK_LEFT]) player.movePlayer(2);
}

SDL 2.0 Key repeat and delay

I'm having a problem with SDL 2.0 keyboard input in pong-like game. When I order to move to the left by pressing left arrow, it is processed by SDL_PollEvents() and responds correctly if the key was pressed once. However, if I keep the key pressed, I get a short delay (as long as Windows key repeat delay) before moving continuously.
Here is function processing events:
void Event::PlayerEvent (Player &player)
{
while (SDL_PollEvent (&mainEvent))
{
switch (mainEvent.type)
{
case SDL_KEYDOWN :
switch (mainEvent.key.keysym.sym)
{
case SDLK_ESCAPE :
gameRunning = false;
break;
case SDLK_LEFT :
player.moving = player.left;
break;
case SDLK_RIGHT :
player.moving = player.right;
}
break;
case SDL_QUIT :
gameRunning = false;
}
}
}
EDIT: After all, I managed to fix this issue by calling
SystemParametersInfo (SPI_SETKEYBOARDDELAY, 0, 0, 0) at the start of the program and SystemParametersInfo (SPI_SETKEYBOARDDELAY, 1, 0, 0) at the end, to return to standard key repeat delay.
For game movement, you would typically not use events, but rather use states.
Try using SDL_GetKeyboardState() outside of the event loop:
const Uint8* keystates = SDL_GetKeyboardState(NULL);
...
if(keystates[SDL_SCANCODE_LEFT])
player.moving = player.left;
else if(keystates[SDL_SCANCODE_RIGHT])
player.moving = player.right;
using SPI_SETKEYBOARDDELAY is not a good approach. This way your game will not be protable anymore since its only available on Windows.
Instead you should use like menthiond in an answer before SDL_GetKeyboardState.
Howeve be aware that you still have to collect the SDL_PollEvent. Otherwise SDL_GetKeyboardState will be always empty.
So it should be like this:
//...
SDL_Event sdlEvent;
while (SDL_PollEvent(&sdlEvent)) {
if (sdlEvent.type == SDL_QUIT) {
//..
}
}
const Uint8* keystates = SDL_GetKeyboardState(NULL);
if(keystates[SDL_SCANCODE_LEFT]) {
//...
}
if(keystates[SDL_SCANCODE_RIGHT]) {
/...
}
//...
simple as that
int vertical = 0;
int horizontal = 0;
float x = 500;
float y = 500;
float speed = 5.0;
in your sdl loop:
if (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_KEYDOWN:
{
switch (event.key.keysym.sym)
{
case SDLK_LEFT: horizontal=-1; break;
case SDLK_RIGHT: horizontal = 1; break;
case SDLK_UP: vertical=-1; break;
case SDLK_DOWN: vertical=+1; break;
}
break;
}
case SDL_KEYUP:
{
switch (event.key.keysym.sym)
{
case SDLK_LEFT: horizontal = 0; break;
case SDLK_RIGHT: horizontal = 0; break;
case SDLK_UP: vertical = 0; break;
case SDLK_DOWN: vertical = 0; break;
}
break;
}
}
}
x += horizontal * speed;
y += vertical * speed;
Use the SDL_GetKeyboardState capturing outside of - while (SDL_PollEvent (&mainEvent)), that works fine.

C++ & SDL: SDL event repeats endlessly

I have this code which is a part of a simple SDL-based game:
SDL_PollEvent(&event);
switch (event.type){
case SDL_QUIT:
quit = true;
break;
case SDL_KEYDOWN: //Det här är för när en knapp trycks ner...
switch (event.key.keysym.sym){
case SDLK_RIGHT:
player1.x_vel = 5.5;
std::cout<<"OOOO\n";
break;
case SDLK_DOWN:
player2_sprite.src.x = 58;
break;
}
break;
case SDL_KEYUP: //Det här är för när en knapp släpps upp..
switch (event.key.keysym.sym){
case SDLK_UP:
std::cout<<"haaaaa\n";
if(player1.canJump){
player1.y_vel = -7.5;
player1.canJump = false;
}
break;
case SDLK_DOWN:
std::cout<<"BBBB\n";
player2_sprite.src.x = 0;
break;
}
break;
}
Now, upon the release of the UP or DOWN arrow key, the release-event keeps triggering forever until any other event is handled. What am I doing wrong here?
You should be checking the return value of SDL_PollEvent. It returns 1 if there is an event to handle, and 0 if there is not. Normally, you handle events in a loop like this (copied from the documentation here):
while (1) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
// handle your event here
}
// do some other stuff here -- draw your app, etc.
}
What is probably happening in your case, is you are handling the event even when SDL_PollEvent returns 0. The event object is left unmodified in that case, so you keep handling the most recent event over and over.