Sign in to follow this  

Game Design Help

This topic is 4367 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

I'm trying to design a simple 3D Space game and I'm having a little issue with some design concepts. I know I need a Window Manager/Input Manager/Graphics Manager. I originally wrote the window manager as a class. I ran into problems with the window resize callback. Any time a window is resize I need to perform tasks in the graphics manager to adjust the display. So I created a callback method, but you can't use a class method for callback unless it's a static method. Now I'm thinking of either dropping the the Window/Input/Graphics classes and going with a group of functions or re-writting them as singltons. I'd like to get some feed back on what a good solution might be for this simple game.

Share this post


Link to post
Share on other sites
What API and OS are you using? It seems like if you have a main loop that checks for messages for window messages (for example the MainWindowProc() in windows that you could just make a singleton "Input manager" and then in your main message callback just have case statements deciding what to do or just forward the message recived directly to the input manager who would decide how to handle it.

For example in windows

LRESULT CALLBACK MainWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
//.... other code
Input::Instance()->ReciveMsg(uMsg, wParam, lParam);
}

//.. Input Class

void ReciveMsg(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_SIZE:
int height = HIWORD(lParam);
int width = LOWORD(lParam);
Window_Manager::Instance()->Resize(height, width)
break;
}
}



something like that, this is just something I whipped up so no gaurentees =)

Share this post


Link to post
Share on other sites
so here is how I implemented the Window Manger. I intend to use a simular design for my input manager. How does it look?

WindowManager.h

#include <string>
#include <vector>

typedef void (*resizeFunction)(int, int);

typedef struct {
// unique id
int id;
// function callback
resizeFunction function;
} resizeCallback;

using namespace std;

class WindowManager
{
private:
//constructors
WindowManager();
virtual ~WindowManager();
//static variables
static WindowManager* wm_instance;
static int wm_width;
static int wm_height;
static int wm_red;
static int wm_green;
static int wm_blue;
static int wm_alpha;
static int wm_zbuffer;
static int wm_stencilBuffer;
static bool wm_fullScreen;
static string wm_windowTitle;
static bool wm_windowCreated;
static std::vector<resizeCallback> resizeVector;
//static private methods
static void GLFWCALL windowResize(int width, int height);

public:
//singleton methods
static WindowManager* getInstance();
static void deleteInstance();
//public methods
bool createWindow(int width, int height, int red, int green, int blue, int alpha, int zbuffer, int stencilBuffer, string windowTitle, bool fullScreen);
bool createWindow(string windowTitle, bool fullScreen);
bool createWindow(bool fullScreen);
bool createWindow();
//public static methods
static int registerResizeFunction(void (*resizePointer)(int, int));
static void unregisterResizeFunction(int id);

};



WindowManager.cpp

#include <GL/glfw.h>
#include "WindowManager.h"

//Define static variables
WindowManager* WindowManager::wm_instance = NULL;
std::vector<resizeCallback> WindowManager::resizeVector;
int WindowManager::wm_width = 640;
int WindowManager::wm_height = 480;
int WindowManager::wm_red = 8;
int WindowManager::wm_green = 8;
int WindowManager::wm_blue = 8;
int WindowManager::wm_alpha = 8;
int WindowManager::wm_zbuffer = 24;
int WindowManager::wm_stencilBuffer = 0;
bool WindowManager::wm_fullScreen = false;
std::string WindowManager::wm_windowTitle = "3DSCS Window";
bool WindowManager::wm_windowCreated = false;


WindowManager::WindowManager()
{
glfwInit();
}

WindowManager::~WindowManager()
{
glfwTerminate();
resizeVector.clear();
}

WindowManager* WindowManager::getInstance()
{
//check to see if the object has been created
if (!wm_instance)
{
//create the Window Manager Object and store it
//in the instance pointer
wm_instance = new WindowManager;
}
// Return the instance pointer
return wm_instance;
}

void WindowManager::deleteInstance()
{
//check to see if the object has been created
if (wm_instance)
{
//delete the object
delete wm_instance;
wm_instance = NULL;
}
}

bool WindowManager::createWindow(int width, int height, int red, int green, int blue,
int alpha, int zbuffer, int stencilBuffer, std::string windowTitle,
bool fullScreen)
{
// Set Variables
wm_width = width;
wm_height = height;
wm_red = red;
wm_green = green;
wm_blue = blue;
wm_alpha = alpha;
wm_zbuffer = zbuffer;
wm_stencilBuffer = stencilBuffer;
wm_fullScreen = fullScreen;
wm_windowTitle = windowTitle;
//Create Window
return createWindow();
}

bool WindowManager::createWindow(string windowTitle, bool fullScreen)
{
// Set Variables
wm_fullScreen = fullScreen;
wm_windowTitle = windowTitle;
//Create Window
return createWindow();
}
bool WindowManager::createWindow(bool fullScreen)
{
// Set Variables
wm_fullScreen = fullScreen;
//Create Window
return createWindow();
}

bool WindowManager::createWindow()
{
//check to see if window created = false
if (wm_windowCreated) return false;
//initialize return value
int returnValue = 0;

//create window through GLFW
if (wm_fullScreen)
{
returnValue = glfwOpenWindow(wm_width, wm_height, wm_red, wm_green, wm_blue, wm_alpha,
wm_zbuffer,wm_stencilBuffer, GLFW_FULLSCREEN);
}
else
{
returnValue = glfwOpenWindow(wm_width, wm_height, wm_red, wm_green, wm_blue, wm_alpha,
wm_zbuffer,wm_stencilBuffer, GLFW_WINDOW);
}
//check for errors
if (!returnValue)
return false;

// Set window title
glfwSetWindowTitle(wm_windowTitle.c_str());

//register with glfw window events
glfwSetWindowSizeCallback(windowResize);

//get values from glfw
wm_red = glfwGetWindowParam(GLFW_RED_BITS);
wm_green = glfwGetWindowParam(GLFW_GREEN_BITS);
wm_blue = glfwGetWindowParam(GLFW_BLUE_BITS);
wm_alpha = glfwGetWindowParam(GLFW_ALPHA_BITS);
wm_zbuffer = glfwGetWindowParam(GLFW_DEPTH_BITS);
wm_stencilBuffer = glfwGetWindowParam(GLFW_STENCIL_BITS);

return true;
}

int WindowManager::registerResizeFunction(void (*resizePointer)(int, int))
{
resizeCallback newResizeCall;
static id = 0;
id++;
newResizeCall.id = id;
newResizeCall.function = (resizeFunction)resizePointer;
resizeVector.push_back(newResizeCall);
return id;
}

void WindowManager::unregisterResizeFunction(int id)
{
std::vector<resizeCallback>::iterator iter;

// Call functions in the vector
for (iter = resizeVector.begin(); iter != resizeVector.end(); ++iter)
{
if (iter->id == id)
{
resizeVector.erase(iter);
break;
}
}
}

void GLFWCALL WindowManager::windowResize(int width, int height)
{
std::vector<resizeCallback>::iterator iter;
wm_width = width;
wm_height = height;
// Call functions in the vector
for (iter = resizeVector.begin(); iter != resizeVector.end(); ++iter)
{
(*iter).function(width, height);
}
}

Share this post


Link to post
Share on other sites
Why is your data static if you have a singleton?
Also, you can use callbacks with class methods, e.g. using boost.function or with signals.

Yet another method is using your own callbacks (if you don't like depending on boost or a signal library):

// base class, not really necessary
class Callback {
};

// resize callback
class ResizeCallback : public Callback
{
public:
virtual void OnResize( int, int ) { }
};

// templated resize callback for non-static class members
template<class Callee>
class ResizeMemberCallback : public ResizeCallback
{
typedef void (Callee::*MethodType)( int, int );
MethodType callback;
Callee * callee;
public:
ResizeMemberCallback( Callee * c, MethodType method ) : callee( c ), callback( method )
{
assert( c && method );
}
void OnResize( int width, int height ) {
// forward the call to the class instance
(*callee.*callback)( width, height );
}
};


Unfortunalety the above forces you to use pointers in your callback vector and
the sample code doesn't provide any value-semantics. Boost.Function is far superior and should be preferred.

HTH,
Pat.

Share this post


Link to post
Share on other sites
I'd prefer to use as few third party libraries as possible. I thought about creating a callback class like you proposed as a way of not using static variables. The only questions I have is why would using static data be a bad thing for a singlton?

Share this post


Link to post
Share on other sites
Quote:
Original post by ChaosX2
The only questions I have is why would using static data be a bad thing for a singlton?

I didn't say it's a bad thing - don't put words in my mouth [smile].
I just asked why they are static, because they don't have to be.
If you use static member variables, you get a Monostate. While a singleton ensures that there is only one instance of the class, a monostate ensure that all instances share the same state (e.g. data). You have create a singleton monostate, which is - um - let's just say "not a commonly applied pattern" [wink].

In conclusion, either use a singleton, which implies that none of your data - except for the singleton instance itself - needs to be static. Or use a monostate, which implies all your data is static so you won't need a singleton instance (all instances would operate on the shared static data).

Hope that clears things up a little and sorry for the confusion,
Pat.

Share this post


Link to post
Share on other sites
I didn't mean that the way it sounded. So basically what I did was created a monostate singleton which is kind of redundant. So which is the preferred pattern, monostate or singleton? I'm thinking singleton.

By the way thanks for the feedback, thats the kind of information I was looking for.

Share this post


Link to post
Share on other sites
I've decided to go with a singleton at this point. I'm pretty weak on C++ templates and I'm having a problem getting the code you posted to work with my current method of registering the callbacks in a Vector. Could you post some example code on how to get the callback template to work with a vector?

Share this post


Link to post
Share on other sites
Changes in the header:

class WindowManager
{
private:
...
// pointers or references must be used, since polymorphism is at work here
typedef std::pair<int, ResizeCallback *> ResizeCall;
typedef std::vector<ResizeCall> CallbackVector;
// the vector now holds a pair of <id, Functor>
CallbackVector resizeVector;

// find by id functor
struct HasId {
// ctor.
HasId( int id ) : id( id ) { }
int id;
// function operator, allows a call like "HasId hasId(x); hasId( call );"
bool operator( )( ResizeCall const & call ) const { return call.first == id; }
};
...
};



Changes in the implementation:

// delete pointers here
WindowManager::~WindowManager()
{
glfwTerminate();
for ( size_t i = 0; i < resizeVector.size(); ++i )
delete resizeVector[i].second;
resizeVector.clear();
}

// note that the windowmanager takes ownership of the pointer and that
// the resize functor must be created using "new"
int WindowManager::registerResizeFunction(ResizeCallback * resizeFunctor)
{
ResizeCall newResizeCall;
static id = 0;
id++;
newResizeCall.first = id;
newResizeCall.second = resizeFunctor;
resizeVector.push_back(newResizeCall);
return id;
}

// find the documentation for "remove_if" in your STL docs
void WindowManager::unregisterResizeFunction(int id)
{
// find the callback and remove it by moving it to the end
CallbackVector::iterator iter = std::remove_if( resizeVector.begin( ),
resizeVector.end( ), HasId( id ) );
// erase it (there can only be one)
delete iter->second;
resizeVector.erase( iter );
}

void GLFWCALL WindowManager::windowResize(int width, int height)
{
CallbackVector::const_iterator iter;
wm_width = width;
wm_height = height;
// Call functions in the vector
for (iter = resizeVector.begin(); iter != resizeVector.end(); ++iter)
{
iter->second.OnResize(width, height);
}
}



Sample usage with "free functions":

struct MyResizeFunctor : public ResizeCallback {
void OnResize( int width, int height ) {
// do something
}
};
...
WindowManager::getInstance()->registerResizeFunction( new MyResizeFunctor );



Sample usage with class members:

class MyClass {
...
void Resized( int, int ) { ... }

int resized;
public:

MyClass( ) {
// register with the window manager
resized = WindowManager::getInstance()->registerResizeFunction( new ResizeMemberCallback( this, Resized ) );
// if the above doesn't compile for some reason:
// resized = WindowManager::getInstance()->registerResizeFunction( new ResizeMemberCallback<MyClass>( this, Resized ) );
}

~MyClass( ) {
// unregister
WindowManager::getInstance()->unregisterResizeFunction( resized );
}
};




HTH,
Pat.

Share this post


Link to post
Share on other sites
The code you posted doesn't seem to compile. Here are the errors I'm getting

error C2440: '=' : cannot convert from 'ResizeCallback *' to 'ResizeMemberCallback *'

int TestManager::registerResizeFunction(ResizeCallback * resizeFunctor)
{
ResizeCall newResizeCall;
static id = 0;
id++;
newResizeCall.first = id;
--> newResizeCall.second = resizeFunctor;
resizeVector.push_back(newResizeCall);
return id;
}




error C2228: left of '.OnResize' must have class/struct/union type

void GLFWCALL TestManager::windowResize(int width, int height)
{
CallbackVector::const_iterator iter;
// Call functions in the vector
for (iter = resizeVector.begin(); iter != resizeVector.end(); ++iter)
{
--> iter->second.OnResize(width, height);
}
}

Share this post


Link to post
Share on other sites
Quote:
Original post by ChaosX2
I didn't mean that the way it sounded. So basically what I did was created a monostate singleton which is kind of redundant. So which is the preferred pattern, monostate or singleton? I'm thinking singleton.


I am filled with absolute loathing for the singleton pattern.

I sometimes use the monostate one.

That said, lots of (<opinion>crazy</opinion>) people use singletons. Enough that they're likely more popular. That dosn't necessarily mean best, though.

They both achieve the same basic thing (one set of data), with different "gotchyas" - the singleton pattern enforces a rule of 1 element, and thus is harder to expand when you suddenly need 2+ of something. On the other hand, two seemingly seperate objects sharing the same data (monostate pattern) can be an unexpected/confusing, bug causing gotchya.

Share this post


Link to post
Share on other sites
Quote:
Original post by ChaosX2
The code you posted doesn't seem to compile. Here are the errors I'm getting

error C2440: '=' : cannot convert from 'ResizeCallback *' to 'ResizeMemberCallback *'
*** Source Snippet Removed ***

Are you sure your vector used ResizeCallback* and not the ResizeMemberCallback*?
You have to use the base class for the vector, not the derived class.
Quote:

error C2228: left of '.OnResize' must have class/struct/union type
*** Source Snippet Removed ***

Typo, mea culpa [smile]
This should read "iter->second->OnResize(width, height);", since the second member of the pair is a pointer.

[edit]
I just hacked together a small test app and it works without problems.
Here's the source:

#include <algorithm>
#include <cassert>
#include <iostream>
#include <string>
#include <vector>

// --------------------------------------------------------------------------------
// CALLBACK STUFF
// --------------------------------------------------------------------------------

// base class, not really necessary
class Callback {
};

// resize callback
class ResizeCallback : public Callback
{
public:
virtual void OnResize( int, int ) { }
};

// templated resize callback for non-static class members
template<class Callee>
class ResizeMemberCallback : public ResizeCallback
{
typedef void (Callee::*MethodType)( int, int );
MethodType callback;
Callee * callee;
public:
ResizeMemberCallback( Callee * c, MethodType method ) : callee( c ), callback( method )
{
assert( c && method );
}
void OnResize( int width, int height ) {
// forward the call to the class instance
(*callee.*callback)( width, height );
}
};

// --------------------------------------------------------------------------------
// STRIPPED DOWN WINDOW MANAGER - JUST FOR TESTING CALLBACKS
// --------------------------------------------------------------------------------

class WindowManager
{
private:
// pointers or references must be used, since polymorphism is at work here
typedef std::pair<int, ResizeCallback *> ResizeCall;
typedef std::vector<ResizeCall> CallbackVector;
// the vector now holds a pair of <id, Functor>
CallbackVector resizeVector;

// find by id functor
struct HasId {
// ctor.
HasId( int id ) : id( id ) { }
int id;
// function operator, allows a call like "HasId hasId(x); hasId( call );"
bool operator( )( ResizeCall const & call ) const { return call.first == id; }
};
// singleton stuff
WindowManager() { }
// hide assignment operator
WindowManager const & operator = ( WindowManager const & );

public:
~WindowManager();

// register callbacks
int registerResizeFunction(ResizeCallback * resizeFunctor);
// unregister callbacks
void unregisterResizeFunction(int id);
// invoke callbacks
void windowResize(int width, int height);

// singleton stuff
static WindowManager * getInstance( ) { static WindowManager instance;; return &instance; }
};

// delete pointers here
WindowManager::~WindowManager()
{
std::clog << "Deleting " << resizeVector.size() << " windowResize callback(s)\n";
for ( size_t i = 0; i < resizeVector.size(); ++i )
delete resizeVector[i].second;
resizeVector.clear();
}

// note that the windowmanager takes ownership of the pointer and that
// the resize functor must be created using "new"
int WindowManager::registerResizeFunction(ResizeCallback * resizeFunctor)
{
ResizeCall newResizeCall;
static id = 0;
id++;
newResizeCall.first = id;
newResizeCall.second = resizeFunctor;
resizeVector.push_back(newResizeCall);
return id;
}

// find the documentation for "remove_if" in your STL docs
void WindowManager::unregisterResizeFunction(int id)
{
// find the callback and remove it by moving it to the end
CallbackVector::iterator iter = std::find_if( resizeVector.begin( ),
resizeVector.end( ), HasId( id ) );
// erase it (there can only be one)
if ( iter != resizeVector.end( ) ) {
std::clog << id << " has been removed\n";
delete iter->second;
std::swap( resizeVector.back( ), *iter );
resizeVector.pop_back( );
}
else std::clog << id << " was not found\n";
}

// invoke callbacks
void WindowManager::windowResize(int width, int height)
{
CallbackVector::const_iterator iter;
std::clog << "Invoking " << resizeVector.size() << " windowResize callback(s)\n";
// Call functions in the vector
for (iter = resizeVector.begin(); iter != resizeVector.end(); ++iter)
{
iter->second->OnResize(width, height);
}
}

// wrapped free function
struct MyResizeFunctor : public ResizeCallback {
void OnResize( int width, int height ) {
std::clog << "MyResizeFunctor: " << width << "x" << height << "\n";
}
};

// --------------------------------------------------------------------------------
// CALLBACK EXPAMPLES
// --------------------------------------------------------------------------------


// class with self-registering callback
class MyClass {
void Resized( int w, int h ) { std::clog << "MyClass: " << w << "x" << h << "\n"; }
int resized;
public:

MyClass( ) {
resized = WindowManager::getInstance()->registerResizeFunction( new ResizeMemberCallback<MyClass>( this, Resized ) );
}

~MyClass( ) {
// unregister
WindowManager::getInstance()->unregisterResizeFunction( resized );
}
};

// --------------------------------------------------------------------------------
// DRIVER PROGRAM
// --------------------------------------------------------------------------------

int main()
{
// test self-registering
MyClass clazz;
// test dynamic register/unregister
MyClass * dynamic = new MyClass;

// add "free function"
WindowManager::getInstance()->registerResizeFunction( new MyResizeFunctor );
// invoke
WindowManager::getInstance()->windowResize( 10, 120 );

// unregister
delete dynamic;

// invoke
WindowManager::getInstance()->windowResize( 50, 5 );

// "clazz" should remove itself upon destruction

return 0;
}


Rip it apart and integrate what you need - it's a gift for you [smile]
Merry christmas!
[/edit]


[Edited by - darookie on December 23, 2005 9:45:18 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by darookie
Are you sure your vector used ResizeCallback* and not the ResizeMemberCallback*?
You have to use the base class for the vector, not the derived class.


D'oh...

Quote:
Original post by darookie
Typo, mea culpa [smile]
This should read "iter->second->OnResize(width, height);", since the second member of the pair is a pointer.


I should have caught that one...

Quote:
Original post by darookie
I just hacked together a small test app and it works without problems.
Here's the source:
*** Source Snippet Removed ***
Rip it apart and integrate what you need - it's a gift for you [smile]
Merry christmas!


Thanks so much...

Share this post


Link to post
Share on other sites
darookie your code works fine on windows but when I compile it on Mac OS X (Xcode) I recieve the following error:

Quote:
error: no matching function for call to 'ResizeMemberCallback<MyClass>::ResizeMemberCallback(MyClass* const, <unknown type>)'

note: candidates are: ResizeMemberCallback<T>::ResizeMemberCallback(T*, void (T::*)(int, int)) [with T = MyClass]


Is this standard C++?

Share this post


Link to post
Share on other sites
never mind I found the error.

This

resized = WindowManager::getInstance()->registerResizeFunction( new ResizeMemberCallback<MyClass>( this, Resized ) );



should be this

resized = WindowManager::getInstance()->registerResizeFunction( new ResizeMemberCallback<MyClass>( this, &MyClass::Resized ) );

Share this post


Link to post
Share on other sites

This topic is 4367 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this