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?
Related
I'm having an issue with creating a Vulkan surface for a GLFW Window in Linux. Obviously you need to create a window without a client API. But somehow GLFW ignores that window hint?
GLFW spits out the following error message:
GLFW Error 65540: Vulkan: Window surface creation requires the window to have the client API set to GLFW_NO_API
I have no clue why this is not working, it seems like GLFW itself is broken.
N.B. the function glfwWindowShouldClose() also seems to ignore close events? Had to make a small workaround.
This is the complete program:
#include <vulkan/vulkan.h>
#include <iostream>
#include <stdexcept>
#include <cstdlib>
#include <vector>
#include <cstring>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
const uint32_t WINDOW_WIDTH = 1024;
const uint32_t WINDOW_HEIGHT = 768;
// NOTE: somehow GLFW's glfwWindowShouldClose() never returns true?!
// So, we create this global for intended behaviour.
bool RUNNING = false;
std::vector<const char*> validation_layers = {
"VK_LAYER_KHRONOS_validation"
};
#if NDEBUG
const bool enable_validation_layers = false;
#else
const bool enable_validation_layers = true;
#endif
struct Application {
GLFWwindow *window;
VkInstance vk_instance;
VkPhysicalDevice vk_physical_device = VK_NULL_HANDLE;
VkDevice vk_device;
VkQueue vk_graphics_queue;
VkSurfaceKHR vk_surface;
};
void glfwErrorCallback(int code, const char* description) {
std::cerr << "GLFW Error " << code << ": " << description << std::endl;
}
bool checkValidationLayerSupport() {
uint32_t count;
vkEnumerateInstanceLayerProperties(&count, nullptr);
std::vector<VkLayerProperties> available_layers(count);
vkEnumerateInstanceLayerProperties(&count, available_layers.data());
for (const auto &layer_name : validation_layers) {
bool found = false;
for (const auto &layer_properties : available_layers) {
if (strcmp(layer_name, layer_properties.layerName)) {
found = true;
break;
}
}
if (!found) return false;
}
return true;
}
void windowCloseCallback(GLFWwindow *window) {
RUNNING = false;
}
void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) {
// nothing
}
static void createVKInstance(VkInstance *instance) {
if ( enable_validation_layers && !checkValidationLayerSupport() ) {
throw std::runtime_error("Validation layer requested, but not available!");
}
VkApplicationInfo app_info = {};
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
app_info.pApplicationName = "Hello Sailor!";
app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
app_info.pEngineName = " No engine";
app_info.engineVersion = VK_MAKE_VERSION(1, 0 ,0);
app_info.apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
create_info.pApplicationInfo = &app_info;
uint32_t glfw_extension_count = 0;
const char **glfw_extensions;
glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count);
create_info.enabledExtensionCount = glfw_extension_count;
create_info.ppEnabledExtensionNames = glfw_extensions;
if (enable_validation_layers) {
create_info.enabledLayerCount = (uint32_t) validation_layers.size();
create_info.ppEnabledLayerNames = validation_layers.data();
}
else {
create_info.enabledLayerCount = 0;
}
auto result = vkCreateInstance(&create_info, nullptr, instance);
if (result != VK_SUCCESS) {
throw std::runtime_error(" Failed to create Vulkan instance!\n");
}
#if 0
uint32_t p_count = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &p_count, nullptr);
std::vector<VkExtensionProperties> props(p_count);
vkEnumerateInstanceExtensionProperties(nullptr, &p_count, props.data());
for (auto &prop : props) {
std::cout << prop.extensionName << std::endl;
}
#endif
}
#include <optional>
struct QueueFamilyIndices {
std::optional<uint32_t> graphics_family;
};
static bool isComplete(QueueFamilyIndices *indices) {
return indices->graphics_family.has_value();
}
static QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
QueueFamilyIndices indices;
uint32_t count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &count, nullptr);
std::vector<VkQueueFamilyProperties> queue_families(count);
vkGetPhysicalDeviceQueueFamilyProperties(device, &count, queue_families.data());
int i = 0;
for (const auto &queue_family : queue_families) {
if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphics_family = i;
}
if (isComplete(&indices)) break;
i++;
}
return indices;
}
static bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
return isComplete(&indices);
}
static void pickPhysicalDevice(VkInstance instance, VkPhysicalDevice *physical_device) {
uint32_t count = 0;
vkEnumeratePhysicalDevices(instance, &count, nullptr);
if (count == 0) {
throw std::runtime_error("Failed to find GPUs with Vulkan support!");
}
std::vector<VkPhysicalDevice> devices(count);
vkEnumeratePhysicalDevices(instance, &count, devices.data());
for (const auto &device : devices) {
if (isDeviceSuitable(device)) {
*physical_device = device;
break;
}
}
if (*physical_device == VK_NULL_HANDLE) {
throw std::runtime_error("Failed to find suitable GPU!");
}
}
static void createLogicalDevice(VkPhysicalDevice physical_device, VkDevice *device, VkQueue *graphics_queue) {
QueueFamilyIndices indices = findQueueFamilies(physical_device);
if (!isComplete(&indices)) {
throw std::runtime_error("No queue family indices!");
}
VkDeviceQueueCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
create_info.queueFamilyIndex = indices.graphics_family.value();
create_info.queueCount = 1;
float queue_priority = 1.0f;
create_info.pQueuePriorities = &queue_priority;
VkPhysicalDeviceFeatures features = {};
VkDeviceCreateInfo device_create_info = {};
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_create_info.pQueueCreateInfos = &create_info;
device_create_info.queueCreateInfoCount = 1;
device_create_info.pEnabledFeatures = &features;
device_create_info.enabledExtensionCount = 0;
if (enable_validation_layers) {
device_create_info.enabledLayerCount = (uint32_t) validation_layers.size();
device_create_info.ppEnabledLayerNames = validation_layers.data();
}
else {
device_create_info.enabledLayerCount = 0;
}
if (vkCreateDevice(physical_device, &device_create_info, nullptr, device) != VK_SUCCESS) {
throw std::runtime_error("Failed to create logical device!");
}
vkGetDeviceQueue(*device, indices.graphics_family.value(), 0, graphics_queue);
}
static void createSurface(GLFWwindow *window, VkInstance instance, VkSurfaceKHR *surface) {
auto result = glfwCreateWindowSurface(instance, window, nullptr, surface);
if (result != VK_SUCCESS) {
throw std::runtime_error("Failed to create window surface!");
}
}
static void initVulkan(/*GLFWwindow *window, VkInstance *instance, VkPhysicalDevice *physical_device, VkDevice *device, VkQueue *graphics_queue, VkSurfaceKHR *surface */
Application *app
) {
createVKInstance(&app->vk_instance);
createSurface(app->window, app->vk_instance, &app->vk_surface);
pickPhysicalDevice(app->vk_instance, &app->vk_physical_device);
createLogicalDevice(app->vk_physical_device, &app->vk_device, &app->vk_graphics_queue);
}
static void initWindow(GLFWwindow *window) {
glfwInit();
glfwSetErrorCallback(glfwErrorCallback);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Vulkan", nullptr, nullptr);
if (window == NULL) {
throw std::runtime_error("Failed to create window!\n");
}
else {
printf("Created window.\n");
}
glfwSetWindowCloseCallback(window, windowCloseCallback);
glfwSetKeyCallback(window, keyCallback);
RUNNING = true;
}
static void mainLoop(GLFWwindow *window) {
while (RUNNING) {
glfwPollEvents();
}
}
static void cleanUp(GLFWwindow *window, VkInstance instance, VkDevice device, VkSurfaceKHR surface) {
printf("Cleaning up!\n");
glfwDestroyWindow(window);
glfwTerminate();
vkDestroySurfaceKHR(instance, surface, nullptr);
vkDestroyInstance(instance, nullptr);
vkDestroyDevice(device, nullptr);
}
static void runApplication(Application *app) {
initWindow(app->window);
initVulkan(app);
mainLoop(app->window);
cleanUp(app->window, app->vk_instance, app->vk_device, app->vk_surface);
}
int main() {
Application app;
try {
runApplication(&app);
}
catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
You fail to store the GLFW Window handle in your Application struct, because you pass it by value, not by reference [1]
glfwInit returns error code, which must be checked, and\or callback should be setup before the call.
glfwVulkanSupported should be called.
You have sequencing error with RUNNING variable. It could conceivably be set true even if the window was closed.
You enable layers, but no reporting extension, so no Vulkan problems would be reported
1:
struct Application {
GLFWwindow *window;
};
// passing GLFWwindow* by value (local copy):
static void initWindow(GLFWwindow *window) {
// writing to a local GLFWwindow*,
// which will be destroyed at end of scope
window = glfwCreateWindow(/*snip*/);
}
// passing GLFWwindow* by value, not reference:
initWindow(app->window);
// reading\dereferencing uninitialized variable app->window:
createSurface(app->window, app->vk_instance, &app->vk_surface);
I was working on changing sprite in my SFML project but it just doesn't want to wark. I tried everything - pointers, refrences and normal string objects. You can see in debug console i printed out this->path that containted a path to file to change but when it left the function this->path was null (empty "" string). Can you please help me? Here's the code:
Player.cpp
#include "Player.h"
#include <iostream>
Player::Player(std::string path)
{
this->path = path;
texture.loadFromFile(path);
sprite.setTexture(texture);
sprite.setPosition(500.f, 700.f);
}
void Player::update()
{
// Movement //
//if (sf::Keyboard::isKeyPressed(sf::Keyboard::W))
//sprite.move(0.f, -velocity);
//if (sf::Keyboard::isKeyPressed(sf::Keyboard::S))
//sprite.move(0.f, velocity);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::A))
sprite.move(-velocity, 0.f);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::D))
sprite.move(velocity, 0.f);
// Movement //
float szerokosc = sprite.getGlobalBounds().width;
float wysokosc = sprite.getGlobalBounds().height;
// Collision with screen //
// Left
if (sprite.getPosition().x < 0.f)
sprite.setPosition(0.f, sprite.getPosition().y);
// Top
if (sprite.getPosition().y < 0.f)
sprite.setPosition(sprite.getPosition().x, 0.f);
// Right
if (sprite.getPosition().x + szerokosc > 1000)
sprite.setPosition(1000 - szerokosc, sprite.getPosition().y);
// Bottom
if (sprite.getPosition().y + wysokosc > 800)
sprite.setPosition(sprite.getPosition().x, 800 - wysokosc);
// Collision with screen //
}
sf::Sprite Player::getSprite()
{
return sprite;
}
bool Player::collides(Food obj)
{
if (sprite.getGlobalBounds().intersects(obj.getSprite().getGlobalBounds()))
return true;
else
return false;
}
void Player::changeSkin(std::string path)
{
this->path = path;
std::cout << "changeSkin(): *this->path" << this->path << std::endl;
std::cout << "changeSkin(): *path" << path << std::endl;
texture.loadFromFile(path);
sprite.setTexture(texture);
}
void Player::updateSkin()
{
std::cout << "updateSkin() *this->path: " << this->path << std::endl;
std::cout << "updateSkin() *path: " << path << std::endl;
}
Player::~Player()
{
//delete texture;
//delete sprite;
}
Player.h
#pragma once
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <time.h>
#include "Food.h"
#include "ShopItem.h"
class Player
{
sf::Texture texture;
sf::Sprite sprite;
std::string path;
bool has_skin = false;
float velocity = 5.f;
public:
explicit Player(std::string path);
~Player();
void update();
sf::Sprite getSprite();
bool collides(Food obj);
void changeSkin(std::string path);
void updateSkin();
};
main.cpp - main() has Player::updateSkin() call
int main()
{
RenderWindow window(VideoMode(1000, 800), "Tomczyszyn Eater v0.21 BETA");
window.setFramerateLimit(60);
srand(time(nullptr));
Texture bg_text;
bg_text.loadFromFile("Assets/Textures/bg.png");
Sprite bg{ bg_text };
Player player{ "Assets/Textures/player.png" };
time_t start{};
Event event;
Intro* intro = new Intro{window};
MainMenu* menu = new MainMenu{&window};
Shop shop(&window);
Game game{player, &window};
bool intro_deleted{ false };
bool menu_deleted{ false };
bool game_started{ false };
int menuState{ 2 };
while (window.isOpen())
{
while (window.pollEvent(event))
{
if (event.type == Event::EventType::Closed)
window.close();
if (Keyboard::isKeyPressed(Keyboard::Space) && game.gameRunning() == false)
{
start = time(nullptr);
game.runGame();
}
else if (Keyboard::isKeyPressed(Keyboard::Escape) && game.gameRunning() == false)
{
menuState = 0;
}
}
window.clear();
if (game.gameRunning() == true && menuState == 1) // Main Game
{
if (game_started == false)
{
start = time(nullptr);
game_started = true;
}
if (intro_deleted == false)
{
delete intro;
intro_deleted = true;
}
if (menu_deleted == false)
{
delete menu;
menu_deleted = true;
}
window.draw(bg);
game.drawMoney();
player.update();
window.draw(player.getSprite());
// Player::updateSkin() - source of the problem
player.updateSkin();
game.update();
game.draw();
if (time(nullptr) - start == 2)
{
start = time(nullptr);
int liczba = rand() % 6 + 1;
string file_name{ "Assets/Textures/food" + to_string(liczba) + ".png" };
Food* newFood = new Food{file_name};
game.spawn(newFood);
}
game.catched();
game.fell();
}
else if (menuState == 0) // Menu
{
if (menu_deleted == true) menu = new MainMenu{ &window };
menu->draw();
menu->update(menuState);
menu_deleted = false;
}
else if (menuState == -1) // Intro
{
start = time(nullptr);
intro->play();
if (intro->intro_started() == true) menuState = 0;
}
else if (menuState == 2) // Shop
{
shop.draw();
shop.backIfClicked(menuState);
shop.checkIfBought(player, game.balance());
game.drawMoney();
}
else
{
game.drawDeathScreen();
}
window.display();
}
}
Shop.cpp - checkIfBought() has Player::changeSkin() call
void Shop::checkIfBought(Player player, int& money)
{
for (int i = 0; i < skins.size(); i++)
{
if (isClicking(skins[i].getSprite()) == true)
{
skins[i].buy(money);
player.changeSkin(skins[i].getPath()); //Plaer::changeSkin() here - source of the problem
std::cout << skins[i].getPath() << std::endl;
}
}
for (int i = 0; i < bg_skins.size(); i++)
{
if (isClicking(bg_skins[i].getSprite()) == true)
{
bg_skins[i].buy(money);
player.changeSkin(bg_skins[i].getPath()); //Plaer::changeSkin() here - source of the problem
}
}
}
Debug Console output:
changeSkin(): this->path = Assets/Textures/skin1.png
changeSkin(): path = Assets/Textures/skin1.png
Assets/Textures/skin1.png
updateSkin() this->path = Assets/Textures/player.png
updateSkin() path = Assets/Textures/player.png
Sorry if you didn't understand my english is bad. I really do need help i've spent so much time on fixing this that i just gave up.
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
Currently trying to display a BMP file on a window through SDL 2
This is my main.cpp file:
`#include "pch.h"
#include <iostream>
#include<SDL.h>
using namespace std;
int main(int argc, char* args[])
{
bool run=true;
sdl a;
if (a.init() == false)
{
cout << "SDL not working" << endl;
return 0;
}
else
if (a.screendisplay() == false)
{
cout << "Enable to open the window" << endl;
return 0;
}
else
if (a.loadmedia() == false)
{
cout << "Unable to load the image" << endl << SDL_GetError()<<endl;
return 0;
}
else
a.imageprocessing();
SDL_Delay(2000);
a.quit();
return 0;
}`.
This is my pch.H file,which was mentioned above in the code:
class sdl
{
public:
bool init();
bool screendisplay();
bool quit();
bool loadmedia();
bool imageprocessing();
private:
SDL_Surface *gsurface = NULL;
SDL_Window * Window = NULL;
SDL_Surface * screenSurface = NULL;
};
Pch.cpp file:
#include "pch.h"
using namespace std;
bool sdl::init()
{
bool sucess = true;
if (SDL_Init(SDL_INIT_VIDEO) <0)
{
cout << "SDL not Working properply" << endl;
return false;
}
else return true;
};
bool sdl::screendisplay()
{
bool sucess;
Window = SDL_CreateWindow("SDL Tutorial", 0, 0,640, 280, SDL_WINDOW_SHOWN);
if (Window = NULL)
{
return sucess = false;
}
else
screenSurface= SDL_GetWindowSurface(Window);
return sucess = true;
};
bool sdl::loadmedia()
{
bool success = true;
gsurface = SDL_LoadBMP("asd.bmp");
if (gsurface == NULL)
{
success = false;
}
return success;
};
bool sdl::imageprocessing()
{
bool success = true;
if (SDL_BlitSurface(gsurface, NULL, screenSurface, NULL) < 0)
{
return success = false;
}
else
SDL_UpdateWindowSurface(Window);
SDL_Delay(2000);
return success;
};
bool sdl::quit()
{
bool sucess;
SDL_DestroyWindow(Window);
SDL_Quit();
return sucess = true;
};
When i run this program,the window appears but with no image. What am i doing wrong here?
I think the problem is in your screendisplay() function:
bool sdl::screendisplay()
{
bool sucess;
Window = SDL_CreateWindow("SDL Tutorial", 0, 0,640, 280, SDL_WINDOW_SHOWN);
if (Window = NULL) // <- Right here
{
return sucess = false;
}
else
screenSurface= SDL_GetWindowSurface(Window);
return sucess = true;
};
You're doing an assignment rather than a comparison. You should turn up your compiler warnings so it catches this.
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!