Jump to content
  • Advertisement
Sign in to follow this  
Mybowlcut

Communicating through objects

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

Hey. I have just changed my whole structure for dealing with Images. Now each Image draws itself and updates itself (if it's a Button and the mouse is over it, it will change to an OVER state, etc.). Because I changed this... a lot of other things got changed. Now I'm left with objects that maintain themselves, which is great... but I don't know how to get anything from these objects... such as determining when to start a new game, view the instructions, etc:
RETURN_CODE menu(Draw_Engine& DE, Audio_Engine& AE, SDL_Event& event_)
{
    // Create the menu screen.
    Screen menu_screen("Screens/Menu_Screen/");

    bool exit_game = false;
    while(!exit_game)
    { // While the user hasn't exit

        while(SDL_PollEvent(&event_))
	{ // While there's events to handle

            if(event_.type == SDL_QUIT)
	    { // If the user has Xed out
                exit_game = true;
		break;
            }

	    // if user pressed "new game"
	    // call game()
	    // else if user pressed "instructions"
	    // call instructions()
	    // else if user pressed "exit"
	    // exit game

	    menu_screen.Update(event_);
	    menu_screen.Draw(DE.Get_Screen());

	    if(!DE.Draw_Screen())
	    {
	        return RC_ERROR;
	    }
        }
    }

    return RC_EXIT_PROGRAM;
}

class Screen : public Drawable, public Updateable, public Uncopyable
{
public:
	Screen();
	Screen(const std::string& directory);
	virtual ~Screen();

	virtual void Draw(SDL_Surface* screen);
	virtual void Update(const SDL_Event& event_);
protected:
	virtual void Load(const std::string& directory);
	std::vector<Base_XImage*> elements;
};



class Base_XImage : public Drawable, public Updateable, public Cloneable
{
public:
	Base_XImage();
	Base_XImage(
		const std::string& file_name_,
		int position_x,
		int position_y,
		bool is_colour_key_,
		SDL_Colour colour_key_ = SDL_Wrappers::colour(0,0,0));
	Base_XImage(const Base_XImage& rhs);
	virtual ~Base_XImage();
	
	Base_XImage& operator=(const Base_XImage& rhs);

	const std::string& Get_File_Name() const;
	const SDL_Rect& Get_Position() const;
	bool Is_Colour_Key() const;
	const SDL_Colour& Get_Colour_Key() const;
	
	virtual void Load();
	virtual void Draw(SDL_Surface* screen) = 0;
	virtual void Update(const SDL_Event& event_) = 0;
	virtual Base_XImage* Clone() const = 0;
protected:
	static void Check_Opening_Tag(const std::string& buffer);
	static void Check_Closing_Tag(const std::string& buffer);

	std::string file_name;
	SDL_Rect position;
	bool is_colour_key;
	SDL_Colour colour_key;
	SDL_Surface* surface;
};


I don't really want to have huge switch statements throughout my code checking to see if a certain button was pressed to start a new game... so I was wondering if anyone has a solution to this? Perhaps a Listener class or something? I'm really not that great with patterns/design like this and so I'm relying greatly on the suggestions of posters. Cheers.

Share this post


Link to post
Share on other sites
Advertisement
Take a look at the Observer pattern. The basic idea is that each button should maintain a list of "observers". These are objects that are "observing" the button and want to know when it's state changes. Whenever the button's state changes, it will iterate over it's list of observers and notify each one about the change, passing it any relevant information.

The strength of this approach is that the button doesn't know who is observing it (it's observers list will consist of either callback functions or pointers to an interface called Observer or something similar), so you can reuse it in many applications and your GUI code will not be mangled with your application code. BTW, if you want to go with the callback function approach, you might want to take a look at boost.function.

Hope this helps.

Share this post


Link to post
Share on other sites
Cheers, Gage.

I've got something like this:
class Observer
{
public:
virtual ~Observer() {}
virtual void Mouse_Click() = 0;
virtual void Mouse_Over() = 0;
};


What do you think? Also, is it worth making a Subject class?
class Subject
{
public:
virtual ~Subject() {}
protected:
std::vector<Observer*> observers;
};

Share this post


Link to post
Share on other sites
Made this:
class Observer
{
public:
virtual ~Observer() {}
virtual void Notify() = 0;
};

class Subject
{
public:
virtual ~Subject() {}
virtual void Add_Observer(Observer* observer)
{
observers.push_back(observer);
}
virtual void Notify_Observers() = 0;
protected:
std::vector<Observer*> observers;
};

class Image : public Subject
{
public:
Image() {}
virtual ~Image() {}

void Update(const std::string& s)
{
if(s == "mouse_click")
{
Notify_Observers();
}
}

virtual void Notify_Observers()
{
std::for_each(observers.begin(), observers.end(),
std::mem_fun(&Observer::Notify));
}
};

class Test_Observer : public Observer
{
public:
Test_Observer() {}
virtual ~Test_Observer() {}

virtual void Notify()
{
std::cout << "Observer notified." << std::endl;
}
};

int main()
{
Image image;

Test_Observer to1;
Test_Observer to2;

image.Add_Observer(&to1);
image.Add_Observer(&to2);

std::string input;
while(input != "exit")
{
std::cin >> input;
image.Update(input);
}

return 0;
}

Just don't know how to apply it to my code... haha. Always a good sign. Also, the calls to Add_Observer are so... hard-code-ish. Up until now I've been reading stuff from files and everything looks after itself... is there anyway to hide this Add_Observer crap, possibly without hard-coding it?

Share this post


Link to post
Share on other sites
Right now your observers have no way of knowing the reason for the notification. You should pass some info in the Notify() function. For example, the subject (e.g., the button) can pass it the 'this' pointer (so the observer knows who caused the notification. This also allows an observer to observe multiple subjects), and maybe some additional data, like the reason for the notification (maybe a constant like MOUSE_OVER or BUTTON_DOWN). If you want this to be generic, you can pass the latter as a void pointer which the observer will cast.

BTW, the Notify_Observers() function will probably be the same for all subjects, so it probably belongs in Subject and not in the derived class.

As for the Add_Observer() calls, I'm not really sure how you should handle them. Maybe if you give some more details, I (or someone else) might have an idea.

EDIT: You might also find this helpful. It contains good explanations and source code.

Share this post


Link to post
Share on other sites
Ok, I've made the header files for my actual project... but before I can continue about the original issue, I'm getting stupid circular dependency errors:
#ifndef OBSERVABLE_H
#define OBSERVABLE_H

#include <vector>

#include "Observer.h"

class Observer;

// Not an interface but complements the Observer interface.
class Observable
{
public:
typedef enum OBSERVABLE_EVENT
{
LMB_CLICK,
RMB_CLICK,
MOUSE_OVER
};

virtual ~Observable() {}
virtual void Add_Observer(Observer* observer);
void Notify_Observers(Observable* observable, OBSERVABLE_EVENT oe);
protected:
std::vector<Observer*> observers;
};

#endif

#include "stdafx.h"

#include "Observable.h"

void Observable::Notify_Observers(Observable* observable, OBSERVABLE_EVENT oe)
{
std::vector<Observer*>::iterator it = observers.begin();
for(; it != observers.end(); ++it)
{ // Notify each observer of the event, passing the updated observable.
(*it)->Notify(observable, oe);
}
}
#ifndef OBSERVER_H
#define OBSERVER_H

#include "Observable.h"

class Observable;

class Observer
{
public:
virtual ~Observer() {}
virtual void Notify(Observable* observable, OBSERVABLE_EVENT oe) = 0;
};

#endif
Ugh... one of the very few things that I hate about C++.

Share this post


Link to post
Share on other sites
Observer.h doesn't need to include Observable.h.

Also, by putting the OBSERVABLE_EVENT enumeration in the base class, you have made the class non-reusable. You can solve this by moving the enumeration to the derived class and making the second parameter to Notify_Observers() a void pointer which the observer will cast to the appropriate type (in this case, to OBSERVABLE_EVENT).

Share this post


Link to post
Share on other sites
Quote:
Original post by Gage64
Observer.h doesn't need to include Observable.h.
Ok.
Quote:
Also, by putting the OBSERVABLE_EVENT enumeration in the base class, you have made the class non-reusable. You can solve this by moving the enumeration to the derived class and making the second parameter to Notify_Observers() a void pointer which the observer will cast to the appropriate type (in this case, to OBSERVABLE_EVENT).
Can you please give me an example of casting a void pointer to an enum? I've never had to cast a void pointer... yeah weird I know. Haha.

Cheers for your help so far. :)

Edit: Figured it out... gosh I'm a smart cookie! Haha. Does this look ok?
void Screen::Notify(Observable* observable, void* oe)
{
Base_XImage::OBSERVABLE_EVENT* event_ = (Base_XImage::OBSERVABLE_EVENT*)oe;
// Do stuff...
}



[Edited by - Mybowlcut on February 6, 2008 8:18:16 AM]

Share this post


Link to post
Share on other sites
Ok.. so I've got all that up and running.. but I'll have to explain my situation so you can give me your opinion on where to go from here.

All my screen class is is an abstraction of elements drawn at once. The elements are Base_XImage pointers. My screen class definition looks like this:
Screen::Screen()
{
}

Screen::Screen(const std::string& directory)
{
Load(directory);
}

Screen::~Screen()
{
for(std::vector<Base_XImage*>::iterator it = elements.begin();
it != elements.end(); ++it)
{
Base_XImage* b = *it;
delete b;
}
}

void Screen::Draw(SDL_Surface* screen)
{
std::for_each(
elements.begin(),
elements.end(),
std::bind2nd(std::mem_fun(&Base_XImage::Draw), screen));
}

void Screen::Update(const SDL_Event& event_)
{
std::vector<Base_XImage*>::iterator it = elements.begin();
for(; it != elements.end(); ++it)
{
(*it)->Update(event_);
}
}

void Screen::Notify(Observable* observable, void* oe)
{
Base_XImage::OBSERVABLE_EVENT* event_ = (Base_XImage::OBSERVABLE_EVENT*)oe;
}

void Screen::Load(const std::string& directory)
{
std::ifstream image_file;
IO::open_read_file(image_file, directory + IMAGES_FN);
IO::read_data_clone<XImage, std::vector<Base_XImage*> >(elements, image_file);
image_file.close();

std::ifstream button_file;
IO::open_read_file(button_file, directory + BUTTONS_FN);
IO::read_data_clone<XButton, std::vector<Base_XImage*> >(elements, button_file);
button_file.close();

std::ifstream audible_button_file;
IO::open_read_file(audible_button_file, directory + AUDIBLE_BUTTONS_FN);
IO::read_data_clone<Audible_XButton, std::vector<Base_XImage*> >(elements, audible_button_file);
audible_button_file.close();

std::ifstream draggable_button_file;
IO::open_read_file(draggable_button_file, directory + DRAGGABLE_BUTTONS_FN);
IO::read_data_clone<Draggable_XButton, std::vector<Base_XImage*> >(elements, draggable_button_file);
draggable_button_file.close();
}

But, going back to my first code sample in my original post, I don't know how to do the parts I have commented out - ie:
// if user pressed "new game"
// call game()
// else if user pressed "instructions"
// call instructions()
// else if user pressed "exit"
// exit game

This is where I need the most help... haha.

Cheers.

Share this post


Link to post
Share on other sites
Depends on how you are doing your input, however for the sake of arguement lets say you are using a mouse, the steps would be;

- Detect mouse event (I recommend 'mouse up' as that's how GUIs like Windows work)
- Find out which component is under the mouse when the even happens
- Call the component's "selected" event function

This 'selected' function will then do the work of "somehow" moving the game to the selected screen (game, instructions, etc).

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!