博主才學尚淺,難免會有錯誤,尤其是設計模式這種極富禪意且需要大量經驗的東西,如果哪里書寫錯誤或有遺漏,還請各位前輩指正。
打 算寫設計模式的目的就是,首先自己可以理清思路,還有就是國內的設計模式資料很豐富,但是並沒有專門用在游戲開發上的講解,看過之后有些不知道怎么用在游 戲方面上,怎么用,博主在學習過程中會結合一些國外的游戲設計模式資料加上自己的理解與實踐,寫出文章,在自己理清思路的同時也希望能對像我這樣的小白們 提供一點微薄幫助。
在我沒學這個模式之前寫的控制部分的代碼,就是把按鍵控制寫成if else簡單的,一次性的寫在update()函數中,對這樣散亂的塊來做輪詢,這樣的代碼不整體,是一次性的,難以維護,如果想要在其中新添加功能會非 常難找,改動也容易出現錯誤。之前的代碼例子如下(錯誤示例,僅給出部分):
void Update() { if (Input.GetKeyDown(KeyCode.J)) { …攻擊操作… } if (Input.GetKeyDown(KeyCode.Space)) { …跳躍操作… } if (Input.GetKey(KeyCode.D)) { …向左移動操作… } }
相信不少人和我一樣是這么寫的,接下來要介紹命令模式,這種模式不僅能用在unity的腳本編程上,所有面向對象語言都適用。
命令模式是游戲中很有用的設計模式,四人幫有一句話是這樣說的:
Encapsulate a request as an object, thereby letting users parameterize clients with different requests, queue or log requests, and support undoable operations.
大概意思是,將請求封裝為一個對象,讓用戶參數化的提出不同的請求,並提供撤銷操作。
我們可以理解為是把命令具體化來調用。
比如把控制命令變成實實在在的實體來調用,就不用像上面錯誤的例子那樣輪詢一堆散亂的東西。
如下圖,我們先把具體控制封裝成函數,J鍵定義為act操作,把space鍵定義為jump操作。
命令模式好處之一:
方便替換按鍵,大家都知道大部分游戲設置中會有替換按鍵的設置,把按鍵設置為自己的常用鍵,玩着順手,使用命令模式我們把每個操作控制封裝成塊,把用戶按鍵操作與實現控制通過命令解耦,更改按鍵十分容易。如下圖:
我們通過賦值b1,b2可以動態改變按鍵操作。但是這樣方便的一切的前提都是使用命令模式把命令具體化來調用。
再把所有button整合成一個數組button
然后代碼就變成了這樣
void Update() { if (Input.GetKeyDown(button[0])) { act(); } if (Input.GetKeyDown(button[1])) { jump(); } if (Input.GetKey(button[2])) { move(); } }
稍微規整了一些。而且這樣我們就可以讓玩家自定義按鍵了。
接下來我們就可以使用命令模式了,就是替換jump(),act(),move()這些操作為具體的command命令。
我們可以用一個借口command來抽象概括所有的這些命令,再分別實現它們。把這些操作看成客戶下的命令,所以每一個command命令都應該有一個執行命令的函數execute()。代碼如下:
using UnityEngine; using System.Collections; public interface ICommand{ void execute(); }
這個接口就是我們的抽象,然后我們在一一實現我們的命令類,拿jump舉例:
using UnityEngine; using System.Collections; public class JumpCommand : ICommand { public void execute() { ….實現jump…. } }
但是這里出現了問題,jump命令是要用在我們的“hero”上的也就是玩家控制的人物,說白了就是一個 gameobject,所以只要把hero的gameobject傳入我們的命令類即可。這樣做帶來了命令模式的另一個好處,舉個博主最愛玩的游戲-傳說 系列,里面一般有4-6個英雄可以替換控制(對,博主就是要安利你們玩。。),隨意更換英雄,就是隨意更換操作的人物,只要我們傳參傳入不同的hero 的gameobject即可。
using UnityEngine; using System.Collections; public interface ICommand{ void execute(GameObject Hero); }
然后這里我們可以選擇是在一個新的腳本中實現操作,還是在這個command中實現,這兩種都可以,前者重用性好,后者重用性差一些,但更具體。實例在一個新腳本HeroCtrl中實現具體操作:
using UnityEngine; using System.Collections; public class JumpCommand : ICommand { public void execute(GameObject Hero) { Hero.GetComponent<HeroCtrl>().jump(); } }
如果想在這個command中實現就可以這樣:
using UnityEngine; using System.Collections; public class JumpCommand : ICommand { float pow = 0.1f; CharacterController controller; public void execute(GameObject Hero) { controller = Hero.GetComponent<CharacterController>(); if (controller.isGrounded) { controller.Move(Vector3.up * pow); Hero.GetComponent<Animation>().CrossFade(…); } } }
此時的結構就是這樣了,我們成功的實現了解耦:
我們在寫AI的時候也可以使用命令模式,此時的AI邏輯只負責“發號施令”就可以了,更加靈活的編寫AI。
然 后再次揭開sims的一個秘密,在玩sims可以對一個小人下許多命令,但是小人需要花時間才能干完一件事,通常我們看到我們想讓他做的事的圖標就會堆在 上面,小人會一個一個的處理,這就是任務列表,我們可以把代做的任務存到一個list中,給每個任務一個index,完成一個就做下一個,產生新任務就堆 在后面,這也是命令模式的一大功能。
然后就是撤銷undo和重做redo部分,這個一般用在策略游戲中,我們錯誤操作了可以及時撤銷。
undo實現方法就是把該任務反過來的操作作為undo函數,舉一個最簡單的例子:
public class addCommand : ICommand { public int execute(int num) { return ++num; } public int undo (int num) { return --num; } }
加法的undo就是減法,前移的undo就是后移。
如果想要redo的話就要把之前的操作儲存起來,最好的辦法把undo,當前操作,redo都存在一個堆中:
當產生一個新操作時,后面的redo全都不要了
游戲回放功能原理與之相同,回放時執行已經記錄好的命令,有些游戲記錄每一幀整個游戲的狀態來回放,這樣會消耗很多內存。
實現結果:
博主寫了一個“玩具”用來專門練習設計模式,gameplay 模仿傳說系列前幾代。
命令模式完美運行:
命令模式完了。總之就是希望前輩們多多指正或者建議,能夠帶來幫助就更好。
博主近期渲染:最近用unity5弄的一些渲染
---- by wolf96