Jump to content
  • Advertisement
Sign in to follow this  
Assassin7257

Loading Different Events in SDL

This topic is 2255 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts


#include"SDL/SDL.h"
#include"SDL/SDL_image.h"
#include<string>
using namespace std;

//screen attributes
const int screenWidth = 640;
const int screenHeight = 480;
const int screenBPP = 32;

//surfaces
SDL_Surface* screen = NULL;

//event
SDL_Event event;

//portion being clipped
SDL_Rect clip [ 2 ];

//pokemonclip
SDL_Rect pokemonClip[3];

SDL_Surface* load_image ( string filename )
{
//temporarily store image
SDL_Surface* loadedImage = NULL;

//store optimizedimage
SDL_Surface* optimizedImage = NULL;

//load the image
loadedImage = IMG_Load( filename.c_str() );

//if no errors
if(loadedImage != NULL)
{
//optimize Image now
optimizedImage = SDL_DisplayFormat( loadedImage );

//free up old surface
SDL_FreeSurface(loadedImage);

//if no errors loading optimizedImage
if(optimizedImage != NULL)
{
SDL_SetColorKey( optimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB (optimizedImage->format, 0, 0xFF, 0xFF));
}
}

//if no errrors
return optimizedImage;
}

void apply_surface(int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = NULL)
{
//create rectangle to represent screen
SDL_Rect offset;

//set offset values
offset.x = x;
offset.y = y;

//blit surface
SDL_BlitSurface(source, clip, destination, &offset);
}

bool init()
{
//load everything
if(SDL_Init(SDL_INIT_EVERYTHING) == -1)
{
return false;
}

//set sscreen
screen = SDL_SetVideoMode(screenWidth, screenHeight, screenBPP, SDL_SWSURFACE);

//if there was an error
if(screen == NULL)
{
return false;
}

//windows caption
SDL_WM_SetCaption("Choose your DECISION", NULL);

//if no errors
return true;
}

void clean_up()
{
//quit
SDL_Quit();
}

//gender class
class Gender
{
private:
SDL_Surface* genderChoice; //genderChoice
SDL_Rect* choice;

public:
bool GenderEvents(); //handles the gender events choice
void showGenderChoice(); //display what gender they choose
void clipGender();

Gender();
~Gender();
};

Gender::Gender()
{
clipGender();
genderChoice = load_image("Characters.png");
choice = NULL;
}

Gender::~Gender()
{
SDL_FreeSurface(genderChoice);
}

void Gender::clipGender()
{
clip[0].x = 83;
clip[0].y = 43;
clip[0].w = 197;
clip[0].h = 385;

clip[1].x = 320;
clip[1].y = 15;
clip[1].w = 410;
clip[1].h = 460;
}

bool Gender::GenderEvents()
{
//get mouse position
int x = 0, y = 0;

apply_surface(0, 0, genderChoice, screen);

if(event.type == SDL_MOUSEBUTTONDOWN)
{
if(event.button.button == SDL_BUTTON_LEFT)
{
//get mouse offsets
x = event.motion.x;
y = event.motion.y;

if( (x > clip[0].x) && (x < clip[0].x + clip[0].w) && (y > clip[0].y) && (y < clip[0].y + clip[0].h) )
{
choice = &clip[0];
return true;
}

if( (x > clip[1].x ) && (x < clip[1].x + clip[1].w) && (y > clip[1].y) && (y < clip[1].y + clip[1].h) )
{
choice = &clip[1];
return true;
}
}
}
}

void Gender::showGenderChoice()
{
apply_surface(0, 0, genderChoice, screen, choice);
}

class PokemonChoice
{
private:
SDL_Surface* Pokemon;
SDL_Rect* PokeChoice; //the choice they make

public:
void setPokemonClip();
void PokemonEvents();
void showPokemon();

PokemonChoice();
~PokemonChoice();
};

PokemonChoice::PokemonChoice()
{
Pokemon = load_image("pokemon.png");
setPokemonClip();
PokeChoice = NULL;
}

PokemonChoice::~PokemonChoice()
{
SDL_FreeSurface(Pokemon);
}

void PokemonChoice::setPokemonClip()
{
pokemonClip[0].x = 25;
pokemonClip[0].y = 140;
pokemonClip[0].w = 145;
pokemonClip[0].h = 185;

pokemonClip[1].x = 275;
pokemonClip[1].y = 140;
pokemonClip[1].w = 145;
pokemonClip[1].h = 185;

pokemonClip[2].x = 475;
pokemonClip[2].y = 150;
pokemonClip[2].w = 145;
pokemonClip[2].h = 185;
}

void PokemonChoice::PokemonEvents()
{
//mouse offsets
int x = 0, y = 0;

apply_surface(0, 0, Pokemon, screen);

if(event.type == SDL_MOUSEBUTTONDOWN)
{
if(event.button.button == SDL_BUTTON_LEFT)
{
x = event.motion.x;
y = event.motion.y;

if( (x > pokemonClip[0].x) && (x < pokemonClip[0].x + pokemonClip[0].w) && (y > pokemonClip[0].y) && (y < pokemonClip[0].y + pokemonClip[0].w))
{
PokeChoice = &pokemonClip[0];
}

if( (x > pokemonClip[1].x) && (x < pokemonClip[1].x + pokemonClip[1].w) && (y > pokemonClip[1].y) && (y < pokemonClip[1].y + pokemonClip[1].w))
{
PokeChoice = &pokemonClip[1];
}

if( (x > pokemonClip[2].x) && (x < pokemonClip[2].x + pokemonClip[2].w) && (y > pokemonClip[2].y) && (y < pokemonClip[2].y + pokemonClip[2].w))
{
PokeChoice = &pokemonClip[2];
}
}
}

showPokemon();
}


void PokemonChoice::showPokemon()
{
apply_surface(0, 0, Pokemon, screen, PokeChoice);
}

void runGame(PokemonChoice& aPokemon, Gender& aGender)
{
bool Gender = aGender.GenderEvents();

if(Gender == true)
{
SDL_Delay(1000);
SDL_Flip(screen);

aPokemon.PokemonEvents();
}
}

int main(int argc, char* args[])
{
//quit flag
bool quit = false;

//if init failed
if(init() == false)
{
return 1;
}


PokemonChoice myPokemon;
Gender myGender;

myPokemon.PokemonEvents();

while(quit == false)
{
while(SDL_PollEvent(&event))
{
runGame(myPokemon, myGender);

if(event.type == SDL_QUIT)
{
quit = true;
}
}

if(SDL_Flip(screen) == -1)
{
return 1;
}
}

clean_up();

return 0;
}


What I'm trying to do is sort of recreate the begginning of a pokemon game, but I don't seem to know how to load the different events. Does anyone have any idea ? They both load at the same time btw, and not just one at a time.

Share this post


Link to post
Share on other sites
Advertisement
You're calling SDL_Flip(screen) in between of Polling events, which is bad, Whenever there is an event like moving mouse, It will flip the screen, You would get weird results.
Instead you could use an enumeration and traverse using that.



enum Gamestate
{
Gender,
Main,
Exit,
};
//Create a gamestate so that only the things you are viewing at the current state will only be shown
int main(int argc, char** argv )
{
Pokemon myPokemon;
Gender myGender;
//Initialize..

Gamestate current = Gender; //Now the game will start in the Gender section
while( current != Exit )
{
while(PollEvent)
{
if(event.type == SDL_QUIT)
{
current = Exit; //Exit out of the game;
}
switch(current) //Now for each Gamestate you can handle events seperately
{
case Gender:
//Handle quitting.
if(Gender)
{
current = Main;
}
break;
case Main:
Pokemon.HandleEvents();
break;
}
}
switch(current)
{
case Gender:
myGender.Draw();
break;
case Main:
myPokemon.Draw();
break;
}
SDL_Flip(screen);
}
}

Share this post


Link to post
Share on other sites
The problem here is that when the gender selection completes, your code allows for the pokemon selection to run "immediately". What I mean here is that despite the delay call, the same event is consumed twice, once by the Gender instance and once by the PokemonChoice. To avoid this, you need to structure your code such that each event causes only one sequence of actions.

JustinDaniel's code is one way of doing this. Another is to have a boolean indicating which is active that persists between calls:


void runGame(PokemonChoice& aPokemon, Gender& aGender, bool &isGender)
{
if(isGender)
{
bool Gender = aGender.GenderEvents();

if(Gender == true)
{
isGender = false;
SDL_Delay(1000);
SDL_Flip(screen);
}
else
{
aPokemon.PokemonEvents();
}
}

Note that in the above code it is impossible for the same event to be processed by both instances. JustinDaniel's approach is more general and will be easier to manage when more states are added.

Fundamentally your problem is unrelated to flipping the screen while processing events. That said, I think you need to make an architectural decision on what style of game you are going to write. If the game is event driven, then you should use a blocking event loop and ensure your game can handle SDL_VIDEOEXPOSE events or similar. Alternatively, write your game as a simulation loop and simply re-draw the screen every frame. The latter is usually easier to write, although wouldn't be a good idea if you were targeting battery powered devices, such as smart phones.

Either way, I would advise against using long SDL_Delays to implement timing based systems. Instead, your code should use SDL_Timers or calls to SDL_GetTicks() with periodic polling. This keeps your user interface nice and responsive. When you call SDL_Delay(), you lose the ability to respond to events in that time. When the user interface becomes unresponsive, users frequently start clicking wildly, and will eventually hit the "close" button. When you application wakes up, it tries to process all these events out of context, and usually ends up doing a bunch of stuff that the user didn't really intend you to do.

Share this post


Link to post
Share on other sites
It's not transverse, its traverse, Traverse means moving to or fro, In this case, moving from one state to another.
How I can implement it in other code as well ?[/quote]
What do you mean by that? Are you asking how it works or anything else?

Share this post


Link to post
Share on other sites
It is a state machine. The different states are represented by different enumeration values. The active state is stored by the value of "current", and transitions are signalled by changing the value of "current" to the desired next state.

Is there any specific part you are having trouble understanding? Perhaps you haven't encountered enumerations yet, or the switch statement? Or is it some broader concept?

Share this post


Link to post
Share on other sites

switch(current) //Now for each Gamestate you can handle events seperately
{
case Gender:
//Handle quitting.
if(Gender)
{
current = Main;
}
break;
case Main:
Pokemon.HandleEvents();
break;
}
}
switch(current)
{
case Gender:
myGender.Draw();
break;
case Main:
myPokemon.Draw();
break;
}


I don't understand the switch statement part, I know how to use switch statements but this is confusing :S The second switch statement in specific

Share this post


Link to post
Share on other sites

enum Gamestate
{
Gender,
Main,
Exit,
};

int main(int argc, char* args[])
{
//if init failed
if(init() == false)
{
return 1;
}


PokemonChoice myPokemon;

Gamestate current = Gender;

while(current != Exit)
{
while(SDL_PollEvent(&event))
{
if(event.type == SDL_QUIT)
{
current = Exit;
}

switch(current)
{
case Gender:
//handle
if(Gender)
{
current = Main;
}
break;
case Main:
myPokemon.PokemonEvents();
break;
}
}
SDL_Flip(screen);
}

clean_up();

return 0;
}



This is my code, and all it shows a blank screen, and if I declare an Gender instance then it'll show an error saying expected ; before the instance

Share this post


Link to post
Share on other sites
One problem is that the enumeration names are colliding with your class name. You can disambiguate using the "class" keyword, but instead you might consider changing the enumeration names - e.g. GenderState or GENDER.

Note that the if(Gender) test in your program was supposed to be a piece of psuedo code indicating that when the change from the Gender screen to the Pokemon screen is made.

Here is your first post rewritten as a state machine (with many other things too):


// It is conventional to include standard headers first (unless foo.cpp is including foo.h)
// Also note the space between the include and the <> or "". This makes it easier to read.
#include <string>
#include <iostream>
#include <algorithm>

// To be cross platform, you should omit the SDL/ directory here.
// To do this, you'll have to configure your compiler though.
#include "SDL/SDL.h"
#include "SDL/SDL_image.h"

using namespace std;

// Most coding conventions use UpperCase or ALL_CAPS for constants
// And camelCase for variables.
const int ScreenWidth = 640;
const int ScreenHeight = 480;
const int ScreenBPP = 32;

// Note: Global variables removed! (global constants are fine).

SDL_Surface* load_image( string filename )
{
// Delay declarations of variables until you can initialise them
SDL_Surface* image = IMG_Load( filename.c_str() );

// Sometimes it can be more convenient to "early out" rather than
// make a deeply nested set of NULL checks
if(!image)
{
return NULL;
}

// In your original code, failing to optimise the image would result in NULL
// being returned. This seems a little harsh given that the image loaded successfully!
// In this implementaiton, failing to optimise means that the unoptimised image is returned instead.
SDL_Surface* temp = SDL_DisplayFormat( image );
if(temp)
{
std::swap(image, temp);
SDL_FreeSurface(temp);
}

SDL_SetColorKey( image, SDL_SRCCOLORKEY, SDL_MapRGB(image->format, 0, 0xFF, 0xFF));
return image;
}

void apply_surface(int x, int y, SDL_Surface* source, SDL_Surface* destination, SDL_Rect* clip = NULL)
{
SDL_Rect offset = { x, y };
SDL_BlitSurface(source, clip, destination, &offset);
}

// You had duplicated the following code in your program 5 times!
// In fact, three of these contained a bug (it used the rectange width instead of height for the last check)
// Writing the logic once makes it easier to read, test and correct bugs.
bool isPointInsideRectangle(int x, int y, SDL_Rect rectangle)
{
return (x > rectangle.x) && (x < rectangle.x + rectangle.w) && (y > rectangle.y) && (y < rectangle.y + rectangle.h);
}

// Instead of returning a boolean, we can return the screen surface
// This avoids a global variable.
SDL_Surface *init()
{
if(SDL_Init(SDL_INIT_EVERYTHING) == -1)
{
return NULL;
}

SDL_Surface *screen = SDL_SetVideoMode(ScreenWidth, ScreenHeight, ScreenBPP, SDL_SWSURFACE);
if(!screen)
{
// If there is a failure here, we still need to ensure SDL is shut down.
SDL_Quit();
return 0;
}

SDL_WM_SetCaption("Choose your DECISION", NULL);
return screen;
}

void clean_up()
{
SDL_Quit();
}

class Gender
{
public:
Gender();
~Gender();

// Allows us to determine if the instance loaded correctly.
bool isValid() const;

// Handles one event at a time, doesn't draw anything
void handleEvent(SDL_Event event);

// Draw the current state to the screen.
void show(SDL_Surface *screen);

// Should return true if a transition to the next game state is allowed
bool update();

// Note: Don't put internal functions like "clipGender" in the "public" portion
// of a class. They are for internal use only

private:
static const int NUM_CLIPS = 2;
static const int TIMER_DURATION_MS = 1000;
SDL_Surface* surface;
// If choice is -1, then no choice is currently active.
// Otherwise choice is a value between 0 and NUM_CLIPS - 1
// i.e. it can be used as an index to "clips".
//
// The reason I made this an integer is because it is easier to debug.
// You can print the value, or inspect it in a debugger, and the meaning of
// the value is immediately apparent.
int choice;
SDL_Rect clips[NUM_CLIPS];
// This value becomes true when the player makes a selection.
// Once made, the choice cannot be changed
bool selectionMade;
// The timer allows us to wait before transitioning.
// We record the current time when the player makes a choice.
// Then we simply check if TIMER_DURATION_MS has elapsed since this event
Uint32 timer;
};

Gender::Gender()
{
surface = load_image("Characters.png");
choice = -1;

clips[0].x = 83;
clips[0].y = 43;
clips[0].w = 197;
clips[0].h = 385;

clips[1].x = 320;
clips[1].y = 15;
clips[1].w = 410;
clips[1].h = 460;

selectionMade = false;
timer = 0;
}

Gender::~Gender()
{
SDL_FreeSurface(surface);
}

bool Gender::isValid() const
{
return surface;
}

bool Gender::update()
{
if(selectionMade)
{
Uint32 now = SDL_GetTicks();
if(now - timer > TIMER_DURATION_MS)
{
return true;
}
}
return false;
}

void Gender::handleEvent(SDL_Event event)
{
if(selectionMade)
{
return;
}

if(event.type == SDL_MOUSEBUTTONDOWN)
{
if(event.button.button == SDL_BUTTON_LEFT)
{
int x = event.motion.x;
int y = event.motion.y;

choice = -1;
for(int i = 0 ; i < NUM_CLIPS ; ++i)
{
if(isPointInsideRectangle(x, y, clips))
{
choice = i;
selectionMade = true;
timer = SDL_GetTicks();
}
}
}
}
}

void Gender::show(SDL_Surface *screen)
{
SDL_Rect *pointer = (choice == -1 ? NULL : &clips[choice]);
apply_surface(0, 0, surface, screen, pointer);
}

// This class is fairly similar to Gender, so I won't comment it excessively.
class PokemonChoice
{
public:
PokemonChoice();
~PokemonChoice();

bool isValid() const;

void handleEvent(SDL_Event event);
void show(SDL_Surface *screen);

private:
static const int NUM_CLIPS = 3;
SDL_Surface* surface;
int choice;
SDL_Rect clips[NUM_CLIPS];
};

PokemonChoice::PokemonChoice()
{
surface = load_image("pokemon.png");

clips[0].x = 25;
clips[0].y = 140;
clips[0].w = 145;
clips[0].h = 185;

clips[1].x = 275;
clips[1].y = 140;
clips[1].w = 145;
clips[1].h = 185;

clips[2].x = 475;
clips[2].y = 150;
clips[2].w = 145;
clips[2].h = 185;

choice = -1;
}

PokemonChoice::~PokemonChoice()
{
SDL_FreeSurface(surface);
}

bool PokemonChoice::isValid() const
{
return surface;
}

void PokemonChoice::handleEvent(SDL_Event event)
{
if(event.type == SDL_MOUSEBUTTONDOWN)
{
if(event.button.button == SDL_BUTTON_LEFT)
{
int x = event.motion.x;
int y = event.motion.y;

choice = -1;
for(int i = 0 ; i < NUM_CLIPS ; ++i)
{
if(isPointInsideRectangle(x, y, clips))
{
choice = i;
}
}
}
}
}


void PokemonChoice::show(SDL_Surface *screen)
{
SDL_Rect *pointer = (choice == -1 ? NULL : &clips[choice]);
apply_surface(0, 0, surface, screen, pointer);
}

enum GameState
{
GenderState,
MainState,
ExitState,
};

int main(int argc, char* args[])
{
SDL_Surface *screen = init();
if(!screen)
{
// When things go wrong, it is better to be alerted to them
cerr << "Failed to start game: " << SDL_GetError() << '\n';
return 1;
}

PokemonChoice pokemon;
if(!pokemon.isValid())
{
cerr << "Failed to load pokenmon: " << SDL_GetError() << '\n';
clean_up();
return 1;
}

Gender gender;
if(!gender.isValid())
{
cerr << "Failed to load gender: " << SDL_GetError() << '\n';
clean_up();
return 1;
}

GameState current = GenderState;
while(current != ExitState)
{
// Handle Input
SDL_Event event;
while(SDL_PollEvent(&event))
{
if(event.type == SDL_QUIT)
{
current = ExitState;
}
else if(current == GenderState)
{
gender.handleEvent(event);
}
else if(current == MainState)
{
pokemon.handleEvent(event);
}
}

// Handle background logic (if any)
if(current == GenderState)
{
bool next = gender.update();
if(next)
{
current = MainState;
}
}

// Draw
if(current == GenderState)
{
gender.show(screen);
}
else if(current == MainState)
{
pokemon.show(screen);
}

SDL_Flip(screen);
}

clean_up();
return 0;
}

Because the enumeration is small, I've used if statements instead of switches so you can concentrate on the idea and not the implementation. You can change it to a switch statement later.

The above shows you how you might implement a timed transition between the two states without compromising your application's ability to respond to events. It also checks for more errors, and prints messages allowing you to diagnose these errors if they occur. It shows how to avoid using global variables to implement your program by passing parameters to the functions that need information.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!