NCURSES keyboard input while calling sleep - c++

I'm trying to use ncurses to create a game. I set it so that the character moves by arrow key input, but if I hold the arrow key for a while and then let go the character will keep moving for a while before stopping.
These are my initializations:
initscr();
start_color();
cbreak();
noecho();
nodelay(stdscr,TRUE);
keypad(stdscr, TRUE);
This is my main loop:
while(1) {
...
if (key == (char)27) {
break;
}
else if (key == KEY_DOWN) {
key = 0;
player->advance(0, 1);
}
else if (key == KEY_UP) {
key = 0;
player->advance(0, -1);
}
else if (key == KEY_LEFT) {
key = 0;
player->advance(-1, 0);
}
else if (key == KEY_RIGHT) {
key = 0;
player->advance(1, 0);
}
else {
key = getch();
}
std::this_thread::sleep_for(std::chrono::milliseconds {1000/30});
}
I'm using the sleep_for because I noticed that if I use timeout then the framrate changes if I'm pressing a key. Using sleep_for creates a consistent framerate, but input is somehow "sticky" as I explained. I need to fix this somehow. Either get a consistent framrate using timeout, or "un-stick" the input when using sleep_for.
player->advance(int, int) moves the player one step in the specified direction. Direction is specified as change in x and change in y to be applied to the current position.

You could call flushinp after each call on getch, to ignore type-ahead.

Related

GLFW key input treatment [duplicate]

Currently, when holding down the desired key, the input registers multiple times. Is there a way to process only the first event after the key is pressed and ignore the following events until the key is released?
I'm using a processInput function, with the following condition:
if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) {
currentXPos--;
if (currentXPos < 0)
currentXPos = 0;
}
currentXPos is just an integer that's affected by the left/right arrow keys. I have an equivalent currentYPos integer also that's affected by the up/down arrow keys. I need to increment/decrement currentXPos once per key-press. I've tried adding a global bool initially set to true and on execution setting it to false, like this:
if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_PRESS) {
if (canMove) {
canMove = false;
currentXPos--;
if (currentXPos < 0)
currentXPos = 0;
}
}
if (glfwGetKey(window, GLFW_KEY_LEFT) == GLFW_RELEASE) {
canMove = true;
}
This does work with the single key, but if I implement this functionality with the right arrow key as well, (for incrementing the same value), the below function constantly returns GLFW_RELEASE after the first release, setting the canMove bool to true and ultimately making it redundant;
if (glfwGetKey(window, GLFW_KEY_RIGHT) == GLFW_RELEASE) {
canMove = true;
}
I've tried using glfwWaitEvents() but that still processes multiple inputs when help for longer than 0.5 seconds or so (the same effect as holding down any character on the keyboard in a search bar/text editor).
When you want to handle every key just once, the best solution is to listen to the key callback event instead of querying the key state in every frame. The key callback is a function that can be hooked into glfw and is called once for every key event.
The callback should look somehow like this:
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_RIGHT && action == GLFW_PRESS)
{
currentXPos--;
if (currentXPos < 0)
currentXPos = 0;
}
}
This callback can then be registered somewhere after the point where the window has been created:
glfwSetKeyCallback(window, key_callback);
More details can be found in the GLFW Input Guide.

C++ How do I Handle all possible Player Movement inputs?

I am trying to clean up movement code I followed from a video tutorial series that was never finished. My intent is for the character to only ever be able to move on X or Y at any given time (so no diagonal). The character has direction facing to keep in mind.
My issue is the player can still press any key they want, or accidently press two keys at the same time.
Ex. if you move Up and make a right turn, accidentally press Right before letting go of Up.
Or if you press Up, press and let go Right to make a slight movement right while continuing to press Up, the player should continue to move up after letting go of Right without having to re-press Up. etc.
Just to make sure all possible input cases are handled intuitively.
EDIT: This is the code so far and I'm getting weird errors I don't know what's wrong
#pragma once
#include "../game.h"
#include "ECS.h"
#include "Components.h"
#include <list>
using namespace std;
class KeyboardController : public Component
{
public:
TransformComponent *transform;
SpriteComponent *sprite;
std::list<SDL_Event> keyDownList;
SDL_Event lastDirection;
void updateKeyState()
{
if (Game::event.type == SDLK_ESCAPE) {
Game::isRunning = false;
}
else if (Game::event.type == SDL_KEYDOWN) {
keyDownList.push_back(Game::event.key.keysym.sym);
}
else if (Game::event.type == SDL_KEYUP) {
keyDownList.remove(Game::event.key.keysym.sym);
}
}
void init() override
{
transform = &entity->getComponent<TransformComponent>();
sprite = &entity->getComponent<SpriteComponent>();
}
void update() override
{
void updateKeyState();
void updateMovement();
}
};
Severity Code Description Project File Line Suppression State
Error (active) E0304 no instance of overloaded function "std::list<_Ty, _Alloc>::push_back [with _Ty=SDL_Event, _Alloc=std::allocator]" matches the argument list Sandbox C:\file_path\KeyboardController.h 31
Severity Code Description Project File Line Suppression State
Error (active) E0415 no suitable constructor exists to convert from "SDL_Keycode" to "SDL_Event" Sandbox C:\file_path\KeyboardController.h 34
You should basically clean up your code by separating the logic between key events and player movement. So your update() method could look like this:
void update() override
{
updateKeyState();
updateMovement();
}
Since you want the player to move only vertically or horizontally (never diagonally), you have to store the key press order in a data structure that can be easily accessed. I think you could use a doubly-linked list:
std::list<SDL_Event> keyDownList;
and we should also store the last key pressed in order to restore the idle animation of the player:
SDL_Event lastDirection;
The updateKeyState() method should add or remove the key to/from the linked list. We should also check if the player wants to leave the game by pressing ESC:
void updateKeyState() {
if (Game::event.type == SDLK_ESCAPE) {
Game::isRunning = false;
} else if (Game::event.type == SDL_KEYDOWN) {
keyDownList.push_back(Game::event.key.keysym.sym);
} else if (Game::event.type == SDL_KEYUP) {
keyDownList.remove(Game::event.key.keysym.sym);
}
}
The updatePlayerMovement() method is where the magic happens. We should basically check which key was pressed first and update the player position accordingly. We also save the down key in the lastDirection field in order to use it when no key is pressed.
void updateMovement() {
// if any key is down
if (keyDownList.size() > 0) {
const SDL_Event downKey = keyDownList.front();
switch (downKey) {
case SDLK_w:
transform->velocity.y = -1;
transform->velocity.x = 0;
sprite->Play("BackWalk");
lastDirection = downKey;
break;
case SDLK_a:
transform->velocity.x = -1;
transform->velocity.y = 0;
sprite->Play("SideWalk");
sprite->spriteFlip = SDL_FLIP_HORIZONTAL;
lastDirection = downKey;
break;
case SDLK_s:
transform->velocity.y = 1;
transform->velocity.x = 0;
sprite->Play("FrontWalk");
lastDirection = downKey;
break;
case SDLK_d:
transform->velocity.x = 1;
transform->velocity.y = 0;
sprite->Play("SideWalk");
sprite->spriteFlip = SDL_FLIP_NONE;
lastDirection = downKey;
break;
}
} else {
// no key is down, handle idle situations
transform->velocity.x= 0;
transform->velocity.y = 0;
switch (lastDirection) {
case SDLK_w:
sprite->Play("BackIdle");
break;
case SDLK_a:
sprite->Play("SideIdle");
break;
case SDLK_s:
sprite->Play("FrontIdle");
break;
case SDLK_d:
sprite->Play("SideIdle");
break;
}
}
}
Note: I haven't tested this code because I don't have the code and structures from your game. So you may have to edit a piece here and there to make it work for you.

SDL_KEYDOWN triggering twice

I am following lazy foo's tutorial, however I realized every time I press press s or p, SDL_KEYDOWNtriggers twice. How can this be fixed?
Here is the code snippet:
while(SDL_PollEvent(&e) != 0) {
if(e.type == SDL_QUIT) {
quit = true;
}
else if(e.type == SDL_KEYDOWN) {
if(e.key.keysym.sym == SDLK_s) {
if(timer.isStarted()) {
timer.stop();
printf("stop\n");
}
else {
timer.start();
printf("start\n");
}
}
else if(e.key.keysym.sym == SDLK_p) {
if(timer.isPaused()) {
timer.unpause();
printf("unpause\n");
}
else {
timer.pause();
printf("pause\n");
}
}
}
}
Pressing s once:
start
stop
TL;DR: Check if e.key.repeat equals to 0 before handling the events.
SDL generates fake repeated keypresses if you hold a key long enough. This is used mostly for text input.
The original key press has .repeat == 0, and fake presses have .repeat == 1.
For convenience reasons probably (I'd argue that it's rather inconvenient), since SDL 2.0.5 the actual key press generates two events instead of one. One has .repeat set to 0, and other (new) one has it set to 1.

How to check if a key sequence is press?

I would like to check if a key sequence is press. Like a password, i would like that to see the message box you must type "bcqwl" in right sequenze. I tried
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
while(true)
{
if(GetKeyState('B') & 0x8000)
{
cout<<"b has been press"<<endl;
if(GetKeyState('C') & 0x8000)
{
cout<<"c has been press"<<endl;
if(GetKeyState('Q') & 0x8000)
{
cout<<"Q has been press"<<endl;
if(GetKeyState('W') & 0x8000)
{
cout<<"W has been press"<<endl;
if(GetKeyState('L') & 0x8000)
{
MessageBox(NULL,"YES","YES",MB_OK);
}
else
{
continue;
}
}
else
{
continue;
}
}
else
{
continue;
}
}
else
{
continue;
}
}
else
{
continue;
}
}
}
But it doesn't works. He print "B has been press" ,many times but not infinitely times,if b is pressed. If after to press b I press c nothing happens.
So i tired:
step:
if(GetKeyState('B') & 0x8000)
{
cout<<"B has been press"<<endl;
goto step1;
}
else
{
goto step;
}
step1:
if(GetKeyState('C') & 0x8000)
{
MessageBox(NULL,"WORK","yes",MB_OK);
}
else
{
goto step;
}
But doesn't work.
I also tired:
#include <iostream>
#include <windows.h>
int main()
{
int progress = 0;
while(progress<=4)
{
if(GetKeyState('B') & 0x8000)
{
std::cout<<"b has been press"<<std::endl;
progress=1;
}
else
{
progress=0;
}
if(progress==1)
{
if(GetKeyState('C') & 0x8000)
{
std::cout<<"c has been press"<<std::endl;
progress=2;
}
else
{
progress=0;
}
}
if(progress==2)
{
if(GetKeyState('Q') & 0x8000)
{
std::cout<<"q has been press"<<std::endl;
progress=3;
}
else
{
progress=0;
}
}
if(progress==3)
{
if(GetKeyState('W') & 0x8000)
{
std::cout<<"w has been press"<<std::endl;
progress=4;
}
else
{
progress=0;
}
}
if(progress==4)
{
if(GetKeyState('L') & 0x8000)
{
std::cout<<"l has been press"<<std::endl;
progress=5;
}
else
{
progress=0;
}
}
}
return 0;
}
But that output "b has been press" for many times but not infinitely if i press b, and after if i press c nothing is happening, praticaly after press b and the program go in if(process==1) but if i press c nothing happen
P.S. sorry for my bad english.
The problem you are still having comes from the fact that you are in no way storing the progress in your key sequence.
Let's say your password is stored in a char array (for easier access to the single characters later on):
#define PWD_LEN 6 // bcqwl has a length of 5 characters but you need +1 for the '\0' (terminating character) at the end of your string
// ...
char password[PWD_LEN] = "bcqwl";
In addition you will need a counter:
#define PWD_LEN 5
// ...
char password[PWD_LEN] = "bcqwl";
int progress = 0;
while(true) { ... }
Both need to be stored before and outside the while loop because both store data that you don't want to reset in each iteration step in your loop.
The counter will be used to track the progress of the user towards completing the key sequence your password represents.
Whenever the user presses a key you will need to do the following checks:
Is the key allowed? - if you have a password abc but the user presses y or !, or something other then a, b or c the key is not allowed.
If the key is allowed take the character it represents and check if it's the same as the character in your password at index progress:
if ( key allowed )
{
if (password[progress] == '<your key character here>')
{
++progress;
}
else
{
// Handle incorrect key stroke relative to key sequence
}
}
Now in order to prevent the counter going bananas I would suggest doing all the checking upon key released, which unlike key pressed is a one time event. A key being pressed can also be part of key hold, in which case you will land in the else (from the code snippet above) many times, which might not be such a good idea.
If the key is ok and in terms of sequence it fits your password then you increase the progress so that in the next iteration step you can do the same check with a new released key event and a new index value for accessing your password array.
When your progress reaches the required value indicating that the key sequence is completed and that all the characters your password consists of have been "inserted" you can break the loop.
This is very basic so I hope that the instructions are clear enough for you to implement.
PREVIOUS ANSWER (now obsolete due to change in the question)
Unless other out-of-the-box way is available to do that (I haven't used GetKeyState() or anything else from windows.h) the general practice is to simply store the pressed state of each button (in an array, a struct etc.).
In your loop you can use a simple chain of if statements (but not nested like you do it!) to check which button is pressed during the current iteration step. Whenever a known key (one that you application wants to process) is pressed, you just need to toggle the respective state of that button in your array, struct or whatever other container you use to store this information:
while(true)
{
// Check state of supported buttons
if(GetKeyState('A') & 0x8000)
{
// store change of state of key A
}
if(GetKeyState('B') & 0x8000)
{
// store change of state of key B
}
if (...)
{
// ...
}
}
At the end or beginning of your while's body you can then ask for the state of each button and also make combined queries:
while(true)
{
// Check state of supported buttons
if(GetKeyState('A') & 0x8000)
{
// store change of state of key A
}
if(GetKeyState('B') & 0x8000)
{
// store change of state of key B
}
if (...)
{
// ...
}
// Do something
// Check for states of buttons
// ...
}
For example let's say you are using the following structure to store the key's state:
typedef struct Key
{
char keyCode;
bool pressed;
} Key;
Key keyA;
keyA.pressed = false;
you can simply do
if (keyA.pressed)
{
// Trigger some change that key 'A' controls
}
to check if your specific button is currently pressed and respectively trigger some action.
For key combinations things are not much different. You just need to use simply boolean logic to handle it:
if (keyA.pressed && keyB.pressed)
{
// Both key 'A' and 'B' are pressed - we have a key combo!
}
You can improve the readability of your code by storing all supported buttons in an array and even adding a nice enum to provide easy access to each button:
enum KeyCode
{
A,
B,
...
};
Key keys[n]; //with n being the number of keys you want to support and also the number of elements your enum has
// Access with an improved readability
if (keys[A].pressed && keys[B].pressed)
{
// ...
}
If you can't find any code for GetKeyState() in particular (highly unlikely) you can look at SDL for example.

Handling input with GLFW

Here is the definition of my keyboard callback method I'm using to check for user input for a 2D basic game. However I'm having a problem handling certain simultaneous key presses.
For example, If I hold the Right and Up arrows keys, the player moves 45 degrees toward the top right of the screen as it should. Then, while still holding the up and right keys, if I press Space (which fires a projectile), that works as well.
However, if I hold the Left and Down arrow keys, the player moves as it should, but when I press Space, I get no input response, so I can't fire a projectile when moving down and left. All other movement + fire projectile combinations work, just the down and left doesn't... I can't figure out why. Any ideas?
if (key == GLFW_KEY_LEFT)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_LEFT, action);
}
else if (key == GLFW_KEY_RIGHT)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_RIGHT, action);
}
else if (key == GLFW_KEY_UP)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_UP, action);
}
else if (key == GLFW_KEY_DOWN)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_DOWN, action);
}
else if (key == GLFW_KEY_SPACE)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_SPACE, action);
}
else { }
Rollover is the property that allows a keyboard to properly register many key presses at once. Keyboards are wired in rows and columns. Even if the keyboard is not square, the individual keys are in a roughly square matrix of wires connecting their switches. The controller connects one row and then tests to see which of the columns are hit. Some key combinations "shadow" others. The controller can tell when the input is ambiguous and send no keys.
Better keyboard use diodes arranged to avoid the ambiguity and thus support "full rollover", although in practice USB limits you to all the modifiers plus 6 distinct keycodes at once.
Sounds like a crappy keyboard.
You may have to procure a better one.
Or use different key combinations.
The code should be
if (key == GLFW_KEY_LEFT)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_LEFT, action);
}
if (key == GLFW_KEY_RIGHT)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_RIGHT, action);
}
if (key == GLFW_KEY_UP)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_UP, action);
}
if (key == GLFW_KEY_DOWN)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_DOWN, action);
}
if (key == GLFW_KEY_SPACE)
{
GameController::getInstance()->getPlayer()->changeKeyPress(GLFW_KEY_SPACE, action);
}
The problem is your code just detect 1 key press at a time so when you press left and right at the same time only the if (key == GLFW_KEY_LEFT) is fired