Reusability

Started by
8 comments, last by Shakedown 16 years, 1 month ago
Howdy. :) I'm making a Pong clone, and I'm aiming to have reusability throughout my mini-engine, and I've stumbled upon a teeny problem. I want to be able to move player 1's paddle up and down using W and S, respectively. Events:

// ....................
// Events.h
// ....................
#ifndef EVENTS_H
#define EVENTS_H
#include "SDL/SDL.h"

namespace Cratos
{
	class EventHandler
	{
	private:
		SDL_Event events;
		Uint8 *keyStates;
	public:
		EventHandler();
		// Returns false if exiting
		bool handleEvents();
	};
}

#endif EVENTS_H

// ....................
// Events.cpp
// ....................
#include "Events.h"

namespace Cratos
{
	EventHandler::EventHandler()
	{
	}

	bool EventHandler::handleEvents()
	{
		keyStates = SDL_GetKeyState(NULL);
		while (SDL_PollEvent(&events))
		{
			if (events.type == SDL_KEYDOWN)
			{
				if (keyStates[SDLK_ESCAPE])
				{
					return false;
				}

				if (keyStates[SDLK_s])
				{// How do I make this part reusable?
				}// What can I do?
			}

			if (events.type == SDL_QUIT)
			{
				return false;
			}
		}
		
		// Successfully handled input; not exiting
		return true;
	}
}

And the EventHandler system is linked to the Core:

// ....................
// Core.h
// ....................
#ifndef CORE_H
#define CORE_H
#include <string>
#include <vector>
#include "SDL/SDL.h"
#include "Events.h"

namespace Cratos
{
	struct windowInfo
	{
		std::string title;
		std::string icon;
		bool fullscreen;
		short fps;
	};

	class Core
	{
	private:
		windowInfo window;
		SDL_Surface *screen;
		std::vector<SDL_Surface *> objects;
		EventHandler eventHandler;
		int screenWidth;
		int screenHeight;
		short screenBPP;
	public:
		Core();
		~Core();
		SDL_Surface *loadImage(std::string filename, Uint8 keyR = 0xFF, Uint8 keyG = 0, Uint8 keyB = 0xFF);
		// Leave destination as 0 if drawing to screen
		// Pass an SDL_Surface to the surface you are drawing to something other than screen
		void applySurface(int x, int y, SDL_Surface *source, SDL_Surface *destination = 0);
		void flip();
		void start();
	};
}

#endif

// ....................
// Core.cpp
// ....................
#include "Core.h"
#include "SDL/SDL_image.h"

namespace Cratos
{
	Core::Core() : screenWidth(800), screenHeight(600), screenBPP(32)
	{
		window.title = "Bong!";
		window.icon = "";
		window.fullscreen = false;
		window.fps = 60;

		SDL_Init(SDL_INIT_EVERYTHING);
		SDL_WM_SetCaption(window.title.c_str(), window.icon.c_str());
		screen = SDL_SetVideoMode(screenWidth, screenHeight, screenBPP, SDL_SWSURFACE);
	}

	Core::~Core()
	{
		for (unsigned int i = 0; i < objects.size(); ++i)
		{
			SDL_FreeSurface(objects);
			objects.erase(objects.begin() + i);
		}

		SDL_Quit();
	}

	SDL_Surface *Core::loadImage(std::string filename, Uint8 keyR, Uint8 keyG, Uint8 keyB)
	{
		SDL_Surface *loadedImage = NULL;
		SDL_Surface *optimizedImage = NULL;
		loadedImage = IMG_Load(filename.c_str());
		if (loadedImage != NULL)
		{
			optimizedImage = SDL_DisplayFormat(loadedImage);
			SDL_SetColorKey(optimizedImage, SDL_SRCCOLORKEY, SDL_MapRGB(optimizedImage->format, keyR, keyG, keyB));
			objects.push_back(optimizedImage);
			SDL_FreeSurface(loadedImage);
			return objects.back();
		}

		return NULL;
	}

	void Core::applySurface(int x, int y, SDL_Surface *source, SDL_Surface *destination)
	{
		if (destination == NULL)
		{
			destination = screen;
		}

		SDL_Rect offset;
		offset.x = x;
		offset.y = y;

		SDL_BlitSurface(source, NULL, destination, &offset);
	}

	void Core::start()
	{
		bool quit = false;
		while (quit == false)
		{
			int starting = SDL_GetTicks();
			
			if (!eventHandler.handleEvents())
			{
				quit = true;
			}

			SDL_Flip(screen);

			// Regulate frame rate
			if (starting < 1000 / window.fps)
			{
				SDL_Delay((1000 / window.fps) - starting);
			}
		}
	}
}

In the if (keyStates[SDLK_s] section, I don't know how I can make the Player 1 paddle move while still maintaining reusability. Here's Object (I derive Paddle from Object; Paddle doesn't really have any implementation yet):

// Object.h
#ifndef OBJECT_H
#define OBJECT_H
#include "SDL/SDL.h"

namespace Cratos
{
	enum movementType {RELATIVE, ABSOLUTE};
	class Object
	{
	protected:
		int xPos;
		int yPos;
		int width;
		int height;
		SDL_Surface surface;
	public:
		Object();
		void move(int x, int y, movementType type = RELATIVE);
	};
}

#endif OBJECT_H

What can I do to make player 1's paddle move down when he/she presses S, but still retain reusability for later use? Also, is there anything "wrong" or "bad practice" within my code? I would appreciate any help at all. Thanks in advance! :D
Jack
Advertisement
I'm no guru, so take this with a large grain of salt, but the first thing that comes to my mind is to create a class or function that is game specific to handle events.

However, after writing this example... it feels a bit overdone and redundant, because at some point, you have to get to the details. So I guess one question would be, what responsibilities do you want to assign to the EventHandler class? If it is not responsible for game specific input handling, then what is it responsible for?

Also, just to restate the disclaimer, I am, in many ways, still a novice in design myself, but I hope this will at least give you something to think about.



// -------------------// IGameEventHandler.h // -------------------class IGameEventHandler{public:  virtual void handleEvents(Uint8 *keyStates) = 0;};// -------------------// PongEventHandler.h // -------------------#include "IGameEventHandler.h"class PongEventHandler: public IGameEventHandler {public:  virtual void handleEvents(Uint8 *keyStates);};// --------------------// PongEventHandler.cpp// --------------------#include "PongEventHandler.h"void       // alternatively you could use bool, if you need toPongEventHandler::handleEvents(Uint8 *keyStates){  if(keyStates[SDLK_s])  {    // do handling code  }  //...}// --------------------// Events.h// --------------------#include "SDL/SDL.h"class IGameEventHandler;namespace Cratos{	class EventHandler	{	private:		SDL_Event events;		Uint8 *keyStates;                IGameEventHandler* m_pGameHandler;	public:		EventHandler();                EventHandler(IGameEventHandler*);		// Returns false if exiting		bool handleEvents();	};}// ------------------------// Event.cpp// ------------------------#include "Events.h"#include "IGameEventHandler.h"namespace Cratos{	EventHandler::EventHandler()	{               m_pGameHandler = NULL;	}	EventHandler::EventHandler(IGameEventHandler* Handler)	{               m_pGameHandler = Handler;	}	bool EventHandler::handleEvents()	{		keyStates = SDL_GetKeyState(NULL);		while (SDL_PollEvent(&events))		{			if (events.type == SDL_KEYDOWN)			{				if (keyStates[SDLK_ESCAPE])				{					return false;				}				if(m_pGameHandler)                                        m_pGameHandler->handleEvents(keyStates);			}			if (events.type == SDL_QUIT)			{				return false;			}		}				// Successfully handled input; not exiting		return true;	}}
Naxos, I want to thank you for taking the time to reply. I really appreciate it :)

I will try something like the code you have provided. And I want EventHandler to be able to handle user input, and direct that information somewhere, somehow (move player 1 paddle down when S is pressed). How could I go about doing that? Do I pass an instance of Paddle to handleEvents() and call Paddle->move(0, 1);? I don't know why, but it gives me a funky feeling to do that.

Thanks again! :)
Jack
Typically you would want to create boolean values for each of the keys, i.e.
bool wPressed;bool sPressed;bool upPressed;bool downPressed;


These should be available to both your EventHandler and your function that moves the paddle.

Now, when the user creates input (presses a key), the EventHandler will pick it up. The EventHandler then should see if it is a KeyPress and what key was pressed, then it will set the appropriate boolean value to true, i.e.

if(keyIsPressed(UP_ARROW)) upPressed = true;


Now, your handler should also pick up KeyReleased events. In that case, the handler determines what key was released, and sets the appropriate boolean value to false;

That should be all that your EventHandler does. Now, to move your paddle, you simply make a check in your game loop, i.e.

if(upPressed) paddle.move(up)


To sum it up, you don't want your EventHandler moving your paddle, you want something else to decide what to do with the information the EventHandler can provide.

Hope this helps at all.
Thanks for your response, Shakedown, but I've already gotten that part down. :)

My real question is: would I receive user input with handleEvents() and send some sort of message to the paddle that it needs to move in some direction? How could I go about achieving this?

Thanks in advance! :)
Jack
It seems to me the thing you're looking for is the observer pattern.

Create an abstract base class EventObserver, with (perhaps amongst others) a method:

notifyKeyDown( Uint8* keyStates );


Furthermore add methods to your EventHandler like:
addEventObserver( EventObserver* observer );hasEventObserver( EventObserver* observer );removeEventObserver( EventObserver* observer );


Slightly adapt your handleEvents function to call notifyKeyDown on all registered observers.

Than your paddle class would inherit from EventObserver and implement notifyKeyDown, and react to W and S presses.

The beauty of this solution lies in the fact that you can register an arbitrary number of classes to respond to keypresses, without the need for EventHandler to have any knowledge about them. EventHandler will only be dependent on the EventObserver class.

Hope this is of help,
Tristan

ps. for more information about observers: http://en.wikipedia.org/wiki/Observer_pattern

Quote:Original post by Sanctux
Thanks for your response, Shakedown, but I've already gotten that part down. :)

My real question is: would I receive user input with handleEvents() and send some sort of message to the paddle that it needs to move in some direction? How could I go about achieving this?

Thanks in advance! :)


Haha, sorry, I quickly glanced over it and thought I saw something else.

Your move function takes (x,y). Does this function place the paddle at this position (i.e. xPos = x, yPos = y)?

Quote:
Paddle->move(0, 1); I don't know why, but it gives me a funky feeling to do that.


Why? This is how movement is usually calculated, at least in 3D, but the same can work for 2D. The reason for doing this is that a function can calculate the sum of the forces acting on your object (for your case the only force would user input), then with the sum of the forces it can apply the force to the object at its current position, and the object will move accordingly.

If you were to use the move() function in this way, you could create a const Vector2(x, y) for each of the desired directions you want your paddles to move. In this case, your two vectors would be:
const Vector2 UP(0, -1);
const Vector2 DOWN(0, 1);

Then, using what I explained in my previous post, your code would look like this:

if(upPressed)   paddle->move(Vector2::UP);if(downPressed) paddel->move(Vector2::DOWN);


This should achieve the re-usability you're looking for; you only need to write the move() function once to take as input a Vector2 (or just an x and y), and then apply those values to the objects current position. Plus, you now have 2 const Vector2's (UP and DOWN) that can be applied to anything that has a move function (like your ball).

Actually, in the case of the ball you probably wouldn't be using cardinal directions (left, right, up, down) because they only act in one direction, but rather you would have a function that calculates both the x and y values and passes them to the move() function.

Hope this is more helpful than my previous post! [smile] (If it's not...then, I will re-read this thread in the morning when I have a fresh mind)

Thank you, Tristan85. I'll look into the Observer Pattern :D

Thank you too, Shakedown ^^ but when I said I felt a bit funky doing "that", I meant sending a message to the paddle. Sorry if I am a bit ambiguous, heh.

But here's my implementation of move() in Object.h:
// ...code here... blah blah cut off for simplicityenum movementType {RELATIVE, ABSOLUTE};// ...some more code here... blah blah cut off for simplicity againvoid move(int x, int y, movementType type = RELATIVE);// ...some more code here... you know the rest...


and here it is in Object.cpp
void Object::move(int x, int y, movementType type){	if (type == RELATIVE)	{		xPos += x;		yPos += y;	}	else	{		xPos = x;		yPos = y;	}}


Feels a bit funky to me, once again, but it lets me position objects absolutely. Is this a bad practice?


Once again, I thank you all for being so nice and helpful! ^_^
I'll be back with more questions on the Observer Pattern if I happen to run into any. *evil laugh*

EDIT: I've searched for simple examples, but the simplest ones I found were a bit too much for me. If it's not too much trouble, perhaps show me something extremely simple?

[Edited by - Sanctux on March 5, 2008 4:40:59 AM]
Jack
I haven't had the pleasure of implementing an observer in a real program, but I took a shot at it after reading the wikipedia page. I hope it is enlightening ( and an appropriate example :] )

//----------------------// IKeyObserver.h//----------------------class IKeyObserver{public:	virtual void notifyKeyStates(Uint8*) = 0;};//----------------------// StringBuilder.h//----------------------class StringBuilder: public IKeyObserver{public:	StringBuilder();	virtual void	notifyKeyStates(Uint8*);	std::string	getString();	void		resetString();	void		buildOn();	void		buildOff();	private:	std::string mString;	bool mBuilding;	char sdlKeyToChar(int);};//------------------------// StringBuilder.cpp//------------------------#include "StringBuilder.h"StringBuilder::StringBuilder(){	mString = "";}voidStringBuilder::notifyKeyStates(Uint8 *keyStates){	if(mBuilding)	{		// add character pressed to string		for(int i=SDLK_a; i<SDLK_z; ++i){			if(keyStates)				mString += sdlKeyToChar(i);		}	}}//other functions omitted for brevity


A modified version of your eventHandler class
// ....................// Events.h// ....................#ifndef EVENTS_H#define EVENTS_H#include "SDL/SDL.h"#include "IKeyObserver.h"namespace Cratos{	class EventHandler	{	private:		SDL_Event events;		Uint8 *keyStates;		//Contains list of IKeyObserver*		//   Use whatever list you like, STL or otherwise		//   hopefully the 'methods' of the 'list' are obvious in their use in the .cpp file.		ObserverList keyObserverList; 	public:		EventHandler();		// Returns false if exiting		bool handleEvents();		void addKeyObserver(IKeyObserver*);		void removeKeyObserver(IKeyObserver*);	};}#endif EVENTS_H// ....................// Events.cpp// ....................#include "Events.h"namespace Cratos{	EventHandler::EventHandler()	{	}	bool EventHandler::handleEvents()	{		keyStates = SDL_GetKeyState(NULL);		while (SDL_PollEvent(&events))		{			if (events.type == SDL_KEYDOWN)			{				if (keyStates[SDLK_ESCAPE])				{					return false;				}				for(int i=0;i<keyObserverList.size();++i)					keyObserverList.get_at_index(i)->notifyKeyStates(keyStates);			}			if (events.type == SDL_QUIT)			{				return false;			}		}				// Successfully handled input; not exiting		return true;	}	void EventHandler::addKeyObserver(IKeyObserver* obs)	{		if( !keyObserverList.exists_in_list(obs) )			keyObserverList.add(obs);	}	void EventHandler::removeKeyObserver(IKeyObserver* obs)	{		if( keyObserverList.exists_in_list(obs) )			keyObserverList.remove(obs);	}			}
Although the Observer Pattern is a very powerful design, I'm not convinced it is appropriate in this situation. If you only have 2 Observers (both paddles), and they will ALWAYS be Observing the Subject, and there will not be any new Observers added or removed, then implementing the pattern might be adding too much complexity.

But, to create a simple Subject/Observer relationship, here's an idea:

For re-usability, create an Observable class:
class Observable { private:  ObserverList list; // Some STL container public:  void AddObserver(Observer o){ list.add(o); } // Observer will also be manually created  void RemoveObserver(Observer o){ list.remove(o); }  void RemoveAllObservers(){ list.clear(); }  // This function is the key to the observer pattern; you can either use the  //"Push" form by sending to the Observers a piece of data, or use the "Pull"  //form by only sending yourself (the Observable), and letting your Observers  //"pull" the information they need from you.  These "pull" functions are simply  //getter methods provided by the class that is inheriting this class.  void NotifyObservers(Anything* anyData){ for_each(Observer o in list){ o.update(this, anyData); } // Push  void NotifyObservers(){ for_each(Observer o in list){ o.update(this); } // Pull};


Now, you create an Observer class:
class Observer { public:  virtual void update(Observable o, Anything* someData) = 0;  virtual void update(Observable o) = 0;};


Now, your EventHandler would inherit from Observable, and your Paddle would inherit from Observer. Then, you would have to define the update methods in your Paddle class. One way you could do this would be to do something like this:

class EventHandler : public Observable { private:  bool upPressed;  bool downPressed;  bool wPressed;  bool sPressed; public:  bool isUpPressed(){ return upPressed; }  bool ...  // Whenever a key is pressed, you want to update your bool values, and then notify all your Observers that a change has been made  void handleEvents(){    //all your stuff here    this->NotifyObservers(); // Pull method};


Now in your Paddle class
class Paddle : public Object, public Observer { public:  void update(Observable o){    // You're going to have to do some type-checking here, but the idea is   if(o.isUpPressed()) this->move(0, -1); } };


REMEMBER, you must add the paddles as Observers to your Observable, or else nothing will happen. So, in main, after you've created your Observable and Observers, just do this:

int main() {...EventHandler eh;Paddle players;Paddle cpus;eh.AddObserver(players);eh.AddObserver(cpus);...}


Now whenever EventHandler changes (a key is pressed), the paddles will be notified and they can re-act accordingly (however you define their update() functions).

If you wanted to use the "Push" method, you could send a Vector2(x,y) as the Anything* someData after calculating the sum of the forces, but then this information would be given to ALL observers, not just the player's paddle, so it would move the CPU paddle in the same direction, so you wouldn't want to do this. Or, just have 1 Observer, the player's paddle.

Now you have 2 re-usable classes, Observable and Observer, that you can use for all sorts of things. Re-usability! [smile]

Hope this gives you some more ideas. Now that I've spent all the time writing this up, I've convinced myself that this would be an interesting and fun thing for you to implement! This achieves your re-usability, and you're no longer sending a message from the EventHandler to the Paddle telling it to move; instead this allows the Paddle to handle its own movement. Now, you can add collision detection in the Paddle's move() function, without having to modify your EventHandler. Have fun!

[Edited by - Shakedown on March 5, 2008 1:04:02 PM]

This topic is closed to new replies.

Advertisement