Skip to content

Instantly share code, notes, and snippets.

@kenpower
Last active March 12, 2025 22:00
Show Gist options
  • Save kenpower/e587e62aa3421f05a2654352c4175451 to your computer and use it in GitHub Desktop.
Save kenpower/e587e62aa3421f05a2654352c4175451 to your computer and use it in GitHub Desktop.
Applying Observer pattern to game entities with multiple dependents

Summary of the Observer Pattern Implementation

The code demonstrates a game system refactored to use the Observer pattern, consisting of these key components:

  • Observer Interface (GameObserver): Defines methods for different game events (player damaged, player death, enemy death).

  • Subject Interface (Subject): Manages a list of observers and provides methods to add, remove, and notify observers.

  • Game Entities (Player and Enemy): Inherit from Subject and notify observers when their state changes.

  • Concrete Observers: Various game systems (UI, Achievements, Leaderboard, Audio, Particles) that respond to game events.

  • Main Function: Demonstrates how to set up the observer relationships and simulate game events.

Key benefits of this implementation include:

  • Decoupled game entities from game systems

  • Improved extensibility and maintainability

  • Cleaner, more focused responsibilities for each class

  • Easier testing and modification of individual components

This structure allows for a more flexible and modular game architecture, where new features or systems can be added without modifying existing game entities.

#include <iostream>
#include <vector>
#include <memory>
class GameUI {
public:
void showGameOver() { /* Implementation */ }
void updateEnemyCount() { /* Implementation */ }
};
class AchievementSystem {
public:
void unlockAchievement(const std::string& achievement) { /* Implementation */ }
void incrementEnemyKillCount() { /* Implementation */ }
};
class LeaderboardSystem {
public:
void submitScore(int score) { /* Implementation */ }
};
class AudioManager {
public:
void playSound(const std::string& soundName) { /* Implementation */ }
};
class ParticleSystem {
public:
void spawnDeathParticles(const Vector2D& position) { /* Implementation */ }
};
class Player {
public:
Player(GameUI* ui, AchievementSystem* achievements, LeaderboardSystem* leaderboard,
AudioManager* audio, ParticleSystem* particles)
: m_ui(ui), m_achievements(achievements), m_leaderboard(leaderboard),
m_audio(audio), m_particles(particles) {}
void takeDamage(int amount) {
health -= amount;
if (health <= 0) {
// Update UI
m_ui->showGameOver();
// Update achievements
m_achievements->unlockAchievement("Game Over");
// Update leaderboard
m_leaderboard->submitScore(score);
// Play sound
m_audio->playSound("player_death");
// Spawn particles
m_particles->spawnDeathParticles(position);
}
}
void addScore(int amount) {
score += amount;
}
private:
int health;
int score;
Vector2D position;
GameUI* m_ui;
AchievementSystem* m_achievements;
LeaderboardSystem* m_leaderboard;
AudioManager* m_audio;
ParticleSystem* m_particles;
};
class Enemy {
public:
Enemy(GameUI* ui, AchievementSystem* achievements, Player* player,
AudioManager* audio, ParticleSystem* particles)
: m_ui(ui), m_achievements(achievements), m_player(player),
m_audio(audio), m_particles(particles) {}
void die() {
// Update UI
m_ui->updateEnemyCount();
// Update achievements
m_achievements->incrementEnemyKillCount();
// Update player score
m_player->addScore(100);
// Play sound
m_audio->playSound("enemy_death");
// Spawn particles
m_particles->spawnDeathParticles(position);
}
private:
Vector2D position;
GameUI* m_ui;
AchievementSystem* m_achievements;
Player* m_player;
AudioManager* m_audio;
ParticleSystem* m_particles;
};
/*
This main function does the following:
Creates instances of all the required systems (GameUI, AchievementSystem, etc.).
Creates a Player instance, passing in pointers to all the required systems.
Creates a vector of Enemy instances (in this case, 3 enemies).
Simulates some game actions:
The player takes some damage
An enemy dies
The player takes fatal damage
*/
int main() {
// Create instances of the required systems
auto ui = std::make_unique<GameUI>();
auto achievements = std::make_unique<AchievementSystem>();
auto leaderboard = std::make_unique<LeaderboardSystem>();
auto audio = std::make_unique<AudioManager>();
auto particles = std::make_unique<ParticleSystem>();
// Create a player
Player player(ui.get(), achievements.get(), leaderboard.get(), audio.get(), particles.get());
// Create some enemies
std::vector<std::unique_ptr<Enemy>> enemies;
for (int i = 0; i < 3; ++i) {
enemies.push_back(std::make_unique<Enemy>(ui.get(), achievements.get(), &player, audio.get(), particles.get()));
}
// Simulate some game actions
std::cout << "Simulating game actions:\n";
std::cout << "Player takes damage\n";
player.takeDamage(50);
std::cout << "Enemy dies\n";
enemies[0]->die();
std::cout << "Player dies\n";
player.takeDamage(100);
return 0;
}
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
// Observer interface
class GameObserver {
public:
virtual ~GameObserver() = default;
virtual void onPlayerDamaged(int currentHealth) {}
virtual void onPlayerDeath() {}
virtual void onEnemyDeath() {}
};
// Subject interface
class Subject {
public:
void addObserver(GameObserver* observer) {
observers.push_back(observer);
}
void removeObserver(GameObserver* observer) {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
protected:
void notifyPlayerDamaged(int currentHealth) {
for (auto observer : observers) observer->onPlayerDamaged(currentHealth);
}
void notifyPlayerDeath() {
for (auto observer : observers) observer->onPlayerDeath();
}
void notifyEnemyDeath() {
for (auto observer : observers) observer->onEnemyDeath();
}
private:
std::vector<GameObserver*> observers;
};
// Player class
class Player : public Subject {
public:
void takeDamage(int amount) {
health -= amount;
notifyPlayerDamaged(health);
if (health <= 0) {
notifyPlayerDeath();
}
}
private:
int health = 100;
};
// Enemy class
class Enemy : public Subject {
public:
void die() {
notifyEnemyDeath();
}
};
// Concrete observer classes
class GameUI : public GameObserver {
public:
void onPlayerDamaged(int currentHealth) override {
std::cout << "UI: Player health updated to " << currentHealth << std::endl;
}
void onPlayerDeath() override {
std::cout << "UI: Game Over screen shown" << std::endl;
}
void onEnemyDeath() override {
std::cout << "UI: Enemy count updated" << std::endl;
}
};
class AchievementSystem : public GameObserver {
public:
void onPlayerDeath() override {
std::cout << "Achievement: 'Game Over' unlocked" << std::endl;
}
void onEnemyDeath() override {
std::cout << "Achievement: Enemy kill count incremented" << std::endl;
}
};
class LeaderboardSystem : public GameObserver {
public:
void onPlayerDeath() override {
std::cout << "Leaderboard: Score submitted" << std::endl;
}
};
class AudioManager : public GameObserver {
public:
void onPlayerDeath() override {
std::cout << "Audio: Playing player death sound" << std::endl;
}
void onEnemyDeath() override {
std::cout << "Audio: Playing enemy death sound" << std::endl;
}
};
class ParticleSystem : public GameObserver {
public:
void onPlayerDeath() override {
std::cout << "Particles: Spawning player death particles" << std::endl;
}
void onEnemyDeath() override {
std::cout << "Particles: Spawning enemy death particles" << std::endl;
}
};
int main() {
Player player;
Enemy enemy;
auto ui = std::make_unique<GameUI>();
auto achievements = std::make_unique<AchievementSystem>();
auto leaderboard = std::make_unique<LeaderboardSystem>();
auto audio = std::make_unique<AudioManager>();
auto particles = std::make_unique<ParticleSystem>();
player.addObserver(ui.get());
player.addObserver(achievements.get());
player.addObserver(leaderboard.get());
player.addObserver(audio.get());
player.addObserver(particles.get());
enemy.addObserver(ui.get());
enemy.addObserver(achievements.get());
enemy.addObserver(audio.get());
enemy.addObserver(particles.get());
std::cout << "Player takes damage:\n";
player.takeDamage(50);
std::cout << "\nEnemy dies:\n";
enemy.die();
std::cout << "\nPlayer dies:\n";
player.takeDamage(100);
return 0;
}
@kenpower
Copy link
Author

kenpower commented Mar 3, 2025

The refactored version using the Observer pattern offers several improvements:

Decoupling: Player and Enemy are no longer directly dependent on other systems. They simply notify observers of events.

Single Responsibility: Each class now has a more focused responsibility. Player and Enemy manage their own state and notify of changes, while other systems react to these notifications.

Extensibility: New observers can be added without modifying Player or Enemy. For example, adding a new achievement or UI element becomes trivial.

Flexibility: Observers can be dynamically added or removed at runtime.

Improved testability: Each component can be easily tested in isolation.

Cleaner main function: The main function is now simpler and doesn't need to manage as many dependencies.

This implementation demonstrates how the Observer pattern can significantly improve the structure and maintainability of the game code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment