I have a event handler class that adds all events to a vector. This vector is cleared after each frame. However if it is cleared after each frame the events cannot be detected outside of the handle function of the event handler class. If I don't clear the vector at the end of each frame. The program becomes unresponsive. Although I can check that my for loop is reading the vector of events for a while until the vector becomes for to clogged up for the program to make sense of it.
My questions are:
Is the use of a for loop to iterate over the events a good way to check for these events? Or would it be more useful to use a while loop to check this vector for events much like you would using while(SDL_PollEvents(&e))?
Why would my program become unresponsive when I am checking the events within the vector when I am not clearing the vector?
How can I efficently check my vector eventList outside of the class. Where I could check for keydown events or similar using the vector and also clearing the vector after I have checked for the events without the program becoming unresponsive?
This is my main function where I am trying to read these events without the above mentioned problems.
int main(int argc, char* argv[]){
EventHandler handler;
SDL_SetRenderDrawColor(handler.render, 49, 49, 49, 255);
SDL_RenderClear(handler.render);
SDL_RenderPresent(handler.render);
if(handler.eventList.size() >= 1){
std::cout << "Detected an event: " << handler.eventList.size() << "\n";
}
for(std::vector<SDL_Event>::const_iterator i = handler.get().begin(); i != handler.get().end(); i++){
if(i->type == SDL_KEYDOWN){
if (i->key.keysym.sym == SDLK_w){
std::cout << "You pressed the W key\n";
}
}
}
handler.eventList.clear();
handler.handle();
}
return 0;
}
This is where I want to be able to check the events thats in the eventList
for(std::vector<SDL_Event>::const_iterator i = handler.get().begin(); i != handler.get().end(); i++){
if(i->type == SDL_KEYDOWN){
if (i->key.keysym.sym == SDLK_w){
std::cout << "You pressed the W key\n";
}
}
}
This is a simplified version of my eventHandler class
class EventHandler {
public:
bool quit;
SDL_Event event;
SDL_Window* window;
SDL_Renderer* render;
int width;
int height;
std::vector<SDL_Event> eventList;
EventHandler()
{
//Iniliaze SDL
if (SDL_Init(SDL_INIT_EVERYTHING) != 0){
std::cout << "SDL Error: " << SDL_GetError();
}
window = SDL_CreateWindow("Fallen Planets", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1024, 1080, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
if (window == NULL){
std::cout << "SDL Error: " << SDL_GetError();
SDL_Quit();
quit = true;
}
render = SDL_CreateRenderer(window, -1, 0);
if (render == NULL){
SDL_DestroyWindow(window);
std::cout << "SDL Error: " << SDL_GetError();
SDL_Quit();
}
//Initialize quit bool
quit = false;
}
void handle(){
while (SDL_PollEvent(&event)){
eventList.push_back(event); // This adds the events to the eventList
if (event.type == SDL_QUIT){
SDL_DestroyWindow(window);
SDL_DestroyRenderer(render);
TTF_Quit();
SDL_Quit();
quit = true;
}
if (event.window.event == SDL_WINDOWEVENT_RESIZED){
width = SDL_GetWindowSurface(window)->w;
height = SDL_GetWindowSurface(window)->h;
}
//Update Mouse State
SDL_GetMouseState(&mouseX, &mouseY);
for (std::vector<SDL_Event>::const_iterator i = eventList.begin(); i != eventList.end(); ++i){
if (i->type == SDL_KEYDOWN){
if (i->key.keysym.sym == SDLK_w){
std::cout << "w key pressed\n";
}
}
}
}
std::vector<SDL_Event> get(){
return eventList;
}
void flush_events(){
eventList.clear();
}
};
The most effective way is to have some functions that are called for each event, whether that be mouse movement, mouse clicks, window resizes ect, and those should be handled in your main event loop by calling while (SDL_PollEvent(&event)) then have some if statements for each event and call the corresponding functions when those if statements are reached. SDL does this because you may have more then 1 event at the same time, ie, moving the mouse while pressing 'w' like any fps game. You can (im guessing by your example) just appending them to a vector within that event polling loop but then that defeats the purpose of having it loop through the events as youll just have to end up doing that to your vector anyway. You should just make some API variables that are set to some value when a specific event occurs, for example, instead of having functions, just have static variables such as static unsigned int mousePosition[2] and set [0] to the x mouse position an [1] to the y mouse position and then use that somewhere in the program when you need the mouse coordinates, or just even static SDL_Point mousePosition = {x, y} in that example.
Eg: (with a singleton class)
#pragma once
#ifndef EVENTS_H
#define EVENTS_H
#include <iostream>
#include <string>
#include <SDL2/SDL.h>
class Events ///Singleton
{
public:
Events(const Events&) = delete;
Events(Events&&) = delete;
Events& operator=(const Events&) = delete;
Events& operator=(Events&&) = delete;
static const bool& Display_Changed_Size();
///Mouse
static const SDL_Point& Mouse_Pos();
static const bool& Scrolled_Down();
static const bool& Scrolled_Up();
///Keyboard
static const std::string& Get_Text_Input();
static const bool& Pasted_Text();
static const bool& Copied_Text();
static const bool& Backspace();
private:
Events();
static Events& Get_Instance();
///Allow Main to access private members. Works well, one instance, only called once for those functions too. in Main
friend class Main;
///For event handling
static void Event_Loop();
///For event handling
static void Reset_Events();
///For quitting, used main only
static const bool& Quit_Application();
///For Event_Loop()
int eventLoopCounter = 0; ///To ensure Event_Loop() doesn't get used twice in the same loop
SDL_Event event;
bool m_quit = false;
bool m_Display_Changed_Size = false;
///Mouse
SDL_Point m_Mouse_Pos = {0,0};
bool m_Scrolled_Up = false;
bool m_Scrolled_Down = false;
///Keyboard
std::string m_Text_Input = "";
bool m_Copied_Text = false;
bool m_Pasted_Text = false;
bool m_Backspace = false;
};
#endif // EVENTS_H
and the .cpp
#include "events.h"
Events::Events()
{
std::cout << "Events constructor called\n";
}
Events& Events::Get_Instance()
{
static Events instance;
return instance;
}
void Events::Event_Loop()
{
if (Get_Instance().eventLoopCounter == 0)
{
Get_Instance().eventLoopCounter += 1;
while (SDL_PollEvent(&Get_Instance().event) != 0)
{
if (Get_Instance().event.type == SDL_QUIT)
{
Get_Instance().m_quit = true;
break;
}
if (Get_Instance().event.type == SDL_WINDOWEVENT){
if(Get_Instance().event.window.event == SDL_WINDOWEVENT_RESIZED) {
Get_Instance().m_Display_Changed_Size = true;
}
}
///Mouse
if (Get_Instance().event.type == SDL_MOUSEMOTION)
{
Get_Instance().m_Mouse_Pos = {Get_Instance().event.motion.x, Get_Instance().event.motion.y};
}
if (Get_Instance().event.type == SDL_MOUSEWHEEL){
if (Get_Instance().event.wheel.y > 0){ ///Scrolling up here
Get_Instance().m_Scrolled_Up = true;
}
if (Get_Instance().event.wheel.y < 0){ ///Scrolling down here
Get_Instance().m_Scrolled_Down = true;
}
}
///Keyboard
if (Get_Instance().event.type == SDL_TEXTINPUT)
{
Get_Instance().m_Text_Input = Get_Instance().event.text.text;
break; ///Break here for multiple key presses registered at once
}
///Keydown
if (Get_Instance().event.type == SDL_KEYDOWN)
{
///Handle copy
if( Get_Instance().event.key.keysym.sym == SDLK_c && SDL_GetModState() & KMOD_CTRL )
{
Get_Instance().m_Copied_Text = true;
}
///Handle paste
if( Get_Instance().event.key.keysym.sym == SDLK_v && SDL_GetModState() & KMOD_CTRL )
{
Get_Instance().m_Pasted_Text = true;
}
if (Get_Instance().event.key.keysym.sym == SDLK_BACKSPACE)
{
Get_Instance().m_Backspace = true;
}
}
}
}
else
{
std::cout << "Called Events::Event_Loop(); more than once\n";
}
}
void Events::Reset_Events()
{
Get_Instance().eventLoopCounter = 0;
Get_Instance().m_quit = false;
Get_Instance().m_Display_Changed_Size = false;
///Mouse
Get_Instance().m_Scrolled_Down = false;
Get_Instance().m_Scrolled_Up = false;
///Keyboard
Get_Instance().m_Text_Input = "";
Get_Instance().m_Pasted_Text = false;
Get_Instance().m_Copied_Text = false;
Get_Instance().m_Backspace = false;
}
const bool& Events::Quit_Application()
{
return Get_Instance().m_quit;
}
const bool& Events::Display_Changed_Size()
{
return Get_Instance().m_Display_Changed_Size;
}
///Mouse
const SDL_Point& Events::Mouse_Pos()
{
return Get_Instance().m_Mouse_Pos;
}
const bool& Events::Scrolled_Down()
{
return Get_Instance().m_Scrolled_Down;
}
const bool& Events::Scrolled_Up()
{
return Get_Instance().m_Scrolled_Up;
}
///Keyboard
const std::string& Events::Get_Text_Input()
{
return Get_Instance().m_Text_Input;
}
const bool& Events::Pasted_Text()
{
return Get_Instance().m_Pasted_Text;
}
const bool& Events::Copied_Text()
{
return Get_Instance().m_Copied_Text;
}
const bool& Events::Backspace()
{
return Get_Instance().m_Backspace;
}
I understand its alot of code but this type of implementation is what I use when I use SDL2, its not prone to any errors as its just a singleton, noone can instantiate and hence modify members. I would modify the line of friend class Main since its there so my main loop class can call the private functions. Even something like friend int main(int argc, char* argv[]); so the int main() can call it or something as such. In order to use it, just include the header "events.h" in anywhere you need events to be called
Usage in mainloop, assuming Mainloop is a function or class and is a friend of Events, just change the existing friend code in the header
Mainloop:
Events::Event_Loop();
.. Code
Events::Reset_Events();
In other files that need events:
if ( SDL_PointInRect( &Events::Mouse_Pos(), &rct) ) {} //example
Related
Pressing CTRL+C while in the terminal sometimes fails to end the program:
int main(int argc, char *argv[]) {
DrawingWindow window = DrawingWindow(WIDTH, HEIGHT, false);
SDL_Event event;
while(true) {
if (window.pollForInputEvents(event)) handleEvent(event);
draw();
window.renderFrame();
}
}
}
bool DrawingWindow::pollForInputEvents(SDL_Event &event) {
if (SDL_PollEvent(&event)) {
if ((event.type == SDL_QUIT) || ((event.type == SDL_KEYDOWN) && (event.key.keysym.sym == SDLK_ESCAPE))) {
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
printMessageAndQuit("Exiting", nullptr);
}
SDL_Event dummy;
// Clear the event queue by getting all available events
// This seems like bad practice (because it will skip some events) however preventing backlog is paramount !
while (SDL_PollEvent(&dummy));
return true;
}
return false;
}
void printMessageAndQuit(const std::string &message, const char *error) {
if (error == nullptr) {
std::cout << message << std::endl;
exit(0);
} else {
std::cout << message << " " << error << std::endl;
exit(1);
}
}
When CTRL+C succeeds (which is most of the time), it does print "Exiting".
I don't know what handleEvent supposed to do, and your code have braces mismatch so looks like it isn't even your actual code. As written, pollForInputEvents fetches one event, checks if it could be considered a quit condition, and then discards all other events in the queue regardless. If event you're looking for happens to be non-first (e.g. as first event you may have keydown of 'ctrl' or mouse move, or window close event, ...), then it never gets processed. handleEvent has the same problem - it gets only first event in frame.
Generally speaking, event processing have to happen in a fast non-blocking loop, which handles events faster than new events are getting in. Processing only one event per frame is a not fast enough, and there is absolutely no reason to process only one event. Events gets accumulated in a queue, you process all events, draw your image frame (potentially slow), present on screen (potentially blocking on vsync), then repeat.
It looks counterintuitive that your event processing is split into two functions.
As I don't see the rest of your code, with "minimal" changes it should be something like:
#include <SDL2/SDL.h>
#include <iostream>
#define WIDTH 640
#define HEIGHT 480
static void handleEvent(SDL_Event &event) {}
static void draw(SDL_Renderer *renderer) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
}
class DrawingWindow {
public:
DrawingWindow(int width, int height, bool fs) {
SDL_Init(SDL_INIT_VIDEO);
window = SDL_CreateWindow("test",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
width, height, 0);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);
}
bool pollForInputEvents(SDL_Event &event);
void renderFrame() {
SDL_RenderPresent(renderer);
}
SDL_Window *window;
SDL_Renderer *renderer;
};
void printMessageAndQuit(const std::string &message, const char *error) {
if (error == nullptr) {
std::cout << message << std::endl;
exit(0);
} else {
std::cout << message << " " << error << std::endl;
exit(1);
}
}
bool DrawingWindow::pollForInputEvents(SDL_Event &event) {
if (SDL_PollEvent(&event)) {
if ((event.type == SDL_QUIT) || ((event.type == SDL_KEYDOWN) && (event.key.keysym.sym == SDLK_ESCAPE))) {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
printMessageAndQuit("Exiting", nullptr);
}
return true;
}
return false;
}
int main(int argc, char *argv[]) {
DrawingWindow window = DrawingWindow(WIDTH, HEIGHT, false);
SDL_Event event;
while(true) {
while(window.pollForInputEvents(event)) handleEvent(event);
draw(window.renderer);
window.renderFrame();
}
}
I want to time text input in SDL2 to not spam it at 1000 key presses per second, rather the standard which is like ~33kps.
main.cpp
#include <iostream>
#include <SDL2/SDL.h>
#include "main.h"
void Main::Init()
{
std::cout << "Main Init called\n";
SDL_Init(SDL_INIT_VIDEO);
Get_Instance().window = SDL_CreateWindow("Program", 0, 30, 1280, 720, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE );
Get_Instance().renderer = SDL_CreateRenderer(Get_Instance().window, -1, SDL_RENDERER_ACCELERATED );
Get_Instance().running = true;
}
void Main::Free()
{
SDL_DestroyRenderer(Get_Instance().renderer);
SDL_DestroyWindow(Get_Instance().window);
SDL_Quit();
std::cout << "Main Free called\n";
}
void Main::Mainloop()
{
Get_Instance(); ///To initialize constructor
if (Get_Instance().mainloopInstanceBlocker == 'C')
{
Get_Instance().mainloopInstanceBlocker = 'B'; ///Begins at I (initialized), then C (constructed) then B (began)
///It works as it begins as constructed, then does the main loop, after set to B, won't enter again.
Get_Instance().Init();
SDL_Event event;
SDL_StartTextInput();
while (Get_Instance().running)
{
///Poll events
SDL_PollEvent(&event);
///To quit program
if ( event.type == SDL_QUIT ){
Get_Instance().running = false;
break;
}
///Clear display to color
SDL_SetRenderDrawColor(Get_Instance().renderer, 0,255,0,255);
SDL_RenderClear(Get_Instance().renderer);
Get_Instance().m_Main_Loop.Mainloop( Get_Instance().window, Get_Instance().renderer, &event );
SDL_RenderPresent(Get_Instance().renderer);
}
SDL_StopTextInput();
Get_Instance().Free();
}
}
int main(int argc, char* argv[])
{
Main::Mainloop();
return 0;
}
main.h
#ifndef MAIN_H
#define MAIN_H
#include "main_loop.h"
class Main
{
public:
Main(const Main&) = delete;
Main(Main&&) = delete;
Main& operator=(const Main&) = delete;
Main& operator=(Main&&) = delete;
static void Mainloop();
private:
Main()
{
std::cout << "Main constructor called\n";
mainloopInstanceBlocker = 'C';
}
static Main& Get_Instance()
{
static Main instance;
return instance;
}
static void Init();
static void Free();
Main_Loop m_Main_Loop;
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
bool running = false;
char mainloopInstanceBlocker = 'I';
};
#endif // MAIN_H
main_loop.h
#ifndef MAIN_LOOP_H
#define MAIN_LOOP_H
#include <iostream>
#include <string>
#include <SDL2/SDL.h>
class Main_Loop
{
public:
Main_Loop();
~Main_Loop();
void Mainloop(SDL_Window* window, SDL_Renderer* renderer, SDL_Event* event);
};
#endif // MAIN_LOOP_H
main_loop.cpp
#include "main_loop.h"
Main_Loop::Main_Loop()
{
}
void Main_Loop::Mainloop(SDL_Window* window, SDL_Renderer* renderer, SDL_Event* event)
{
if (event->type == SDL_TEXTINPUT)
{
std::cout << event->text.text << std::endl;
}
}
Main_Loop::~Main_Loop()
{
}
Your problem is
SDL_PollEvent(&event);
SDL_PollEvent returns 0 if there are no more events in the queue, but in that case it doesn't write anything into passed event structure. Since you no longer check return value of PollEvent, you operate on stale data left by last event you've had. At the very least do something like
if(!SDL_PollEvent(&event)) event.type = 0;
Your Mainloop function is questionable - does it imply there could be only one event per frame? What'd you do if there are multiple events happened in single frame? I'd suggest separating event processing and rendering - i.e. make event processing function that handles entire event queue and reacts to events by modifying your data, and separate rendering function that only does rendering.
I'm trying to implement Mouse class to handle any related issues to mouse actions. Everything works fine except detecting a mouse button being hold. The library doesn't provide this functionality for the mouse yet there is an option for keyboard event (i.e. through REPEAT flag). I have to implement it manually. The first and simple approach is to set a flag for press and release button
bool Mouse::isRightDown()
{
if (m_button == GLFW_MOUSE_BUTTON_RIGHT && m_action == GLFW_PRESS){
m_isRightHold = true;
...
}
bool Mouse::isRightUp()
{
if (m_button == GLFW_MOUSE_BUTTON_RIGHT && m_action == GLFW_RELEASE ){
m_isRightHold = false;
...
}
bool Mouse::isRightHold()
{
if ( g_RightFlag ){
...
return true;
}
return false;
}
Now in rendering loop, I can do the following
while(!glfwWindowShouldClose(window)){
glfwPollEvents();
...
// Handle Right Button Mouse
if ( Mouse::Instance()->isRightHold() ){
std::cout << "Right Mouse Button is hold..." << std::endl;
}
...
glfwSwapBuffers(window);
}
But the problem with this approach is the fact that while-loop is faster than human's reaction for releasing the button, therefore, single click will be considered as a hold event. I've considered another approach by updating a global Boolean variable (i.e. g_RightFlag). The variable will be updated every 900 second in an independent thread as follows
while (true){
//std::this_thread::sleep_for(delay);
std::chrono::duration<int, std::ratio<1, 1000>> delay(900);
bool sleep = true;
auto start = std::chrono::system_clock::now();
while(sleep)
{
auto end = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
if ( elapsed.count() > delay.count() ){
sleep = false;
}
}
mtx.lock();
g_RightFlag = m_isRightHold;
g_LefFlag = m_isLeftHold;
mtx.unlock();
}
This solution is better than the first approach but still inconsistent because threads are not synchronized. At some moments, when I do just a single click, the hold event is detected (i.e. in milliseconds). How can I improve my approach to handle Hold Mouse Event?
main.cpp
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "mouse.h"
int main(void)
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(800,600,"LearnOpenGL",nullptr,nullptr);
if( window == nullptr ){
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glewExperimental = GL_TRUE;
if( glewInit() != GLEW_OK ){
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0,0, width, height);
// callback events
//Keyboard Event;
Mouse::Instance();
Mouse::Instance()->init(window);
while(!glfwWindowShouldClose(window)){
glfwPollEvents();
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Handle Right Button Mouse
if ( Mouse::Instance()->isRightDown() ){
std::cout << "Right Mouse Button is pressed..." << std::endl;
}
if ( Mouse::Instance()->isRightUp() ){
std::cout << "Right Mouse Button is released..." << std::endl;
}
if ( Mouse::Instance()->isRightHold() ){
std::cout << "Right Mouse Button is hold..." << std::endl;
}
// Handle Left Button Mouse
if ( Mouse::Instance()->isLeftDown() ){
std::cout << "Left Mouse Button is pressed..." << std::endl;
}
if ( Mouse::Instance()->isLeftUp() ){
std::cout << "Left Mouse Button is released..." << std::endl;
}
if ( Mouse::Instance()->isLeftHold() ){
std::cout << "Left Mouse Button is hold..." << std::endl;
}
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
mouse.h
#ifndef MOUSE_H
#define MOUSE_H
#include <thread>
#include <atomic>
#include <chrono>
#include <GLFW/glfw3.h>
class Mouse
{
public:
static Mouse* Instance(){
if(s_pInstance == NULL)
s_pInstance = new Mouse;
return s_pInstance;
}
void init(GLFWwindow* window);
bool isRightDown();
bool isRightUp();
bool isRightHold();
bool isLeftDown();
bool isLeftUp();
bool isLeftHold();
std::atomic<int> m_button, m_action, m_mode;
private:
static void mouse_button_callback(GLFWwindow* window, int button, int action, int mods);
bool m_isRightHold, m_isLeftHold;
GLFWwindow* m_pWindow;
Mouse();
static Mouse* s_pInstance;
std::thread m_OnHoldThread;
void initThread();
void updateThread();
void update(int b, int a, int m);
};
#endif
mouse.cpp
#include "mouse.h"
#include <iostream>
#include <mutex> // std::mutex
std::mutex mtx;
Mouse* Mouse::s_pInstance = NULL;
bool g_LefFlag(false);
bool g_RightFlag(false);
Mouse::Mouse()
: m_button(-1), m_action(-1), m_mode(-1),
m_isRightHold(false), m_isLeftHold(false)
{
initThread();
}
void Mouse::init(GLFWwindow* window)
{
m_pWindow = window;
glfwSetMouseButtonCallback(window, mouse_button_callback);
}
void Mouse::mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
Mouse::Instance()->update(button, action, mods);
}
void Mouse::initThread()
{
m_OnHoldThread = std::thread(&Mouse::updateThread,this);
}
void Mouse::updateThread()
{
//std::chrono::milliseconds delay(1100);
while (true){
//std::this_thread::sleep_for(delay);
std::chrono::duration<int, std::ratio<1, 1000>> delay(900);
bool sleep = true;
auto start = std::chrono::system_clock::now();
while(sleep)
{
auto end = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
if ( elapsed.count() > delay.count() ){
sleep = false;
}
}
mtx.lock();
g_RightFlag = m_isRightHold;
g_LefFlag = m_isLeftHold;
mtx.unlock();
}
}
bool Mouse::isRightDown()
{
if (m_button == GLFW_MOUSE_BUTTON_RIGHT && m_action == GLFW_PRESS){
m_isRightHold = true;
m_button = -1;
m_action = -1;
m_mode = -1;
return true;
}
return false;
}
bool Mouse::isRightUp()
{
if (m_button == GLFW_MOUSE_BUTTON_RIGHT && m_action == GLFW_RELEASE ){
m_isRightHold = false;
mtx.lock();
g_RightFlag = m_isRightHold;
mtx.unlock();
m_button = -1;
m_action = -1;
m_mode = -1;
return true;
}
return false;
}
bool Mouse::isRightHold()
{
if ( g_RightFlag ){
m_button = -1;
m_action = -1;
m_mode = -1;
return true;
}
return false;
}
bool Mouse::isLeftDown()
{
if (m_button == GLFW_MOUSE_BUTTON_LEFT && m_action == GLFW_PRESS){
m_isLeftHold = true;
m_button = -1;
m_action = -1;
m_mode = -1;
return true;
}
return false;
}
bool Mouse::isLeftUp()
{
if (m_button == GLFW_MOUSE_BUTTON_LEFT && m_action == GLFW_RELEASE ){
m_isLeftHold = false;
mtx.lock();
g_LefFlag = m_isLeftHold;
mtx.unlock();
m_button = -1;
m_action = -1;
m_mode = -1;
return true;
}
return false;
}
bool Mouse::isLeftHold()
{
if ( g_LefFlag ){
m_button = -1;
m_action = -1;
m_mode = -1;
return true;
}
return false;
}
void Mouse::update(int b, int a, int m)
{
m_button = b;
m_action = a;
m_mode = m;
}
Why don't you just get a high-resolution time of m_isRightHold = true; event and compare time period elapsed since then at every main loop iteration while m_isRightHold continues to be true to determine that mouse button has been held for long enough to consider a click or hold to happen?
My OpenGL function glutSpecialFunc requires a void function pointer with 3 int parameters. This is easy to do with global functions by simply
glutSpecialFunc(processArrowKeys);
But i want to make it point to a member-function in a struct in another file like so:
inputs.h
struct Keyboard {
bool leftMouseDown;
bool keyRight, keyLeft, keyUp, keyDown;
Keyboard() : leftMouseDown(false), keyRight(false), keyLeft(false), keyUp(false), keyDown(false) {
}
void Keyboard::processArrowKeys(int key, int x, int y) {
// process key strokes
if (key == GLUT_KEY_RIGHT) {
keyRight = true;
keyLeft = false;
keyUp = false;
keyDown = false;
}
else if (key == GLUT_KEY_LEFT) {
keyRight = false;
keyLeft = true;
keyUp = false;
keyDown = false;
}
else if (key == GLUT_KEY_UP) {
keyRight = false;
keyLeft = false;
keyUp = true;
keyDown = false;
}
else if (key == GLUT_KEY_DOWN) {
keyRight = false;
keyLeft = false;
keyUp = false;
keyDown = true;
}
}
};
main.cpp
#include "inputs.h"
Keyboard keyboard;
...
int main(int argc, char **argv) {
...
// callbacks
glutDisplayFunc(displayWindow);
glutReshapeFunc(reshapeWindow);
glutIdleFunc(updateScene);
glutSpecialFunc(&keyboard.processArrowKeys); // compiler error: '&': illegal operation on bound member function expression
glutMouseFunc(mouseButton);
glutMotionFunc(mouseMove);
glutMainLoop();
return 0;
}
Any idea how to solve this compiler error?
You can't do that directly because member functions have an implicit this pointer which has to be passed somehow through the call-chain. Instead you create an intermediary function that will forward the call to the right place:
void processArrowKeys(int key, int x, int y) {
keyboard.processArrowKeys(key, x, y);
}
int main() {
// ...
glutSpecialFunc(processArrowKeys);
// ...
}
keyboard in your code appears to be global so it is going to work. If you ever want to have a non-global state, then you will have to use a user-data pointer that some GLUT implementations support as an extension (including FreeGLUT and OpenGLUT):
void processArrowKeys(int key, int x, int y) {
Keyboard *k = (Keyboard*)glutGetWindowData();
k->processArrowKeys(key, x, y);
}
int main() {
// ...
glutSpecialFunc(processArrowKeys);
glutSetWindowData(&keyboard);
// ...
}
This is a problem that haunts me for years.
Here's my game.h and game.cpp files:
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();
}
Now here's my main file:
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;
}
As you can see, I have a std::map that stores key states (One for each arrow of the keyboard), and then I have a procedure called check_states(), that iterate over all the states at each main loop, and set them to true if their respective arrows are pressed (down), and to false when they are released.
The problem:
If I press UP and keep it holding, and then I press LEFT (Without releasing the UP key), the player will move diagonally. Nevertheless, if I release the LEFT, while still holding the UP key, the player will stop, and the state for UP will be true (And I see this because I'm couting it).
Theory
The al_wait_for_event() waits until the event queue specified is non-empty (Which means that when I press the UP key, it copies the event to e, and then when I press the LEFT, it must cancel UP and assign a new event to e, thus creating the unpleasant LAG when I press more than one key at once). Having that in mind, I've concluded: Well, I could have at least FIVE separate event_queues, and FIVE different "event objects". I've managed to create a vector of event_queues and of events and to iterate over both of them at each main loop while passing each event to its respective event_queue AND THE PROBLEM PERSISTED.
SO, how can I press more than one key in real-time? By real-time I mean real real-time, without lags, without any key canceling each other. The answer is key states? Why? How can I do it using events? Is it possible at all?.
EDIT:
I've changed my control_actor() procedure in order to use al_key_down() instead of checking for events, here's its code:
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;
}
}
And the new 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;
}
The post on the allegro forums linked in the comment is from 2002, and that code does not work anymore on Allegro 5. So I've checked the docs, and I'll tell you: THE PROBLEM PERSISTED. The EXACT same thing happens. One arrow cancels the other and the player stops moving for a while, as soon as I press another arrow at the same time.
The Basic Keyboard Example on the allegro wiki may be of more help than that old post.
There is no need to manage multiple event queues here. Every key press and release should get pushed into the queue -- you just need to make sure you process every event in the queue.
Basically, you want a main loop that:
Processes every event currently in the queue
Updates the game state
Redraws the screen
Here is an example I drafted up:
#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;
}
Note the while(!al_is_event_queue_empty(event_queue)). This ensures that we don't miss any event before moving on to the update loop.
If you try running the example, the circle should respond appropriately to any combination of the WASD keys.
If you hold S+D, it will move diagonally right and down.
Release S, and it will continue moving right, but not down.
Hope this helps!