遊戲設計中的觀察者模式
遊戲設計中的觀察者模式
觀察者模式是最初的四種模式中使用最廣泛、最廣為人知的一種,但是在遊戲設計領域中,觀察者模式確並不是十分常見。今天來給大家舉例說明觀察者模式在遊戲設計中的應用。
成就解鎖系統
假設我們在遊戲中添加了一個成就系統。它將有幾十個不同的成就,玩家可以通過完成特定的行為來獲得這些成就。比如說“殺死100個怪物”、“從橋上跌落”等等。
這是很難簡單明了地實現的,因為我們有一個龐大的可以通過各種不同的行為來實現的成就系統。如果我們不小心的話,成就系統的各個分支會充斥我們代碼庫的各個角落。當然,“從橋上跌落”多少和物理引擎有關,但是我們真的希望看到在沖突解決算法的線性代數中調用unlockFallOffBridge()方法嗎?
我們想要的是,像往常一樣,把所有與遊戲的一個方面有關的代碼很好地集中在一個地方。挑戰在於成就是由遊戲玩法的不同方面所觸發的。如何在不將成就代碼與所有代碼耦合的情況下工作?
這就是觀察者模式的作用。它允許一段代碼宣布一些事件發生了,而不關心誰收到了通知。
例如,我們有一段物理代碼用來處理重力並可以跟蹤哪些物體在光滑的平面上滾動,哪些物體正直線下降,走向死亡。要實現“從橋上摔下來”的成就,我們可以直接那裏添加成就代碼,但那將會十分糟糕。相反,我們可以這樣做:
void Physics::updateEntity(Entity& entity) { bool wasOnSurface = entity.isOnSurface(); entity.accelerate(GRAVITY); entity.update();if (wasOnSurface && !entity.isOnSurface()) { notify(entity, EVENT_START_FALL); } }
這麽做只是說明有一個東西跌落了下來,而並不在意有沒有人關心。
成就系統自己進行註冊,這樣每當物理代碼發送通知時,成就系統就會收到通知。然後,成就系統就可以檢查墜落的物體是否是玩家在遊戲中的英雄,以及他墜落的位置是否是一座橋梁。如果是這樣的話,它就可以展示菜單來告訴玩家已解鎖對應的成就,這樣它就在不涉及物理代碼的情況下完成了所有這些工作。
工作原理
下面簡單介紹一下觀察者模式的設計原理
觀察者
我們將從nosy類開始,它想知道另一個對象什麽時候做了一些特定的事情。這些對象是由這個接口定義的:
class Observer { public: virtual ~Observer() {} virtual void onNotify(const Entity& entity, Event event) = 0; };
任何實現它的具體類都成為一個觀察者。在這個例子中,這是成就系統:
class Achievements : public Observer { public: virtual void onNotify(const Entity& entity, Event event) { switch (event) { case EVENT_ENTITY_FELL: if (entity.isHero() && heroIsOnBridge_) { unlock(ACHIEVEMENT_FELL_OFF_BRIDGE); } break; // Handle other events, and update heroIsOnBridge_... } } private: void unlock(Achievement achievement) { // Unlock if not already unlocked... } bool heroIsOnBridge_; };
主體
通知方法由被觀察的對象調用。這個對象即為“主體”。它有兩個工作。首先,它有一份觀察者的名單,他們非常耐心地等待著它的來信:
class Subject { private: Observer* observers_[MAX_OBSERVERS]; int numObservers_; };
重要的一點是,主體公開了修改列表的公共API:
class Subject { public: void addObserver(Observer* observer) { // Add to array... } void removeObserver(Observer* observer) { // Remove from array... } // Other stuff... };
這允許外部代碼控制誰接收通知。主體與觀察者交流,但與觀察者不耦合。在我們的示例中,沒有一行物理代碼會提到成就。然而,它仍然可以與成就系統對話。這就是這個模式的巧妙之處。
同樣重要的是,主體有一個觀察者列表,而不是一個單一的觀察者。它確保觀察者不會隱式地彼此耦合。例如,假設音頻引擎也觀察秋季事件,以便它能播放適當的聲音。如果主體只支持一個觀察者,當音頻引擎自己註冊時,就會取消對成就系統的註冊。
這意味著這兩個系統會互相幹擾——而且以一種特別惡劣的方式,因為第二個系統會使第一個系統失效。支持一個觀察者列表,可以確保每個觀察者都被獨立對待。據他們所知,他們每個人都是世界上唯一關註這個話題的人。
主體的另一個工作就是發送通知:
class Subject { protected: void notify(const Entity& entity, Event event) { for (int i = 0; i < numObservers_; i++) { observers_[i]->onNotify(entity, event); } } // Other stuff... };
可觀察的物理引擎
現在,我們只需要將所有這些連接到物理引擎中,這樣它就可以發送通知,而成就系統就可以自己連接起來接收通知。我們將保持接近原始設計模式和繼承主體:
class Physics : public Subject { public: void updateEntity(Entity& entity); };
這讓我們可以在Subject protected中使用notify()。通過這種方式,派生的物理引擎類可以調用它來發送通知,但是它外部的代碼不能。同時,addObserver()和removeObserver()是公共的,所以在物理系統中的任何東西都可以觀察到它。
現在,當物理引擎做了一些值得註意的事情時,它調用notify(),就像前面的示例一樣。它將遍歷觀察者列表並給他們所有的提示。
總結
觀察者模式解決了項目中的耦合問題,使遊戲中對象之間的通信更加容易,提高了代碼的重用能力。
遊戲設計中的觀察者模式