国产精品天干天干,亚洲毛片在线,日韩gay小鲜肉啪啪18禁,女同Gay自慰喷水

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

游戲編程模式(三):觀察者模式

2023-07-10 04:02 作者:寧牁兒  | 我要投稿

只要你隨便打開一個手機或者電腦的應用,十有八九它就用了MVC架構(gòu),觀察者模式幾乎隨處可見,以至于在Java語言中它被放進了核心庫中(java.util.Observer),C#語言中更是直接嵌入了語法(event關(guān)鍵字)。

在游戲開發(fā)中,說到觀察者模式,典型的場景——成就系統(tǒng):

一.

設想一個場景,有一段物理系統(tǒng)相關(guān)的代碼處理重力,并且追蹤了哪些物體待在地表哪些墜入深淵,而開發(fā)者需要實現(xiàn)”從地表掉落到深淵“的成就。如果把成就相關(guān)代碼放入物理相關(guān)代碼中,那會是一團糟。當然,我們大概也不會這樣做,通常會稍微做一些分離,比如把被追蹤的物體和事件抽離出來:

void Physics::updateEntity(Entity& entity)
{
? ?bool wasOnSurface = entity.isOnSurface();
? ?entity.accelerate(GRAVITY);
? ?entity.update();
? ?if (wasOnSurface && !entity.isOnSurface())
? ?{
? ? notify(entity, EVENT_START_FALL);
? ?}
}

但這明顯還不夠,并沒有完全解耦,現(xiàn)在可以直接引入觀察者模式:

二.

觀察者

class Observer
{
public:
? ?virtual ~Observer() {}
? ?virtual void onNotify(const Entity& entity, Event event) = 0;
};

實現(xiàn)了Observer類的具體類就成為了觀察者,在成就系統(tǒng)中,可以是:

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);
? ? ? ? ? ?}
? ? ? ? ? ?...
? ? ? ?}
? ?}
private:
? ?...
? ?bool heroIsOnBridge_;
};

被觀察者

class Subject
{
private:
? ?Observer* observers_[MAX_OBSERVERS];
? ?int numObservers_;
public:
? ?void addObserver(Observer* observer){
? ? // 添加到數(shù)組中……
? ?}
? ?void removeObserver(Observer* observer){
? ? // 從數(shù)組中移除……
? ?}
? ?void notify(const Entity& entity, Event event){
? ? ? ?for (int i = 0; i < numObservers_; i++){
? ? ? ? observers_[i]->onNotify(entity, event);
? ? ? ?}
? ?}
};

被觀察的對象擁有通知的方法,并維護了一個列表,保存等它通知的觀察者。現(xiàn)在可以讓物理系統(tǒng)實現(xiàn)Subject類:

class Physics : public Subject{
public:
? ?void updateEntity(Entity& entity);
};

三.

多線程和同步

值得注意的是,觀察者模式是同步的,也就是說,被觀察者直接調(diào)用了觀察者,這意味著直到所有觀察者通知方法返回后,被觀察者才會繼續(xù)自己的工作。在UI線程中,這需要小心,對事件的同步響應應該盡快返回,否則會導致UI鎖死。當有必要的耗時操作時,應該將其送到其它線程或者工作隊列中去。而當進一步引入了線程和鎖,要防止觀察者獲得被觀察者擁有的鎖,否則就會進入死鎖。

四.

鏈式觀察者

上述實現(xiàn)方法中,Subject擁有一列指針指向觀察它的Observer。我們可以將觀察者的列表分布到觀察者自己中來解決內(nèi)存的動態(tài)分配問題,鏈表在這里起到重要作用:

class Subject{
private:
Observer* head_;
? ?
? ?//添加和刪除節(jié)點的方法 add/remove
? ?...
};

class Observer{
friend class Subject;

private:
Observer* next_;
};

void Subject::notify(const Entity& entity, Event event)
{
? ?Observer* observer = head_;
? ?while (observer != NULL)
? ?{
? ? ? ?observer->onNotify(entity, event);
? ? ? ?observer = observer->next_;
? ?}
}

鏈表的各個基本操作在此就不展開敘述,當然,和其它單鏈表一樣,在刪除節(jié)點時需要遍歷鏈表,所以進一步可以考慮引入雙向鏈表來獲得常量時間的刪除操作。

還有一個問題,被觀察者是按鏈表的順序來通知觀察者的,這就要求觀察者之間不應該有順序相關(guān)性,否則觀察者之間會依然存在一個微妙的耦合。

五.

鏈表節(jié)點池

繼續(xù)深入,會發(fā)現(xiàn)之前的方法存在一個致命的問題:如果我們打算將某個觀察者注冊到不同的被觀察者列表中,鏈表的節(jié)點添加操作會使得靠前加入的鏈表遭到破壞,鏈表節(jié)點池可以解決這一問題:

class Node{

private:
Observer* observer;
? ?Node* next;
};

把鏈表節(jié)點本身和觀察者的角色解耦,在每次將觀察者加入列表時,新建一個Node節(jié)點。

六.

對象銷毀

再往下深入,討論一個被觀察者或者觀察者被刪除時會發(fā)生什么。

在C++這種可以主動進行內(nèi)存回收的語言中,如果不小心在某些觀察者上調(diào)用了delete,而被觀察者仍然擁有指向它的指針,這時被觀察試圖發(fā)送一個通知,自然會導致程序出錯,所以,在被刪除時取消注冊是觀察者的職責,通??梢栽谒奈鰳?gòu)器上加上removeObserver() ?。

如果是不小心在被觀察者上調(diào)用delete,這倒是不會導致程序出錯,但是它的觀察者列表中的那些觀察者可能就永遠收不到通知了,這將造成內(nèi)存浪費。解決辦法也簡單,讓被觀察者在它被刪除前發(fā)送一個最終的“死亡通知” ,通知各觀察者“自身死亡的消息”,觀察者就可以做出相應的行為。

垃圾回收

那在那些不能主動進行內(nèi)存回收的語言中呢?它們有垃圾回收器,不存在不小心調(diào)用delete的情況,但這并不能說明就安全了。

設想一個場景:有UI顯示玩家角色情況的狀態(tài),比如健康和道具。 當玩家在屏幕上時,你為其初始化了一個對象。 當UI退出時,直接忘掉這個對象,交給GC清理。每當角色臉上挨了一拳,就發(fā)送一個通知。 UI觀察到了,然后更新健康槽, 當玩家離開場景,卻沒有取消觀察者的注冊。此時,UI界面不再可見,但也不會進入垃圾回收系統(tǒng),因為角色的觀察者列表還保存著對它的引用。 每一次場景加載后,將會給那個不斷增長的觀察者列表添加一個新實例。玩家玩游戲時,來回跑動,打架,角色的通知發(fā)送給所有的界面。 它們不在屏幕上,但它們接受通知,這樣就浪費CPU循環(huán)在不可見的UI元素上了。


游戲編程模式(三):觀察者模式的評論 (共 條)

分享到微博請遵守國家法律
东乡| 泸西县| 灵川县| 滦平县| 建湖县| 宜都市| 桃江县| 平谷区| 都兰县| 威海市| 泉州市| 宁南县| 米易县| 桑植县| 房产| 丹寨县| 习水县| 江北区| 那坡县| 台山市| 新乡市| 邳州市| 镇沅| 睢宁县| 沂水县| 承德市| 会泽县| 鹤山市| 遵义县| 延长县| 山东省| 旅游| 保靖县| 昌黎县| 平昌县| 江源县| 揭东县| 手游| 襄樊市| 崇仁县| 张掖市|