Jump to content

  • Log In with Google      Sign In   
  • Create Account


Loading Different Events in SDL


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
12 replies to this topic

#1 Assassin7257   Members   -  Reputation: 105

Like
0Likes
Like

Posted 19 March 2012 - 08:55 PM

#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.

Sponsor:

#2 JustinDaniel   Members   -  Reputation: 137

Like
0Likes
Like

Posted 19 March 2012 - 09:55 PM

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);
	  }
}


#3 rip-off   Moderators   -  Reputation: 6874

Like
0Likes
Like

Posted 20 March 2012 - 05:10 AM

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.

#4 Assassin7257   Members   -  Reputation: 105

Like
0Likes
Like

Posted 20 March 2012 - 08:21 PM

Can you explain what a transverse is ? How I can implement it in other code as well ?

#5 JustinDaniel   Members   -  Reputation: 137

Like
0Likes
Like

Posted 20 March 2012 - 10:02 PM

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 ?

What do you mean by that? Are you asking how it works or anything else?

#6 Assassin7257   Members   -  Reputation: 105

Like
0Likes
Like

Posted 21 March 2012 - 06:39 AM

Yea, how it works

#7 rip-off   Moderators   -  Reputation: 6874

Like
0Likes
Like

Posted 21 March 2012 - 08:20 AM

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?

#8 Assassin7257   Members   -  Reputation: 105

Like
0Likes
Like

Posted 21 March 2012 - 06:38 PM

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

#9 Assassin7257   Members   -  Reputation: 105

Like
0Likes
Like

Posted 21 March 2012 - 07:43 PM

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

#10 rip-off   Moderators   -  Reputation: 6874

Like
0Likes
Like

Posted 22 March 2012 - 04:19 AM

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[i]))
                {
                    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[i]))
                {
                    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.

#11 Assassin7257   Members   -  Reputation: 105

Like
0Likes
Like

Posted 22 March 2012 - 07:56 PM

SDL_Rect *pointer = (choice == -1 ? NULL : &clips[choice]) this part confused me, whats with the question mark ?

#12 JustinDaniel   Members   -  Reputation: 137

Like
0Likes
Like

Posted 22 March 2012 - 09:02 PM

Are you sure you took C++? You seem to be suffering with the basics.

?: is a conditional operator, What it does is, It converts the conditional expression into a bool and checks if it is true or false.
i.e
(choice == -1)
Checks if it is true or false;
if it is true. It executes the first expression in this case it is NULL.

If it is false, it executes the second expression, i.e &clips[choice];

In Grammar
Is choice equal to -1? If so then please take the value of NULL. Or else take the value of &clips[choice];

Simple.

#13 rip-off   Moderators   -  Reputation: 6874

Like
0Likes
Like

Posted 23 March 2012 - 05:32 AM

Most uses of the conditional operator can be thought of like short hand for trivial if/else statements. In this situation, the code is a concise equivalent of:
SDL_Rect *pointer;
if(choice == -1)
{
    pointer = NULL;
}
else
{
    pointer = &clips[choice]);
}
I prefer not to write such code because having an uninitialised pointer like that is asking for someone to re-order the code and accidentally introduce a bug.

An alternative would be:

SDL_Rect *pointer = NULL;
if(choice != -1)
{
    pointer = &clips[choice]);
}
Some people dislike the conditional operator, they think it is too cryptic. This is more a stylistic matter though.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS