Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411644 Posts in 69395 Topics- by 58450 Members - Latest Member: pp_mech

May 15, 2024, 03:02:32 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)SDL Advice
Pages: [1]
Print
Author Topic: SDL Advice  (Read 1938 times)
hous
Level 0
*



View Profile
« on: March 03, 2010, 06:25:56 PM »

Hi all. I'm pretty new here, and brand new with SDL. I've been doing the Lazy Foo tuts and they're pretty great, but I'm looking for advice about how to best separate my Event/Input code from the rest of my program. All of the examples on Lazy Foo and elsewhere use an event loop in main to capture key presses, like this:
Code:
 while( SDL_PollEvent( &event ) ){
        switch( event.type ){
            /* Look for a keypress */
            case SDL_KEYDOWN:
                /* Check the SDLKey values and move change the coords */
                switch( event.key.keysym.sym ){
                    case SDLK_LEFT:
                        //do something
                        break;
                    case SDLK_RIGHT:
                        //do something
                        break;
                    case SDLK_UP:
                        //do something
                        break;
                    case SDLK_DOWN:
                        //do something
                        break;
                    default:
                        break;
                }
            }
        }
    }

This seems messy to me. Does anyone have an example of an input/event class that would capture all these events and broadcast them so that other classes can listen for them and act appropriately? Or am I thinking about this in the wrong way...

The way I see it, my Player1 class (just an example) would be able to listen for the directional buttons, space, ctrl, etc. while a Controller class of some sort would listen for Enter (pause), etc.

Anyone else working in SDL that could shed some light on this?
Logged
Gold Cray
Level 10
*****


Gold Cray


View Profile WWW
« Reply #1 on: March 03, 2010, 08:02:34 PM »

It turns out I got this from lazy foo lesson 10.
Code:
  Uint8* keys;
...
  keys=SDL_GetKeyState(NULL);
...
  if(keys[SDLK_whatever])
  {
    doSomething();
  }
  

Just pass keys to whatever function the class uses to check for input.
« Last Edit: March 03, 2010, 08:12:31 PM by Gold Cray » Logged
hous
Level 0
*



View Profile
« Reply #2 on: March 04, 2010, 09:19:32 AM »

That'll do Smiley Thanks, Gold Cray.
Logged
Chromanoid
Level 10
*****



View Profile
« Reply #3 on: March 04, 2010, 09:49:27 AM »

you could implement a finite state machine too (especially you want to implement key combos).
Logged
salade
Level 4
****



View Profile
« Reply #4 on: March 05, 2010, 02:11:07 PM »

the only problem with SDL_GetKeyState is that there is no way to tell if a button was just pressed without comparing it to what was pressed last time the keystate was checked. alternatively, you could try working with the SDL_KEYDOWN and SDL_KEYUP events, but then those ruin the point of having the keysate in th first place. This is, in my opinion, perhaps one of the biggest design flaws in SDL. It's not that big a deal though, really just annoying more than anything, and I wouldn't be supprised if there was some cosmic reason for it.
Logged
Kekskiller
Guest
« Reply #5 on: March 05, 2010, 02:45:49 PM »

It's probably due to the fact that SDL uses this array for it's own input events? Maybe I'm wrong, but I'm fucking sure that storing all key stats in an array is the "classic" way to check the keyboard state. I can remember that in QBasic there was a function you could use to check a specific key's state - no events or things like that. Every key event is a synthesized functionality, basing on an array of keystates for fast and easy access.
Logged
Tycho Brahe
Level 10
*****

λx.x


View Profile
« Reply #6 on: March 06, 2010, 01:59:26 PM »

for my most recent thing I'm transferring the sdl event to an alternate class specifically designed to handle it. I then store pointers to functions for each of the buttons to be executed when sdl detects a keydown or keyup event.

Unfortunately when passing pointers to functions (if the function is a function of a class) the function has to be static, so I also pass a pointer to the object of the class that the keypress handling class will call the function of.

it boils down to this:

game/engine base class (Base.h):
Code:
#include "InputHandler.h"
class Base
{
public:
    Base()
    {

    }
    virtual ~Base() {}
    static void CallBackFunc(int button,int typeCB, void* cla)
    {
        Base* BASEP = static_cast<Base*>(cla);
        switch(typeCB)
        {
            case MOUSE_DOWN:
            BASEP->MouseDown(button);
            break;
            case MOUSE_UP:
            BASEP->MouseUp(button);
            break;
            case BUTTON_DOWN:
            BASEP->ButtonDown(button);
            break;
            case BUTTON_UP:
            BASEP->ButtonUp(button);
            break;
        }
    }
    void ButtonDown(int button)
    {
        std::cout<<"Button "<<button<<" down\n";
    }
    void ButtonUp(int button)
    {
        std::cout<<"Button "<<button<<" up\n\n";
    }
    void MouseDown(int button)
    {
        std::cout<<"Mouse "<<button<<" down\n";
    }
    void MouseUp(int button)
    {
        std::cout<<"Mouse "<<button<<" up\n\n";
    }
    void Initialise()
    {
        INHANDLER.RegisterCallback(&CallBackFunc,this);//this is the pointer to itself which is passed to the input handling class
    }
    InputHandler INHANDLER;

protected:
private:

};
This has a few important things:
-A callback function which the input handler will call on an sdl event (eg, SDL_KEYDOWN)
-specialised functions for each of the possible sdl events
-An instance of the InputHandler class (below)
-An initialisation functions which registers the callback function and the pointer to the class


input handling class (in InputHandler.h):
Code:
enum
{
    MOUSE_DOWN,
    MOUSE_UP,
    BUTTON_DOWN,
    BUTTON_UP
};
class InputHandler
{
public:
    InputHandler() {}
    virtual ~InputHandler() {}
    void PassEventHandler(SDL_Event ev)
    {
        SDL_GetMouseState(&x,&y);
        while (SDL_PollEvent(&ev))
        {
            switch (ev.type)
            {
            case SDL_QUIT:
                eFunc();
                break;
            case SDL_KEYDOWN:
                if (ev.key.keysym.sym == SDLK_ESCAPE)
                {
                    eFunc();
                }
                else
                {
                    Keys[ev.key.keysym.sym] = true;
                    cbFunc(ev.key.keysym.sym,BUTTON_DOWN,ClassPointer);
                }
                break;
            case SDL_KEYUP:
                Keys[ev.key.keysym.sym] = false;
                cbFunc(ev.key.keysym.sym,BUTTON_UP,ClassPointer);
                break;
            case SDL_MOUSEBUTTONDOWN:
                if (ev.button.button == SDL_BUTTON_LEFT)
                {
                    cbFunc(1,MOUSE_DOWN,ClassPointer);
                }
                if (ev.button.button == SDL_BUTTON_RIGHT)
                {
                    cbFunc(2,MOUSE_DOWN,ClassPointer);
                }
                break;
            case SDL_MOUSEBUTTONUP:
                if (ev.button.button == SDL_BUTTON_LEFT)
                {
                    cbFunc(1,MOUSE_UP,ClassPointer);
                }
                if (ev.button.button == SDL_BUTTON_RIGHT)
                {
                    cbFunc(2,MOUSE_UP,ClassPointer);
                }
                break;
                case SDL_MOUSEMOTION:
                x = ev.button.x;
                y = ev.button.y;
                break;
            }
        }
    }
    int GetWinMousePosX()
    {
        return x;
    }
    int GetWinMousePosY()
    {
        return y;
    }
    bool ButtonDown(int Button)
    {
        return Keys[Button];
    }
    int RegisterExitCallback(void (*pt2Func)(void))
    {
        if (pt2Func == NULL)
        {
            return 1;
        }
        eFunc = pt2Func;
        return 0;
    }
    int RegisterCallback(void (*pt2Func)(int,int,void*),void* pt2Class)
    {
        if (pt2Class != NULL)
        {
            ClassPointer = pt2Class;
        }
        else
        {
            return 1;
        }
        if (pt2Func != NULL)
        {
            cbFunc = pt2Func;
        }
        else
        {
            return 2;
        }
        return 0;
    }
protected:
private:
    bool Keys[322];
    bool left, right;
    int x,y;
    void (*cbFunc)(int,int,void*);
    void (*eFunc)(void);
    void* ClassPointer;
};

The input handler class basically holds a couple of things:
-The pointer to the class:
this is passed to the static callback function  so it can access other properties of the class (static functions can only access other static functions and variables)

-The call back function pointer:
This is called every time a SDL_KEYDOWN or SDL_KEYUP even is found

-The exit function pointer:
This is called if the input class finds a SDLK_ESCAPE in the SDL_KEYDOWN case, and basically quits the sdl while loop.

-The buttons array, mouse buttons variables and x/y positions variables:
This is here to store a persistent record of which buttons are down and which buttons (or mouse buttons) are up so that a function can access that property without waiting for a SDL_KEYDOWN or KEYUP case. The x/y properties are used to store the WINDOWS mouse coordinates, not the coordinates in the opengl scene.

-A set of functions to easily return the property of a button or the location of the mouse pointer



to use this you just need a couple of simple things in you sdl while loop (heres my one):
Code:
    GAME.Initialise();
    GAME.INHANDLER.RegisterExitCallback(&ExitNicely);
    while (!done)                                                 
    {
        SDL_Event ev;                                               //Create the SDL_Event object to receive and process keyboard and mouse input
        GAME.INHANDLER.PassEventHandler(ev);
        GAME.Render();                                             
    }                                                                                                 
Here I initialise the instance of my base class (named GAME) and registering the exit function; ExitNicely, which I have created earlier and which basically sets done (the boolean value in the loop) to true. Then, while it is looping I first get the SDL_Event and pass it to my input handler in the base class which in turn will call any functions registered with it. I then render the game and loop again.


This might seem like a pretty pointlessly complicated system but it allows for three main things:

  • The input handler can be passed to any function or class, effectively giving that class  (or function) the ability to get information about button or mouse states
  • It can be used in any class. As the pointer to the class is a void pointer, any pointer can be used here and extracted
  • The callback function, what it does and the functions it in turn calls (or doesnt call) can be defined in the class using the input handler class, instead of in the input handler, meaning it can be used within multiple different base classes


Anyway, that's just how I do it, you might want to look into using the sdl polling loop within a different class or function, but you might not want to go all the way into using function pointers.


also, to awesome c++ coders everywhere, some feedback on how I do it would be great  Shrug

[/list]
Logged
hous
Level 0
*



View Profile
« Reply #7 on: March 07, 2010, 08:55:17 PM »

So what I ended up doing using an Observer pattern. Not sure if this is the best way to go about it but it seems to be working. The Input class inherits from the Publisher class and anything that needs to listen for SDL_Events inherits from the Subscriber class.

Pub/Sub classes that I ripped off from http://codewrangler.home.comcast.net/~codewrangler/tech_info/patterns_code.html:
Code:
#include <list>

#ifndef _OBSERVER_H
#define _OBSERVER_H

class Publisher;
class Subscriber
{
public:
    virtual ~Subscriber();
    virtual void update(const Publisher& publisher);
protected:
    Subscriber();
};

Subscriber::Subscriber(){};
Subscriber::~Subscriber(){};
void Subscriber::update(const Publisher& publisher){};

class Publisher
{
public:
    virtual ~Publisher();
    virtual void attach(Subscriber*);
    virtual void detach(Subscriber*);
    virtual bool notify(); // bool return for a failure condition
protected:
    Publisher(); // protected default ctor
private:
    typedef std::list<Subscriber*> ObsList;
    typedef ObsList::iterator ObsListIter;
    ObsList mSubscribers;
};

Publisher::Publisher(){};
Publisher::~Publisher(){};

void Publisher::attach (Subscriber* obs) {
    mSubscribers.push_back(obs);
}

void Publisher::detach (Subscriber* obs) {
    mSubscribers.remove(obs);
}

bool Publisher::notify () {
    ObsList detachList;
    for (ObsListIter i (mSubscribers.begin()); i != mSubscribers.end(); ++i) {
        (*i)->update(*this);
    }
    for (ObsListIter j (detachList.begin()); j != detachList.end(); ++j) {
        detach(*j);
    }
    return true; // always return true for now
}

#endif /* _OBSERVER_H */

The Input class:
Code:
#include "SDL/SDL.h"
#include "Observer.h"

#ifndef _INPUT_H
#define _INPUT_H

class Input : public Publisher
{
private:
    Uint8 *keystates;
    SDL_Event event[100];
public:
    void processEvent(SDL_Event event);
    bool getKeystate(SDLKey keystate);
    SDL_Event getLatestEvents();
    void addToLatestEvents(SDL_Event);
    Input();
};

Input::Input(){};

void Input::processEvent(SDL_Event lastEvent)
{
    keystates = SDL_GetKeyState(NULL);
    this->addToLatestEvents(lastEvent);
    notify();
}

bool Input::getKeystate(SDLKey keystate)
{
    return keystates[keystate];
}

SDL_Event Input::getLatestEvents()
{
    return event[0];
}

void Input::addToLatestEvents(SDL_Event latestEvent)
{
    for(int i = 1; i < 100; i++){
        event[i] = event[i-1];
    }
    event[0] = latestEvent;
}

#endif /* _INPUT_H */

The unfinished Character class:
Code:
using namespace std;

#include <string>
#include <vector>
#include <iostream>
#include "SDL/SDL.h"
#include "SDL/SDL_image.h"
#include "constants.h"
#include "Input.h"
#include "Observer.h"

#ifndef _CHARACTER_H
#define _CHARACTER_H

class Character : public Subscriber
{
private:
    int offSet;
    int xVelocity;
    int yVelocity;
    int frame;
    int status;
    int width;
    int height;
    int clips;
    vector<SDL_Rect*> walk_left;
    vector<SDL_Rect*> walk_right;
    SDL_Surface* character;
    void moveLeft();
    void moveRight();
    void moveDown();
    void moveUp();
    void halt();
    Input& mInput;

public:
    explicit Character();
    virtual ~Character();
    Character(Input&,string,int,int,int);
    void handle_events();
    void show();
    virtual void update(const Publisher&);
};

Character::Character(Input& inputAddress, string filename, int w, int h, int c) : mInput(inputAddress)
{
    //Attach Character as Subscriber of Input Subject
    mInput.attach(this);
    offSet = 0;
    xVelocity = 0;
    yVelocity = 0;
    frame = 0;
    status = CHARACTER_RIGHT;
    width = w;
    height = h;
    clips = c;
    character = load_image( filename );

    if( character == NULL )
    {
        cout << "error - couldn't load image for character";
    }

    for(int i = 0; i < clips; i++){
        SDL_Rect* toAdd = new SDL_Rect;
        toAdd->x = 0;
        toAdd->y = i * height;
        toAdd->w = width;
        toAdd->h = height;
        walk_left.push_back(toAdd);
        SDL_Rect* toAdd2 = new SDL_Rect;
        toAdd2->x = width;
        toAdd2->y = i * height;
        toAdd2->w = width;
        toAdd2->h = height;
        walk_right.push_back(toAdd2);
    }
}

//Update Character's Action from Input
void Character::update(const Publisher& publisher)
{
    if (&publisher == &mInput) {
        if(mInput.getKeystate(SDLK_RIGHT)){
            this->moveRight();
        }
        if(mInput.getKeystate(SDLK_LEFT)){
            this->moveLeft();
        }
        if(mInput.getKeystate(SDLK_DOWN)){
            this->moveDown();
        }
        if(mInput.getKeystate(SDLK_UP)){
            this->moveUp();
        }
        if(!mInput.getKeystate(SDLK_RIGHT) && !mInput.getKeystate(SDLK_LEFT)){
            this->halt();
        }
    }
}

void Character::moveLeft()
{
    xVelocity = -(width / 4);
}

void Character::moveRight()
{
    xVelocity = width / 4;
}

void Character::moveDown()
{
   
}

void Character::moveUp()
{
   
}

void Character::halt()
{
    xVelocity = 0;
}

void Character::show()
{
    //If Character is moving left
    if( xVelocity < 0 )
    {
        //Set the animation to left
        status = CHARACTER_LEFT;

        //Move to the next frame in the animation
        frame++;
    }
    //If Character is moving right
    else if( xVelocity > 0 )
    {
        //Set the animation to right
        status = CHARACTER_RIGHT;

        //Move to the next frame in the animation
        frame++;
    }
    //If Character standing
    else
    {
        //Restart the animation
        frame = 0;
    }

    //Loop the animation
    if( frame >= clips )
    {
        frame = 0;
    }

    //Show the stick figure
    if( status == CHARACTER_RIGHT )
    {
        apply_surface( offSet, SCREEN_HEIGHT - height, character, screen, walk_right[ frame ] );
    }
    else if( status == CHARACTER_LEFT )
    {
        apply_surface( offSet, SCREEN_HEIGHT - height, character, screen, walk_left[ frame ] );
    }
}

Character::~Character()
{
    mInput.detach(this);
    SDL_FreeSurface( character );
}

#endif /* _CHARACTER_H */

Using this setup any class can inherit from Subscriber and have access to the current keystate array and latest 100 events. Don't know if I'll need that, though...
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic