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.
Related
I am making a google chrome dinosaur game using an SDL Template for graphics and I am almost finished but I have run into the issue of needing to be able to reset to the starting values of the game just like how you reset when you hit spacebar after dying in the google chrome dinosaur game. I have made an isGameOver function but I don't know how to reset the values to start a new game which is my main problem.
this is my GameScene class where the game over logic is located
GameScene.h
#pragma once
#include "Scene.h"
#include "GameObject.h"
#include "Player.h"
#include "Floor.h"
#include "Obstacle.h"
#include "FlyingObstacle.h"
#include <vector>
#include "util.h"
#include "text.h"
class GameScene : public Scene
{
public:
GameScene();
~GameScene();
void start();
void draw();
void update();
std::vector<Obstacle*> spawnedObstacle;
std::vector<FlyingObstacle*> spawnedBird;
private:
Player* player;
Floor* floor;
float spawnTime;
float currentSpawnTimer;
void floorCollision();
void obstacleCollision();
void birdCollision();
void obstacleSpawn();
void birdSpawn();
void despawnObstacle(Obstacle* obstacle);
void despawnBird(FlyingObstacle* bird);
int points;
int highscore;
bool isGameOver;
};
GameScene.cpp
#include "GameScene.h"
GameScene::GameScene()
{
// Register and add game objects on constructor
player = new Player();
this->addGameObject(player);
floor = new Floor();
this->addGameObject(floor);
points = 0;
highscore = 0;
}
GameScene::~GameScene()
{
delete player;
}
void GameScene::start()
{
Scene::start();
// Initialize any scene logic here
initFonts();
isGameOver = true;
currentSpawnTimer = 300;
spawnTime = rand() % 300; //spawn time of 5 seconds
for (int i = 0; i < 1; i++)
{
obstacleSpawn();
}
for (int i = 0; i < 3; i++)
{
birdSpawn();
}
}
void GameScene::draw()
{
Scene::draw();
drawText(110, 20, 255, 255, 255, TEXT_CENTER, "POINTS: %03d", points);
if (player->getIsAlive() == true)
{
drawText(900, 20, 255, 255, 255, TEXT_CENTER, "PRESS SPACE TO START MOVING");
}
if (player->getIsAlive() == false)
{
if (isGameOver == false)
drawText(SCREEN_WIDTH / 2, 200, 255, 255, 255, TEXT_CENTER, "YOU LOSE! PRESS SPACE TO SHOW POINTS");
if (isGameOver == true)
{
drawText(SCREEN_WIDTH / 2, 200, 255, 255, 255, TEXT_CENTER, "HIGHSCORE: %03d", highscore);
if (points > highscore)
{
drawText(SCREEN_WIDTH / 2, 200, 255, 255, 255, TEXT_CENTER, "NEW HIGHSCORE: %03d", points, highscore);
}
}
}
}
void GameScene::update()
{
if (isGameOver == true)
{
if (app.keyboard[SDL_SCANCODE_SPACE])
{
isGameOver = false;
}
}
if (isGameOver == false)
{
Scene::update();
floorCollision();
obstacleCollision();
birdCollision();
if (currentSpawnTimer > 0)
currentSpawnTimer--;
if (currentSpawnTimer <= 0)
{
for (int i = 0; i < 1; i++)
{
obstacleSpawn();
}
for (int i = 0; i < 3; i++)
{
birdSpawn();
}
currentSpawnTimer = spawnTime;
}
//This is where Gravity strength is located
if (player->getOnFloor() == false) {
player->setY(player->getY() + 7);
}
else {
player->getY() + 0;
}
}
}
void GameScene::floorCollision()
{
//Checks for collisions
for (int i = 0; i < objects.size(); i++)
{
//Cast to floor
Floor* floor = dynamic_cast<Floor*>(objects[i]);
//Check if the floor was casted
if (floor != NULL)
{
int collision = checkCollision(
player->getX(), player->getY(), player->getWidth(), player->getHeight(),
floor->getX(), floor->getY(), floor->getWidth(), floor->getHeight()
);
if (collision == 1)
{
player->setOnFloor(true);
if (player->getIsAlive() == true)
{
points++;
highscore++;
}
break;
}
}
}
}
void GameScene::obstacleCollision()
{
for (int i = 0; i < objects.size(); i++)
{
Obstacle* obstacle = dynamic_cast<Obstacle*>(objects[i]);
if (obstacle != NULL)
{
if (obstacle != NULL)
{
int collision = checkCollision(
player->getX(), player->getY(), player->getWidth(), player->getHeight(),
obstacle->getX(), obstacle->getY(), obstacle->getWidth(), obstacle->getHeight()
);
if (collision == 1)
{
player->doDeath();
isGameOver = true;
break;
}
}
}
}
}
void GameScene::birdCollision()
{
for (int i = 0; i < objects.size(); i++)
{
FlyingObstacle* bird = dynamic_cast<FlyingObstacle*>(objects[i]);
if (bird != NULL)
{
if (bird != NULL)
{
int collision = checkCollision(
player->getX(), player->getY(), player->getWidth(), player->getHeight(),
bird->getX(), bird->getY(), bird->getWidth(), bird->getHeight()
);
if (collision == 1)
{
player->doDeath();
isGameOver = true;
break;
}
}
}
}
}
void GameScene::obstacleSpawn()
{
Obstacle* obstacle = new Obstacle();
this->addGameObject(obstacle);
obstacle->setPosition(1200, 300 + (rand() % 300));
spawnedObstacle.push_back(obstacle);
}
void GameScene::birdSpawn()
{
FlyingObstacle* bird = new FlyingObstacle();
this->addGameObject(bird);
bird->setPos(1200, 300 + (rand() % 300));
spawnedBird.push_back(bird);
}
void GameScene::despawnObstacle(Obstacle* obstacle)
{
int index = -1;
for (int i = 0; i < spawnedObstacle.size(); i++)
{
//If pointer matches
if (obstacle == spawnedObstacle[i])
{
index = i;
break;
}
}
//If any match is found
if (index != -1)
{
spawnedObstacle.erase(spawnedObstacle.begin() + index);
delete obstacle;
}
}
void GameScene::despawnBird(FlyingObstacle* bird)
{
int index = -1;
for (int i = 0; i < spawnedBird.size(); i++)
{
//If pointer matches
if (bird == spawnedBird[i])
{
index = i;
break;
}
}
//If any match is found
if (index != -1)
{
spawnedBird.erase(spawnedBird.begin() + index);
delete bird;
}
}
I tried making an isGameOver bool inside my GameScene.h and I made it so that pressing spacebar would reset the game but in reality when the player dies the screen pauses every movement instead of resetting and if I press space again the game continues to move even though the player is dead.
Your entry point for the game(probably main function will probably have some form like this)
#include ...
#include ...
...
int main()
{
bool game_running = true;
while(game_running)
{
GameInstance* game = new Game(); //Allocation and initialization of all resources needed to play the game.
game.play(); // This function will finish when you lose the game.
delete game; // Deallocation.
if(!run_again())
{
game_running = false;
}
}
return 0;
}
After deallocation, you check if user wants to play again, you enter the next iteration of while loop, and new instance of Game is allocated, where all the resource handling takes place. The only problem is that allocating Game instance for every iteration might be a little costly, but you can ignore that if you don't worry about performance much.
So I was determined to get collision working myself from online resources without asking for help & I successfully followed a tutorial on lazyfoo to get collision working but quickly realised other problems.
So I started trying to get my collision working on my player (just a rect with controls atm) but couldn't actually access the rect inside the player class, I had to initialize it in main & use that for collision which meant transferring my collision function to main (not exactly perfect).
My question: So my collision is limited to two rects which I dedicated to the player & a single asteroid but my idea was to create multiple asteroids coming from the top of the screen, how would I go about altering the below code to accept a vector of "asteroids". Any advice about altering my code to be better object oriented etc is more then welcome but my main problem is collision. Please read collision carefully before posting, the specific X and Y params from the .cpp provides are hardcoded there.
Below Code order:
My current way of defining both player & asteroid for collision
Collision function
Asteroid.cpp & Player.cpp
My new way of loading asteroids - This is what I need collision to work with
Player aPlayer(200, 50, 50, 50);
Asteroid oneAsteroid(200, -50, 50, 50);
bool check_collision(SDL_Rect aPlayer, SDL_Rect oneAsteroid) {
//The sides of the rectangles
int leftA, leftB;
int rightA, rightB;
int topA, topB;
int bottomA, bottomB;
//Calculate the sides of rect A
leftA = aPlayer.X;
rightA = aPlayer.X + aPlayer.width;
topA = aPlayer.Y;
bottomA = aPlayer.Y + aPlayer.height;
//Calculate the sides of rect B
leftB = oneAsteroid.X;
rightB = oneAsteroid.X + oneAsteroid.width;
topB = oneAsteroid.Y;
bottomB = oneAsteroid.Y + oneAsteroid.height;
if (bottomA <= topB)
{
return false;
}
if (topA >= bottomB)
{
return false;
}
if (rightA <= leftB)
{
return false;
}
if (leftA >= rightB)
{
return false;
}
//If none of the sides from A are outside B
return true;
}
#include "asteroids.h"
Asteroid::Asteroid()
{
}
Asteroid::~Asteroid()
{
}
Asteroid::Asteroid(int x, int y, int w, int h)
{
X = x; Y = y; width = w; height = h;
//SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Square Constructed with Param(&p)", this);
}
void Asteroid::Render(SDL_Renderer * aRenderer)
{
printf("");
SDL_Rect* aAsteroid = new SDL_Rect();
aAsteroid->x = X; aAsteroid->y = Y; aAsteroid->w = width; aAsteroid->h = height;
SDL_SetRenderDrawColor(aRenderer, 0, 0, 0, 255);
SDL_RenderFillRect(aRenderer, aAsteroid);
}
void Asteroid::Init()
{
velocity.X = 0;
velocity.Y = 5;
}
void Asteroid::Update()
{
Y = Y + velocity.Y;
// printf("%d \n", Astero);
if (Y > 650) {
Y = -50;
}
}
#include "Player.h"
#include "asteroids.h"
using namespace std;
Player::Player()
{
}
Player::~Player()
{
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Player Destroyed (&p)", this);
}
Player::Player(int x, int y, int w, int h)
{
X = x; Y = y; width = w; height = h;
//SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Square Constructed with Param(&p)", this);
}
void Player::Init()
{
velocity.X = 10;
velocity.Y = 10;
}
void Player::Update()
{
Y = Y + floorStart;
if (X < 0) {
X = 0;
}
if (KEY_RIGHT == true) {
X++;
}
if (X > SCREEN_WIDTH - 50) {
X = SCREEN_WIDTH - 50;
}
if (Y > SCREEN_HEIGHT - 100) {
Y = SCREEN_HEIGHT - 100;
}
if (KEY_LEFT == true) {
X--;
}
}
void Player::Input(SDL_Event event)
{
if (event.type == SDL_KEYDOWN && event.key.repeat == NULL)
{
if (event.key.keysym.sym == SDLK_LEFT) {
KEY_LEFT = true;
}
else {
KEY_LEFT = false;
}
if (event.key.keysym.sym == SDLK_RIGHT) {
KEY_RIGHT = true;
}
else {
KEY_RIGHT = false;
}
}
if (event.type == SDL_KEYUP && event.key.repeat == NULL) {
if (event.key.keysym.sym == SDLK_LEFT) {
KEY_LEFT = false;
}
if (event.key.keysym.sym == SDLK_RIGHT) {
KEY_RIGHT = false;
}
}
}
void Player::Render(SDL_Renderer * aRenderer)
{
SDL_Rect* thePlayer = new SDL_Rect;
thePlayer->x = X; thePlayer->y = Y; thePlayer->w = width; thePlayer->h = height;
SDL_SetRenderDrawColor(aRenderer, 0, 0, 0, 255);
SDL_RenderFillRect(aRenderer, thePlayer);
//SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "Rendering (&p)", this);
}
What I need collision to work with, a vector of asteroids:
std::vector<Asteroid*> asteroidList;
asteroidList.push_back(new Asteroid(150, 350, 50, 50));
asteroidList.push_back(new Asteroid(70, 120, 125, 125));
The function to check collision in main.cpp
if (check_collision(aPlayer.thePlayer, oneAsteroid.aAsteroid)) {
printf("#######################");
}
And lastly, how would I go about rendering the array of asteroids?
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 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().
I'm self learning programming using various online tutorials and a couple of books. Currently it's C++. I've done a bit of OpenGL and SDL in the last few days.
I have a small program that creates a wall to stop a small square from passing through it.
Here's my code:
//
// main.cpp
// SDL_Template
//
// The headers
#include <stdlib.h>
#include <string>
// SDL headers
#include <SDL/SDL.h>
#include "SDL_image/SDL_image.h"
//#include "SDL/SDL_ttf.h"
//#include "SDL/SDL_mixer.h"
// Other headers
#include <OpenGL/gl3.h>
// Screen attributes
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;
// The frame rate
const int FRAMES_PER_SECOND = 20;
// The attributes of the square
const int SQUARE_WIDTH = 20;
const int SQUARE_HEIGHT = 20;
// The surfaces
SDL_Surface *square = NULL;
SDL_Surface *screen = NULL;
// The event structure
SDL_Event event;
// The wall
SDL_Rect wall;
// The square
class Square
{
private:
// The collision box of the square
SDL_Rect box;
// The velocity of the square
int xVel, yVel;
public:
// Initializes the variables
Square();
// Takes key presses and adjusts the square's velocity
void handle_input();
// Moves the square
void move();
// Shows the square on the screen
void show();
};
//The timer
class Timer
{
private:
// The clock time when the timer started
int startTicks;
// The ticks stored when the timer was paused
int pausedTicks;
// The timer status
bool paused;
bool started;
public:
// Initializes variables
Timer();
// The various clock actions
void start();
void stop();
void pause();
void unpause();
// Gets the timer's time
int get_ticks();
// Checks the status of the timer
bool is_started();
bool is_paused();
};
SDL_Surface *load_image(std::string filename)
{
// The image that's loaded
SDL_Surface* loadedImage = NULL;
// The optimized surface that will be used
SDL_Surface* optimizedImage = NULL;
// Load the image
loadedImage = IMG_Load(filename.c_str());
// If the image loaded
if (loadedImage != NULL)
{
// Create an optimized surface
optimizedImage = SDL_DisplayFormat(loadedImage);
// Free the old surface
SDL_FreeSurface(loadedImage);
// If the surface was optimized
if (optimizedImage != NULL)
{
// Color key surface
SDL_SetColorKey(optimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB(optimizedImage->format, 0, 0xFF, 0xFF));
}
}
// Return the optimized surface
return optimizedImage;
}
void apply_surface(int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = NULL)
{
// Holds offsets
SDL_Rect offset;
// Get offsets
offset.x = x;
offset.y = y;
// Blit
SDL_BlitSurface(source, clip, destination, &offset);
}
bool check_collision(SDL_Rect A, SDL_Rect B)
{
// The sides of the rectangles
int leftA, leftB;
int rightA, rightB;
int topA, topB;
int bottomA, bottomB;
// Calculate the sides of rect A
leftA = A.x;
rightA = A.x + A.w;
topA = A.y;
bottomA = A.y + A.h;
// Calculate the sides of rect B
leftB = B.x;
rightB = B.x + B.w;
topB = B.y;
bottomB = B.y + B.h;
// If any of the sides from A are outside of B
if( bottomA <= topB )
{
return false;
}
if( topA >= bottomB )
{
return false;
}
if( rightA <= leftB )
{
return false;
}
if( leftA >= rightB )
{
return false;
}
// If none of the sides from A are outside B
return true;
}
bool init()
{
// Initialize all SDL subsystems
if (SDL_Init( SDL_INIT_EVERYTHING ) == -1)
{
return false;
}
// Set up the screen
screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);
// If there was an error in setting up the screen
if (screen == NULL)
{
return false;
}
// Set the window caption
SDL_WM_SetCaption("Move the Square", NULL);
// If everything initialized fine
return true;
}
bool load_files()
{
// Load the square image
square = load_image("square.bmp");
// If there was a problem in loading the square
if (square == NULL)
{
return false;
}
// If everything loaded fine
return true;
}
void clean_up()
{
// Free the surface
SDL_FreeSurface(square);
// Quit SDL
SDL_Quit();
}
Square::Square()
{
// Initialize the offsets
box.x = 0;
box.y = 0;
// Set the square's dimentions
box.w = SQUARE_WIDTH;
box.h = SQUARE_HEIGHT;
// Initialize the velocity
xVel = 0;
yVel = 0;
}
void Square::handle_input()
{
// If a key was pressed
if (event.type == SDL_KEYDOWN)
{
//Adjust the velocity
switch (event.key.keysym.sym)
{
case SDLK_UP: yVel -= SQUARE_HEIGHT / 2; break;
case SDLK_DOWN: yVel += SQUARE_HEIGHT / 2; break;
case SDLK_LEFT: xVel -= SQUARE_WIDTH / 2; break;
case SDLK_RIGHT: xVel += SQUARE_WIDTH / 2; break;
}
}
// If a key was released
else if (event.type == SDL_KEYUP)
{
//Adjust the velocity
switch (event.key.keysym.sym)
{
case SDLK_UP: yVel += SQUARE_HEIGHT / 2; break;
case SDLK_DOWN: yVel -= SQUARE_HEIGHT / 2; break;
case SDLK_LEFT: xVel += SQUARE_WIDTH / 2; break;
case SDLK_RIGHT: xVel -= SQUARE_WIDTH / 2; break;
}
}
}
void Square::move()
{
// Move the square left or right
box.x += xVel;
// If the square went too far to the left or right or has collided with the wall
if (( box.x < 0 ) || ( box.x + SQUARE_WIDTH > SCREEN_WIDTH ) || ( check_collision(box, wall)))
{
// Move back
box.x -= xVel;
}
// Move the square up or down
box.y += yVel;
// If the square went too far up or down or has collided with the wall
if (( box.y < 0 ) || ( box.y + SQUARE_HEIGHT > SCREEN_HEIGHT) || (check_collision(box, wall)))
{
// Move back
box.y -= yVel;
}
}
void Square::show()
{
// Show the square
apply_surface(box.x, box.y, square, screen);
}
Timer::Timer()
{
// Initialize the variables
startTicks = 0;
pausedTicks = 0;
paused = false;
started = false;
}
void Timer::start()
{
// Start the timer
started = true;
// Unpause the timer
paused = false;
// Get the current clock time
startTicks = SDL_GetTicks();
}
void Timer::stop()
{
// Stop the timer
started = false;
// Unpause the timer
paused = false;
}
void Timer::pause()
{
// If the timer is running and isn't already paused
if ((started == true) && (paused == false))
{
// Pause the timer
paused = true;
// Calculate the paused ticks
pausedTicks = SDL_GetTicks() - startTicks;
}
}
void Timer::unpause()
{
// If the timer is paused
if (paused == true)
{
// Unpause the timer
paused = false;
// Reset the starting ticks
startTicks = SDL_GetTicks() - pausedTicks;
// Reset the paused ticks
pausedTicks = 0;
}
}
int Timer::get_ticks()
{
// If the timer is running
if (started == true)
{
// If the timer is paused
if (paused == true)
{
// Return the number of ticks when the timer was paused
return pausedTicks;
}
else
{
// Return the current time minus the start time
return SDL_GetTicks() - startTicks;
}
}
// If the timer isn't running
return 0;
}
bool Timer::is_started()
{
return started;
}
bool Timer::is_paused()
{
return paused;
}
int main(int argc, char* args[])
{
// Quit flag
bool quit = false;
// The square
Square mySquare;
// The frame rate regulator
Timer fps;
// Initialize
if( init() == false )
{
return 1;
}
// Load the files
if (load_files() == false)
{
return 1;
}
// Set the wall
wall.x = 300;
wall.y = 40;
wall.w = 40;
wall.h = 400;
// While the user hasn't quit
while (quit == false)
{
// Start the frame timer
fps.start();
// While there are events to handle
while (SDL_PollEvent(&event))
{
// Handle events for the square
mySquare.handle_input();
// If the user has Xed out the window
if (event.type == SDL_QUIT)
{
// Quit the program
quit = true;
}
}
// Move the square
mySquare.move();
// Fill the screen white
SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0xFF, 0xFF, 0xFF));
// Show the wall
SDL_FillRect (screen, &wall, SDL_MapRGB(screen->format, 0x77, 0x77, 0x77));
// Show the square on the screen
mySquare.show();
// Update the screen
if (SDL_Flip(screen) == -1)
{
return 1;
}
// Cap the frame rate
if (fps.get_ticks() < 1000 / FRAMES_PER_SECOND)
{
SDL_Delay((1000 / FRAMES_PER_SECOND) - fps.get_ticks());
}
}
// Clean up
clean_up();
return 0;
}
I completely understand how this site works, so I'm not asking you to check my code. My code compiles in both Xcode 4.5 and Visual Studio 2010. In Xcode it compiles, but throws some warnings up (although it still builds), but nothing happens when it's run. However in Visual Studio 2012, it compiles, with no warnings and runs successfully.
I have searched here and the C++/SDL forums/help pages, but I haven't found any similar cases.
Why may this be? Seems it runs in Visual Studio 2010, I'm confident it isn't the code...
In case you were wondering the warnings flagged up in Xcode are:
229 enumeration values not handled in switch: 'SDLK_UNKNOWN','SDLK_BACKSPACE','SDLK_TAB'...
This is highlighted on the switch (event.key.keysym.sym) line of code.
So my questions are:
What kind of problems could case this warning error?
Are there any commonly know issues with programs working in Visual Studio and not Xcode?
I'm guessing (it seems I can't find anything about it) it's a setting somewhere that I haven't spotted...
I apologise for the length of this.
You are not handling all the possible choices in your switch statement. If this is what you intended then you can remove the warning by using the default case like the following:
switch (event.key.keysym.sym)
{
case SDLK_UP: yVel -= SQUARE_HEIGHT / 2; break;
case SDLK_DOWN: yVel += SQUARE_HEIGHT / 2; break;
case SDLK_LEFT: xVel -= SQUARE_WIDTH / 2; break;
case SDLK_RIGHT: xVel += SQUARE_WIDTH / 2; break;
default: break;
}
Xcode is simply warning you that you are not handling all possible values for the event.key.keysym.sym enumeration. Since I doubt you want to handle ever single different type of key press, this isn't a problem, as I would maybe see if I could turn down the warning level to suppress these warnings.
As for the program not running successfully when built by Xcode, I don't know. Perhaps SDL is set up in a different way?