談談Unity編程中的“觀察者模式”


3樓貓 發佈時間:2024-08-04 16:32:40 作者:太陽核子 Language

前言

當我在學習unity一段時間後,由於自己那不自量力的野心膨脹,總是想要做一個較大體量的遊戲出來。於是我奮指疾敲,想要在Visual Studio的黑框框和五彩斑斕的變量名裡敲出一個龐大的遊戲世界。但是很快我敗下陣來:
①這十幾個腳本咋管理嘛!好混亂啊!哎呀,這裡看來必須要引用那個腳本了,設置成public在編輯器裡拖一拖吧,一段時間後:尼瑪怎麼拖了這麼多。②我靠,這個腳本里代碼四五百行,改動的話需要考慮很多多,複雜度成倍上升。③要不要動這個腳本啊?可是它裡面關係到十幾個腳本啊,我“感動”嘛,根本不敢動啊!
諸如此類的的問題都非常棘手,我意識到,遊戲要稍微做出點體量,必須解決它們!可是該怎麼解決啊?我上B站大學搜來搜去,搞來搞去,最後終於知道了解決代碼結構之間問題的學問叫做“編程設計模式”。又過了一段時間,我瞭解到了設計模式裡的“觀察者模式”,學了一段時間後,我感慨道:觀察者模式,你是我的神!
觀察者模式在我後來的編程中,起到的作用好比紙巾和拉屎之間的關係,可以說是作用巨大,根本離不開。那麼接下來,我就按照我自己的理解方式,還有我在實踐中的具體運用,來談談“觀察者模式”吧。

啥是“觀察者模式”?

觀察者模式(有時又被稱為模型(Model)-視圖(View)模式、源-收聽者(Listener)模式或從屬者模式)是軟件設計模式的一種。在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實現事件處理系統。 ——百度
好,百度已經給我們解釋完了,這一塊咱們pass掉,接著往下走。
哈哈,開玩笑。這解釋得也太抽象了吧,沒關係,我來用更簡單的方式向你解釋這一概念。
首先我們要知道觀察者模式中的兩種角色:【觀察者】和【被觀察者】
想象有一個人氣明星,比如周杰倫~。杰倫有非常多的歌迷,這些歌迷對他的新專輯翹首以盼(話說距離上次偉作已經過去兩年了……),都在關注著杰倫的專輯動向,一有點風吹草動大家就會沸騰。那麼,觀察杰倫的歌迷們就是【觀察者】,被歌迷們觀察的杰倫就是【被觀察者】。
在觀察者模式中,被觀察者的狀態發生改變時,就會向所有的觀察者們發送通知,觀察者們就可以根據這個通知做出各自相應的行為。類比到杰倫和歌迷上,就是當杰倫發新專輯時(簡直天方夜譚!),他會在各種社交媒體、音樂軟件上發佈這個消息,而所有關注杰倫的歌迷在看到這一消息後,有的掏出錢包,有的奔走相告,有的因激動而變身狒狒:吼吼哇哇!
當然,觀察者模式中還有一個很重要的概念:【主題】,我更習慣稱呼它為【中間體】。中間體是觀察者和被觀察者之間的橋樑,就好像一個代理人,負責管理有哪些觀察者正在觀察自己代理的被觀察者。每當被觀察者狀態改變發送消息時,消息首先到達中間體,再由中間體傳遞出去。在杰倫和歌迷的比喻中,中間體就好比是社交媒體、音樂平臺。

為什麼說“觀察者模式”優秀?

觀察者模式之優秀,以至於C#官方都將其寫入語法之中:委託delegate、事件event。那具體優秀在哪些方面呢?
一、低耦合
在編程中,中間體以一種抽象形式存在,這樣使得觀察者和被觀察者彼此之間不相互依賴,而是單獨依賴於中間體這個抽象。 你可能會想,為什麼不讓觀察者和被觀察者直接聯繫起來,這樣豈不更加直觀?其實,引入一層中間體並不是為了增加複雜性,而是為了增加設計的靈活性和可擴展性。儘管從表面上看,直接讓兩者聯繫可能看起來更簡單,但這種直接聯繫實際上會限制系統的靈活性和可維護性:直接聯繫意味著觀察者和被觀察者之間將存在緊密的耦合,如果未來需要更改被觀察者的實現或添加新的觀察者類型,這種緊密耦合將導致大量的代碼修改和潛在的錯誤。
二、支持一對多通信
被觀察者會向所有已註冊的觀察者對象發送通知,這種機制簡化了一對多系統設計的難度。當觀察目標的狀態發生變化時,所有相關的觀察者都能自動接收到通知並作出相應的處理。
這使得在遊戲中某些一對多事件編程中更加容易,比如“戰鬥結束”這個事件觸發後,可能還要觸發一系列的事件比如“玩家血量重置”、“新關卡解鎖”、“獎勵發放”、“NPC對話”等等。這樣的話,運用觀察者模式,讓“戰鬥結束”成為被觀察者,讓諸如“玩家血量重置”等事件成為觀察者,這樣一旦“戰鬥結束”的通知發了出來,所有的觀察者收到消息並且被觸發。
三、易於拓展
  • 開閉原則:觀察者模式滿足開閉原則的要求,即軟件實體(類、模塊、函數等)應該對擴展開放,對修改關閉。在觀察者模式中,增加新的觀察者無需修改原有系統代碼,只需創建新的觀察者類並實現更新接口即可。
  • 靈活性:由於觀察者和觀察目標之間的耦合度低,因此係統可以靈活地添加、刪除或修改觀察者,而不會對其他部分造成太大影響。
四、 表現層和數據層分離
觀察者模式定義了穩定的消息更新傳遞機制,並抽象了更新接口。這使得可以有各種各樣不同的表示層充當具體觀察者角色,從而實現了表示層和數據邏輯層的分離。

運用方式之一:事件中心

觀察者模式的運用之一就是【事件中心】。
想象有一個失物招領的大廳,很多丟失物品的人在大廳的工作人員處登記:自己丟失了手機、自己私房錢不見了、自己放在戶外的雪糕不見了等等,然後就回家去了。過了幾天,有人撿到了手機,來到失物招領大廳上交手機(拾金不昧,我輩榜樣)。大廳的工作人員查詢登記簿後,發現這是某個人丟失的手機,於是通知這個人。丟失手機的人得到消息後,歡天喜地來到大廳處領取幾年沒見(一日不見,如隔三秋)的手機。
在這個例子中,失物招領大廳相當於事件中心(中間體);在大廳登記自己丟失物品的人們則是觀察者,他們通過在大廳註冊登記,從而讓大廳能夠時刻替他們留意自己的東西有沒有被別人撿到;撿到手機的人是被觀察者,在撿到手機後向大廳發送“自己撿到了手機”這個消息;大廳在得到這個消息後,在查詢登記簿後通知丟失了手機的人。
假設在遊戲中,我們需要在戰鬥結束後為玩家發放獎勵,那麼運用事件中心的思想,我們可以像下面這樣做(注意,代碼只是展示思路,很多地方並不嚴謹):
創立一個事件中心:
一個用來發放獎勵的對象:
在腳本初始化時,獎勵發放者把觸發獎勵的事件類型(戰鬥結束)和自己發放獎勵的行為註冊給事件中心

在腳本初始化時,獎勵發放者把觸發獎勵的事件類型(戰鬥結束)和自己發放獎勵的行為註冊給事件中心

一個控制戰鬥結束的對象:
向事件中心廣播”戰鬥結束“事件的對象。

向事件中心廣播”戰鬥結束“事件的對象。

在這些腳本中,一旦BattleManager中的BattleEnd方法被調用,那麼便會向事件中心發送”戰鬥結束“的通知,而RewardGiver先前已經在事件中心註冊好了對應的事件和觸發行為,所以事件中心會找到這個註冊,並且激活這個註冊中的行為,也就是獎勵發放行為。這樣一來,戰鬥結束後玩家便能收到獎勵。
通過事件中心的思想,我們解耦了”控制戰鬥結束“的腳本和”發放獎勵“的腳本,使其只依賴於事件中心這個抽象,兩者之間的關聯性大大降低。就好比失物招領大廳的例子中,丟失物品的人和撿到物品的人之間,並沒有直接的接觸,幾乎沒有什麼關聯。

運用方式之二:事件類

這種對於觀察者模式的運用方式其實原理和事件中心是大差不差的。只不過這種方式將各種【主題】或者叫做【中間體】分散成各種事件類,一箇中間體就是一個事件類,並不集中在某一箇中心。
還是以上述戰鬥結束髮放獎勵為例子,我們運用事件類的思想來實踐觀察者模式。
首先創造一個事件類的基類,之後所有的事件類都繼承自此基類:
然後我們創造一個“戰鬥結束”類繼承自基類:
之後同樣的,在RewardGiver裡通過“戰鬥結束”類註冊獎勵發放行為:
在BattleMManager內戰鬥結束時通過“戰鬥結束”類廣播事件:
可以看到,其基本代碼和事件中心的代碼是很像的。用事件類思想的好處是通過查看有多少種事件類,就可以一目瞭然的知道遊戲內的各種事件種類。當不需要事件時,直接刪除對應的事件類即可。

結尾

觀察者模式是一種非常優秀的思想,天知道前輩們是怎麼一步一步總結出來的,真是眾多智慧凝聚的產物。
希望我的這篇探討觀察者模式的文章能夠幫到在開發遊戲的你們,咱們下一篇文章再見!

© 2022 3樓貓 下載APP 站點地圖 廣告合作:asmrly666@gmail.com