在Unity实现游戏命令模式
本文由開發者Najmm Shora介紹在Unity中通過使用命令模式實現回放功能,撤銷功能和重做功能。我們可以使用該方法來強化自己的策略類游戲。
你是否想知道《超級食肉男孩》(Super Meat Boy)等游戲是如何實現回放功能的?其中一種方法是完全按照玩家發出的命令執行輸入,這意味著輸入需要以某種方式存儲。
命令模式可用于執行此操作和其他操作。如果你希望在策略游戲里實現撤銷和重做功能,命令模式也非常實用。
?
在本教程中,我們將使用C#實現命令模式,然后使用命令模式來遍歷3D迷宮中的機器人角色。
我們會學習到以下內容:
?
- 命令模式的基礎知識。
- 實現命令模式的方法。
- 對輸入命令進行排隊,并推遲執行。
- 在執行前,撤銷和重做已發出的命令。
本教程使用Unity 2019.1和C# 7,學習本文你需要熟悉Unity的使用,并且對C#有一定的了解。
學習準備
本教程將為你提供項目文件和素材,請發送[命令模式]到微信后臺,獲取下載地址。
下載完成項目素材后,請解壓文件,并在Unity中打開Starter項目。然后打開RW/Scenes文件夾,打開主場景。
如下圖所示,場景中有一個迷宮和機器人,左側有一個顯示指令的終端UI。地面的是一個網格,當玩家在迷宮中移動機器人時,這些網格將有助于玩家進行觀察。
?
場景中最有趣的部分是Bot對象,它代表游戲中的機器人,我們在層級窗口單擊選中該對象。
?
在檢視窗口查看該對象,可以看見它帶有Bot組件,我們將在發出輸入命令時使用該組件。
?
理解Bot的邏輯
我們打開RW/Scripts文件夾,在代碼編輯器打開Bot腳本。我們不必了解Bot腳本的作用,但要了解其中的Move方法和Shoot方法的使用。
我們發現,Move方法會接收一個類型為CardinalDirection的輸入參數。CardinalDirection是一個枚舉,類型為CardinalDirection的枚舉對象可以為Up,Down,Right或Left。
根據所選的CardinalDirection不同,機器人會在網格上朝著對應方向移動一個網格。
?
Shoot方法可以讓機器人發射炮彈,摧毀黃色的墻體,但對其它墻體毫無作用。
?
現在查看ResetToLastCheckpoint方法,我們對迷宮進行觀察。在迷宮中,有一些點被稱為檢查點。為了通過迷宮,機器人應該到達綠色檢查點。
?
在機器人穿過新檢查點時,該點會成為機器人的最后檢查點。ResetToLastCheckpoint方法會重置機器人的位置到最后檢查點。
?
什么是命令設計模式
命令模式是《設計模式:可復用面向對象軟件的基礎》(Design Patterns: Elements of Reusable Object-Oriented Software)一書中介紹的23種設計模式之一。
書中寫道:命令模式把請求封裝為對象,從而允許我們使用不同的請求,隊列或日志請求,來參數化處理其它對象,并支持可撤銷的操作。
這么表達或許難以理解,下面我們詳細講解一下。
封裝:方法調用封裝為對象的過程。
?
參數化其它對象:封裝的方法可以根據輸入參數來處理多個對象。
請求的隊列:得到的“命令”可以在執行前和其它命令一起存儲。
?
“Undoable”(可撤銷)在此不是指無法實現的東西,而是指可以通過撤銷功能恢復的操作。那么這些內容怎么用代碼表示呢?
簡單來說,Command類會有Execute方法,該方法可以接收一個名為Receiver的對象作為輸入參數。因此,Execute方法會由Command類進行封裝。
Command類的多個實例可以作為常規對象來傳遞,這表示它們可以存儲在數據結構中,例如:隊列,棧等。
為了執行命令,Execute方法需要進行調用。觸發執行過程的類叫作Invoker。
我們的項目中已包含一個名叫BotCommand的空類。下面我們將完成要求,讓Bot對象可以使用命令模式執行動作。
移動機器人Bot對象
實現命令模式
首先,打開RW/Scripts文件夾,在編輯器打開BotCommand腳本,并加入下面的代碼。
代碼解讀:
?
- commandName變量用于存儲用戶可以理解的命令名稱。
- BotCommand構造函數會接收一個函數和一個字符串,它幫助我們設置Command對象的Execute方法和名稱。
- ExecuteCallback委托會定義封裝方法的類型。封裝方法會返回void類型,接收類型為Bot對象作為輸入參數。
- Execute屬性會引用封裝方法,我們要使用它來調用封裝方法。
- ToString方法會被重寫,返回commandName字符串,該方法主要在UI中使用。
保存改動,現在我們已經實現了命令模式。
創建命令
我們從RW/Scripts文件夾中打開BotInputHandler腳本。
我們將創建BotCommand的5個實例,這些實例會分別封裝方法,從而讓Bot對象向上、下、左、右移動,以及讓機器人發射炮彈。
添加下列代碼到BotCommand類中。
在每個實例中,都有一個匿名方法傳到構造函數。該匿名方法會封裝在相應命令對象之中,每個匿名方法的簽名都符合ExecuteCallback委托設置的要求。
此外,構造函數的第二個參數是一個字符串,表示用于指代命令的名稱。該名稱會通過命令實例的ToString方法返回,它會在后面為UI使用。
在前4個實例中,手機游戲出售平臺匿名方法會在Bot對象上調用Move方法。
對于MoveUp、MoveDown、MoveLeft和MoveRight命令,傳入Move方法的參數分別是CardinalDirection.Up,CardinalDirection.Down,CardinalDirection.Left和CardinalDirection.Right,這些參數對應著Bot對象的不同移動方向。
在第5個實例上,匿名方法在Bot對象調用Shoot方法。這將在執行該命令時,讓機器人發射炮彈。
現在我們創建了命令,這些命令需要在用戶發出輸入時進行訪問。請將下面的代碼添加到BotInputHandler中。
HandleInput方法會根據用戶的按鍵,返回單個命令實例。繼續下一步前,保存改動內容。
使用命令
現在我們要使用創建好的命令。打開RW/Scripts文件夾,在代碼編輯器打開SceneManager腳本。在該類中,我們會發現有UIManager類型的uiManager變量的引用。
UIManager類為場景中的終端UI提供了實用的功能性方法。此外,Bot變量引用了附加到Bot對象的Bot組件。
我們將下面的代碼添加給SceneManager類,替換代碼注釋//1的已有代碼。
?
保存代碼,通過使用這些代碼,我們可以在游戲視圖正常運行項目。
運行游戲并測試命令模式
現在要構建所有內容,在Unity編輯器按下Play按鈕。
我們可以使用W,A,S,D按鍵輸入方向命令。輸入射擊模式時,使用F鍵。最后按下回車鍵執行命令。
?
?
現在觀察代碼添加到終端UI的方式。命令會通過它們在UI中的名稱表示,該效果通過commandName變量實現。
在執行前,UI會滾動到頂部,執行后的代碼行會被移除。
詳解命令代碼
現在我們詳解在使用命令部分添加的代碼。
botCommands列表存儲了BotCommand實例的引用。考慮到內存,我們只可以創建5個命令實例,但有多個引用指向相同的命令。此外,executeCoroutine變量引用了ExecuteCommandsRoutine,后者會處理命令的執行過程。
如果用戶按下回車鍵,更新檢查結果,此時它會調用ExecuteCommands,否則會調用CheckForBotCommands。
CheckForBotCommands使用來自BotInputHandler的HandleInput靜態方法,檢查用戶是否發出輸入信息,此時會返回命令。返回的命令會傳遞到AddToCommands。然而,如果命令被執行的話,即如果executeRoutine不是空的話,它會直接返回,不把任何內容傳遞給AddToCommands。因此,用戶必須等待執行過程完成。
AddToCommands給返回的命令實例添加了新引用,返回到botCommands。
UIManager類的InsertNewText方法會給終端UI添加新一行文字。該行文字是作為輸入參數傳給方法的字符串。我們會在此給它傳入commandName。
ExecuteCommands方法會啟動ExecuteCommandsRoutine。
UIManager類的ResetScrollToTop會向上滾動終端UI,它會在執行過程開始前完成。
ExecuteCommandsRoutine有一個for循環,它會迭代botCommands列表中的命令,通過把Bot對象傳給Execute屬性返回的方法,逐個執行這些命令。在每次執行后,我們會添加CommandPauseTimeseconds時長的暫停。
UIManager類的RemoveFirstTextLine方法會移除終端UI里的第一行文字,只要那里仍有文字。因此,每個命令執行后,它的相應名稱會從終端UI移除。
執行所有命令后,botCommands會清空,機器人會使用ResetToLastCheckpoint,重置到最后檢查點。接著,executeRoutine會設為null,用戶可以繼續發出更多輸入信息。
實現撤銷和重做功能
我們再運行一次場景,嘗試到達綠色檢查點。現在無法撤銷輸入的命令,這意味著如果犯了錯,我們無法后退,除非執行完所有命令。
我們可以通過添加撤銷功能和重做功能來解決該問題。返回SceneManager.cs腳本,在botCommands的List聲明后添加以下變量聲明。
?
undoStack變量屬于來自Collections命名空間的Stack類,它會存儲撤銷的命令引用。
現在,我們要分別為撤銷和重做添加UndoCommandEntry和RedoCommandEntry兩個方法。在SceneManager類中,添加下面代碼到ExecuteCommandsRoutine后。
?
解讀這部分代碼:
?
- 如果命令正在執行,或botCommands列表是空的,UndoCommandEntry方法不執行任何操作。否則,它會把最后輸入的命令引用推送到undoStack上。這部分代碼也會從botCommands列表移除命令引用。
- UIManager類的RemoveLastTextLine方法會移除終端UI的最后一行文字,這樣在發生撤銷時,終端UI內容符合botCommands的內容。
- 如果undoStack為空,RedoCommandEntry不執行任何操作。否則,它會把最后的命令從undoStack移出,然后通過AddToCommands把命令添加到botCommands列表。
現在我們添加鍵盤輸入來使用這些方法。在SceneManager類中,把Update方法的主體替換為下列代碼。
?
現在按下U鍵會調用UndoCommandEntry方法,按下R鍵會調用RedoCommandEntry方法。
處理邊緣情況
現在我們快要完成該教程了,在完成前,我們要確定二件事:
?
- 輸入新命令時,undoStack應該被清空。
- 執行命令前,undoStack應該被清空。
首先,我們給SceneManager添加一個新方法。添加下面的方法到CheckForBotCommands之后。
?
該方法會清空undoStack,然后調用AddToCommands方法。
現在把CheckForBotCommands內的AddToCommands調用替換為下列代碼:
最后,復制粘貼下列代碼到ExecuteCommands方法內的if語句中,從而在執行前清空undoStack。
?
現在項目終于完成了,我們保存并構建項目。在Unity編輯器單擊Play按鈕。輸入命令,按下U鍵撤銷命令,按下R鍵恢復被撤銷的命令。
下圖展示了讓機器人到達綠色檢查點。
?
?
總結
以上是生活随笔為你收集整理的在Unity实现游戏命令模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人工智能(AI)在游戏中的应用(下)
- 下一篇: 使用Unity引擎打造赛博朋克之城!CI