The Player

To demonstrate how the Observer pattern can be used, we will look at a common situation in games. We will have a Player with some health that will need to be shared. The health of the Player can often be used for many things in a game. The value of health might be displayed as part of the HUD. It can also be displayed as a colored health bar in either the HUD or directly at the top or at the bottom of the Player. Additionally, the game may switch to a game over screen when the Player health is at or below zero.

These display elements, as well as the stage switching mechanisms, are dependent directly on the Player's health. Since it is very unlikely that these variables are all in the same scope, it would take some work if we tried to implement this via polling. In that case, each object would need to find the Player and ask for the health value. Since the health of the Player is unlikely to change every frame, most of this work is being wasted. Instead, we will make the Player derive from the Subject so it can notify all Observers when the health has changed:

class Player: public Subject 
{
public:
Player(float maxHealth);
void AdjustHealth(float health);
virtual void RegisterObserver(Observer* pToAdd);
virtual void UnregisterObserver(Observer* pToRemove);
virtual void Notify(void);
private:
typedef std::vector<Observer*> ObserverVec;

float m_maxHealth;
float m_health;
ObserverVec m_observers;
};

This Player class is very simple. Since this is only an example, we will only be focusing on the health. In the constructor, we can set the max health. The AdjustHealth method will be used to make the health change. Of course, we also implement each of the base class virtual methods. In the private section, we use an STL vector to keep track of our Observers. We also store our constructor value, as well as a variable for our current health:

Player::Player(float maxHealh): 
m_maxHealth(maxHealth),
m_health(maxHealth)
{
}

The Player constructor sets data passed in by the user. Since the base Subject class has no data, there is nothing special to do here:

void Player::RegisterObserver(Observer* pToAdd) 
{
ObserverVec::iterator itor;
itor = std::find(m_observers.begin(),
m_observers.end(),
pToAdd);

assert(itor == m_observers.end());
m_observers.push_back(pToAdd);
}

The RegisterObserver method takes a pointer to an Observer and adds it to the vector of the Observers. Depending on the behavior of the Observer, being added to the list twice could cause a lot of problems and could be a difficult bug to track down. In this example, we have chosen to assert if the same Observer is added twice. After that, we add it to our vector:

void Player::UnregisterObserver(Observer* pToRemove) 
{
ObserverVec::iterator itor;
itor = std::find(m_observers.begin(),
m_observers.end(),
pToRemove);

if (itor != m_observers.end())
{
std::swap(*itor, *(--m_observers.end()));
m_observers.pop_back();
}
}

Our UnregisterObserver class is a little more forgiving. If we don't find the Observer in the vector, we ignore it, instead of throwing an assert. This will make a little more sense later. You will see that our Observers will automatically remove or unregister in their own destructors. However, unregistering twice is unlikely to cause a problem. The line std::swap(*itor, *(--m_observers.end())) might look a little scary. Remember that the end method returns an iterator to one past the end of the vector. So, before we dereference, we decrement our iterator so it is pointing at the last element in the vector. Then we swap and pop, removing the correct element:

void Player::Notify(void) 
{
size_t size = m_observers.size();
for (size_t i = 0; i < size; ++i)
m_observers[i]->Update(m_health, m_maxHealth);
}

As we said before, the Notify method doesn't need to exist. It would be fine if the class logic notifies the Observers internally, perhaps in Setter methods or when the data changes as in our AdjustHealth method. However, if there was more than one piece of data that the Observers cared about, the user could make many changes and send all the data to the Observers just once. Or, perhaps initializing the Observer data before the game has started.

This method is simple. It loops through the vector of the Observers and calls the Update method, sending the health data to those that care:

void Player::AdjustHealth(float adjustHealth) 
{
m_health += adjustHealth;
Notify();
}

This method simulates the Player gaining or losing health. As you can see, after the health is modified, the class calls its own Notify method, letting all Observers know about the change.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.236.70