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);
// ...
}
Related
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
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!
I'm working on the motion for my paddles in my PONG clone and the program doesn't want to register SDL_KEYUP/DOWN sometimes. I think I have an idea of why this is but now how to fix it. So the way my game is looped currently I am using a state machine in my main() function that uses a Game class state which manages two paddles and a ball all of which are three different class instances. In side my Game class I run the events/logic/render from all three classes like so
void Game::events()
{
while (SDL_PollEvent(&event))
{
PlayerOne.events();
PlayerTwo.events();
Game_Ball.events();
}
}
void Game::logic()
{
PlayerOne.logic(delta.get_ticks());
PlayerTwo.logic(delta.get_ticks());
Game_Ball.logic();
}
void Game::render()
{
SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0, 0, 0));
PlayerOne.render();
PlayerTwo.render();
Game_Ball.render();
}
And the paddle class movement is managed like so (as fixed in the last question I had(:
void Paddle::events()
{
Uint8 *keystates = SDL_GetKeyState(NULL);
if (event.type == SDL_KEYDOWN)
{
if (player == 1)
{
if (keystates[SDLK_o] == 1)
{
yVel -= 100;
}
if (keystates[SDLK_k] == 1)
{
yVel += 100;
}
}
else
{
if (keystates[SDLK_w] == 1)
{
yVel -= 100;
}
if (keystates[SDLK_s] == 1)
{
yVel += 100;
}
}
}
if (event.type == SDL_KEYUP)
{
if (player == 1)
{
if (keystates[SDLK_o] == 0)
{
yVel += 100;
}
if (keystates[SDLK_k] == 0)
{
yVel -= 100;
}
}
else
{
if (keystates[SDLK_w] == 0)
{
yVel += 100;
}
if (keystates[SDLK_s] == 0)
{
yVel -= 100;
}
}
}
if (event.type == SDL_QUIT)
{
quit = true;
}
}
The problem here is that sometimes, if not most of the time, the keys don't react to move the player paddles up and down. but when they do it's less than half the time. I think this is because there is only a 1/3 chance that the program polls when a key is released or pressed, sometimes resulting in no change in the position. Basically, if PLAYERONE hits a key while Game is running PlayerTwo.events() it won't register the key press/release. Could this be a problem in the way I'm implementing the game loop in my main function?
int main(int argc, char* args[])
{
//init SDL
if (init() == false)
{
return 1;
}
//load everything
if (load_files() == false)
{
return 1;
}
delta.start();
currentState = new Game;
while (quit == false)
{
//handle state events
currentState->events();
// do state logic
currentState->logic();
//timer reset
delta.start();
//change state if needed
change_state();
//render state
currentState->render();
if (SDL_Flip(screen) == -1)
{
return 1;
}
}
clean_up();
return 0;
}
And here are the definitions and constructors
class Paddle
{
public:
int player;
SDL_Surface *sprite = NULL;
float x, y, w, h;
float yVel;
SDL_Rect *clip;
void events();
void logic(Uint32 deltaTicks);
void render();
Paddle();
~Paddle();
};
class Game : public GameState
{
private:
int server;
TTF_Font *score = NULL;
Paddle PlayerOne;
Paddle PlayerTwo;
Ball Game_Ball;
public:
SDL_Rect clip[2];
void events();
void logic();
void render();
Game();
~Game();
};
Game::Game()
{
//RESOURSES
PlayerOne.sprite = load_image("Game_sprite.png");
PlayerTwo.sprite = load_image("Game_sprite.png");
clip[0].x = 0;
clip[0].y = 0;
clip[0].w = 20;
clip[0].h = 20;
clip[1].x = 0;
clip[1].y = 20;
clip[1].w = 20;
clip[1].h = 100;
//PLAYER ONE
PlayerOne.x = 420;
PlayerOne.y = 100;
PlayerOne.w = 60;
PlayerOne.h = 100;
PlayerOne.clip = &clip[1];
PlayerOne.yVel = 0;
PlayerOne.player = 1;
//PLAYER TWO
PlayerTwo.x = 60;
PlayerTwo.y = 100;
PlayerTwo.w = 60;
PlayerTwo.h = 100;
PlayerTwo.clip = &clip[1];
PlayerTwo.yVel = 0;
PlayerTwo.player = 2;
//BALL
Game_Ball.Ball_Bound.x = 190;
Game_Ball.Ball_Bound.y = 190;
Game_Ball.Ball_Bound.w = 60;
Game_Ball.Ball_Bound.h = 60;
Game_Ball.clip = &clip[0];
}
Because that looks pretty clean to me. Other than another game loop implementation, what could I do to fix the keys not registering due to being in a different function? Or is it a loop implementation problem? If it is a loop problem, can you be through or include a reference as to how to fix it?
I don't see anything wrong in this code. However, the very possible cause might be simply limitation of how many keys are pressed on keyboard. Try using shift, ctrl and alt, keys for controls, they usually bypass the limitation.
I'm working on a PONG clone and just on the title screen. I have a class to work the title screen loop of the state machine and it uses very little, only a single sprite and a single true type font message. But when I call the function to render the message onto the SDL_Surface, it throws my program into whack. The error I receive is Unhandled exception at 0x6F4C2A9D (SDL_ttf.dll) in Pong.exe: 0xC0000005: Access violation reading location 0x00000000. Usually this means that I didn't initialize something or didn't define it in the class definition or something, but it all seems in order. So I'll post the code here in hopes that someone sees what's up with the render function or the bits surrounding it.
To be perfectly clear the exception is thrown on this line:
Title_Message = TTF_RenderText_Solid(font, "PONG", color);
//start code
/*CLASSES*/
class GameState
{
public:
virtual void events() = 0;
virtual void logic() = 0;
virtual void render() = 0;
virtual ~GameState(){};
};
class Button
{
public:
SDL_Rect button_clip[2];
SDL_Rect button;
SDL_Surface *button_sprite = NULL;
Button();
};
class Title : public GameState
{
private:
SDL_Surface *Title_Message = NULL;
SDL_Rect *clip;
Button Title_Button;
public:
void events();
void logic();
void render();
Title();
~Title();
};
/*FONTS*/
SDL_Color color = { 255, 255, 255 };
TTF_Font *font = NULL;
bool init()
{
//initialize all SDL subsystems
if (SDL_Init(SDL_INIT_EVERYTHING) == -1)
{
return false;
}
//set up screen
screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);
//check screen
if (screen == NULL)
{
return false;
}
//init TTF
if (TTF_Init() == -1)
{
return false;
}
//set window caption
SDL_WM_SetCaption("PONG", NULL);
//if evetything worked
return true;
}
//load files
bool load_files()
{
font = TTF_OpenFont("PIXELITE.ttf", 45);
if (font == NULL)
{
return false;
}
return true;
}
/*CLASS DEFINITIONS*/
Button::Button()
{
}
Title::Title()
{
Title_Message = TTF_RenderText_Solid(font, "PONG", color);
Title_Button.button_sprite = load_image("Start.png");
Title_Button.button.x = 200;
Title_Button.button.y = 350;
Title_Button.button.w = 100;
Title_Button.button.h = 50;
//clips not hover
Title_Button.button_clip[0].x = 0;
Title_Button.button_clip[0].y = 0;
Title_Button.button_clip[0].w = 100;
Title_Button.button_clip[0].h = 50;
//clips hover
Title_Button.button_clip[1].x = 0;
Title_Button.button_clip[1].y = 50;
Title_Button.button_clip[1].w = 100;
Title_Button.button_clip[1].h = 50;
}
Title::~Title()
{
SDL_FreeSurface(Title_Message);
SDL_FreeSurface(Title_Button.button_sprite);
}
void Title::events()
{
int x = 0;
int y = 0;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_MOUSEMOTION)
{
x = event.motion.x;
y = event.motion.y;
if ((x > Title_Button.button.x) && (x < (Title_Button.button.x + Title_Button.button.w)) && (y > Title_Button.button.y) && (y < (Title_Button.button.y + Title_Button.button.h)))
{
clip = &Title_Button.button_clip[1];
}
else
{
clip = &Title_Button.button_clip[0];
}
}
if (event.type == SDL_QUIT)
{
quit = true;
}
}
}
void Title::logic()
{
}
void Title::render()
{
apply_surface(Title_Button.button.x, Title_Button.button.y, Title_Button.button_sprite, screen, clip);
apply_surface((SCREEN_WIDTH - Title_Message->w) / 2, 100, Title_Message, screen);
}
Anybody got an idea? Thanks!
I will post my suggestions from the comments as an actual answer:
The trouble-causing line Title_Message = TTF_RenderText_Solid(font, "PONG", color); refers to the global variable font of type TTF_Font*. The line is also part of the constructor of the class Title.
main looks like this:
int main(int argc, char* args[])
{
//init SDL
init();
//load everything
load_files();
currentState = new Title;
//...
font is initialized to NULL at declaration, an actual object is assigned only in load_files() which is executed at the beginning of main before Title is the first time instantiated.
So load_files() has to assign a valid pointer to font, otherwise the next line in main will cause an access violation.
load_files() provides a return value depending on whether creating and assigning this object was successful. However main never checks for this value and thus it is not guaranteed that font is a valid pointer.
As knefcy pointed out the problem was a wrong filename in load_files().
Question:
My while loop is not exiting. During debugging it has says very specifically that stateID is equal to 3. However, when I add std::cout << stateID; the value 1 is always written to the console, no matter what the debugger is telling me.
In the code, I render the screen after the input loop. This shows that, without a doubt, the input loop exits properly, as expected. Also, if that is not enough, It would have to exit for the stateID to change in the first place. Please, no more discussion of the nested loop being the issue. And I have also used breakpoints after the loop which are properly hit.
Code:
int main()
{
stateID = 1;
GameState* state = new GameStateTitle();
sf::RenderWindow window(sf::VideoMode(1000, 600), "RPG");
//GAME LOOP//
while (stateID != 3)
{//INPUT//
sf::Event event;
while (window.pollEvent(event))
{
switch (event.type)
{
//Window closed
case sf::Event::Closed:
set_next_state(3);
break;
}
}
change_state(state);
//RENDERING//
window.clear(sf::Color::Black);
state->render(window);
window.display();
}
/////////////
window.close();
return 0;
}
void set_next_state(int new_state)
{
//Set the next state to take place
next_state = new_state;
}
void change_state(GameState *current_state)
{
//Check if the next state is null or exit
if (next_state != 0)
{
if (next_state != 3)
delete current_state;
//Set the new state
switch(next_state)
...
}
stateID = next_state;
next_state = 0;
}
}
Below is my full code, in case I missed something important.
Main.cpp
#include "GameState.hpp"
int main()
{
stateID = STATE_TITLE;
GameState* state = new GameStateTitle();
sf::RenderWindow window(sf::VideoMode(1000, 600), "RPG");
//GAME LOOP//
while (stateID != STATE_EXIT)
{
//INPUT//
sf::Event event;
while (window.pollEvent(event))
{
switch (event.type)
{
//Window closed
case sf::Event::Closed:
set_next_state(STATE_EXIT);
break;
}
}
///////////
////LOGIC//
state->logic();
///////////
change_state(state);
//RENDERING//
window.clear(sf::Color::Black);
state->render(window);
std::cout << stateID;
window.display();
/////////////
}
///////////////
window.close();
return 0;
}
GameState.hpp
#pragma once
#include <SFML/Graphics.hpp>
//DEFINITIONS ETC//
enum GAME_STATES {
STATE_NULL,
STATE_TITLE,
STATE_BATTLE,
STATE_EXIT
};
///////////////////
//Game State Class//
class GameState
{
public:
virtual void input(void) = 0;
virtual void logic(void) = 0;
virtual void render(sf::RenderWindow &window) = 0;
};
////////////////////
//FUNCTIONS//
void set_next_state(int new_state);
void change_state(GameState *current_state);
/////////////
//VARIABLES//
static int stateID;
static int next_state;
/////////////
//Title GameState Class//
class GameStateTitle : public GameState
{
private:
sf::Texture img_title;
sf::Sprite bgr_title;
public:
GameStateTitle(void);
~GameStateTitle(void);
void input(void);
void logic(void);
void render(sf::RenderWindow &window);
};
/////////////////////////
//Battle GameState Class//
class GameStateBattle : public GameState
{
private:
sf::Image img_left;
sf::Image img_right;
public:
GameStateBattle(void);
~GameStateBattle(void);
void input(void);
void logic(void);
void render(sf::RenderWindow &window);
};
//////////////////////////
GameState.cpp
#include "GameState.hpp"
//Game state general functions
void set_next_state(int new_state)
{
//Set the next state to take place
next_state = new_state;
}
void change_state(GameState *current_state)
{
//Check if the next state is null or exit
if (next_state != STATE_NULL)
{
if (next_state != STATE_EXIT)
delete current_state;
//Set the new state
switch(next_state)
{
case STATE_TITLE:
current_state = new GameStateTitle;
break;
case STATE_BATTLE:
current_state = new GameStateBattle;
break;
}
stateID = next_state;
next_state = STATE_NULL;
}
}
//The functions of the title state
GameStateTitle::GameStateTitle(void)
{
//Load texture
img_title.loadFromFile("title_screen.png");
//Set texture to background
bgr_title.setTexture(img_title);
}
GameStateTitle::~GameStateTitle(void)
{
}
void GameStateTitle::input(void)
{
}
void GameStateTitle::logic(void)
{
}
void GameStateTitle::render(sf::RenderWindow &window)
{
window.draw(bgr_title);
}
//The functions of the battle state
GameStateBattle::GameStateBattle(void)
{
}
GameStateBattle::~GameStateBattle(void)
{
}
void GameStateBattle::input(void)
{
}
void GameStateBattle::logic(void)
{
}
void GameStateBattle::render(sf::RenderWindow &window)
{
}
You have two while loops. The condition in the outer while will never be tested until the inner loop exits.
Consider using just one loop, with a single condition.
sf::Event event;
while ( (stateID != 3) && window.pollEvent(event)) {
}
You are breaking out of the switch and not out of the while.
The program below (attempts to) duplicate your code.
It works as expected, with both loops ending when they are supposed to.
Btw, this is what we call a short, contained, correct example.
Correct as in compilable, using globals such as stateID or magic numbers such as the 3 in while (stateID != 3) is bad form and not the mark of a professional.
How does your program deviate from the one below?
#define STATE_TITLE 2
#define STATE_BATTLE 3
#define STATE_NULL 0
int stateID;
int next_state;
struct Event
{
Event(int Num) : type(Num){}
Event() : type( 5 ){} // random number grater than 1
enum Type
{
Closed = 1 // equivalent to sf::Event::Closed
};
int type;
};
class Window
{
public:
int pollEvent( Event& Ev )
{
return --Ev.type;
}
};
void set_next_state(int new_state)
{
next_state = new_state;
}
void change_state()
{
if (next_state != STATE_NULL)
{
stateID = next_state;
next_state = STATE_NULL;
}
}
int _tmain()
{
stateID = STATE_TITLE;
Window window;
//GAME LOOP//
while (stateID != 3)
{//INPUT//
Event event;
// note that there is at least one event with a Closed type before window.pollEvent(event) return false (0)
while (window.pollEvent(event))
{
switch (event.type)
{
//Window closed
case Event::Closed:
set_next_state(3);
break;
}
}
change_state();
}
return 0;
}
I changed the function change_state to the following:
void change_state(GameState *current_state, int &ID)
{
//Check if the next state is null or exit
if (next_state != STATE_NULL)
{
if (next_state != STATE_EXIT)
delete current_state;
//Set the new state
switch(next_state)
{
case STATE_TITLE:
current_state = new GameStateTitle;
break;
case STATE_BATTLE:
current_state = new GameStateBattle;
break;
}
ID = next_state;
next_state = STATE_NULL;
}
}
The issue must've been within the declaration of stateID, because it was never being changed in this function.