遊戲程式設計之命令模式
阿新 • • 發佈:2018-12-28
1、什麼是命令模式
最近看了《遊戲程式設計模式》這本書,裡面介紹了遊戲開發時常用的設計模式,當然這些設計模式不只是在開發遊戲時才管用,它們同樣適用於其他軟體開發,適用於各種語言。這裡我記錄一下自己的學習筆記以及結合unity的使用方法。命令模式是常用的設計模式之一,它的定義是這樣:將一個請求封裝為一個物件,從而使你可以用不同的請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可撤銷的操作。這個定義聽起來似乎晦澀難懂,下面用unity遊戲開發的例子來說明:2、對客戶進行引數化
比如在遊戲開發中,產品經理給你提了這樣一個需求:按下按鍵A,控制角色攻擊;按下按鍵B,控制角色奔跑;按下按鍵C,控制角色跳躍。面對這樣一個簡單的需求,我們或許會這樣寫:void然後,產品經理又提了需求,使用者可以自定義按鍵功能,在很多遊戲中都有做這樣的功能,為了實現這樣的功能,我們應該將這些對Attack()和Run()的呼叫轉化成可以變換的東西,下面用命令模式來重寫一下這個功能: 先定義一個抽象類Command作為基類,再定義具體的子類來重寫Excute();HandleInput() { if (Input.GetKeyDown(KeyCode.A)) { Attack(); } else if (Input.GetKeyDown(KeyCode.B)) { Run(); } else if (Input.GetKeyDown(KeyCode.C)) { Jump(); } }
public abstract class Command{ public abstract void Excute(GameActor actor); } public class AttackCommand : Command { public override void Excute() { //攻擊邏輯 } } public class RunCommand : Command { public override void Excute() { //奔跑邏輯 } } public class在MonoBehaviour的Update函式中,每幀去監聽使用者輸入,並返回對應的commandJumpCommand : Command { public override void Excute() { //跳躍邏輯 } }
public class GameControl : MonoBehaviour { private Command buttonA; private Command buttonB; private Command buttonC; private void Start() { buttonA = new AttackCommand(); buttonB = new JumpCommand(); buttonC = new RunCommand(); } private void Update() { Command cmd = HandleInput(); if (cmd != null) { cmd.Excute(actor); } } //處理使用者輸入 private Command HandleInput() { if (Input.GetKeyDown(KeyCode.A)) { return buttonA; } else if (Input.GetKeyDown(KeyCode.B)) { return buttonB; } else if (Input.GetKeyDown(KeyCode.C)) { return buttonC; } else { return null; } } }這樣,在按鍵觸發和函式呼叫中間就加了一層Command,如果要自定義按鍵功能,直接修改Button對應的Command就行了。現在我們也可以修改一下上面的程式碼,讓我們可以用這套機制去控制任意角色物件,只需將要控制的角色物件傳進來即可:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameActor { } public class Actor1 : GameActor { } public class Actor2 : GameActor { } public abstract class Command{ public abstract void Excute(GameActor actor); } public class AttackCommand : Command { public override void Excute(GameActor actor) { //攻擊邏輯 } } public class RunCommand : Command { public override void Excute(GameActor actor) { //奔跑邏輯 } } public class JumpCommand : Command { public override void Excute(GameActor actor) { //跳躍邏輯 } } public class GameControl : MonoBehaviour { private Command buttonA; private Command buttonB; private Command buttonC; private GameActor actor; private void Start() { buttonA = new AttackCommand(); buttonB = new JumpCommand(); buttonC = new RunCommand(); actor = new Actor1(); } private void Update() { Command cmd = HandleInput(); if (cmd != null) { cmd.Excute(actor); } } //處理使用者輸入 private Command HandleInput() { if (Input.GetKeyDown(KeyCode.A)) { return buttonA; } else if (Input.GetKeyDown(KeyCode.B)) { return buttonB; } else if (Input.GetKeyDown(KeyCode.C)) { return buttonC; } else { return null; } } }
3、支援可撤銷的操作
命令模式在需要支援可撤銷操作的情況下也能輕鬆應對,假如我們需要給玩家提供撤銷移動操作的功能時,我們可以先把玩家輸入產生的command存入棧中(或者其他資料結構),在撤銷時,從棧中取出棧頂的Command,再呼叫該Command的Undo(),就實現了撤銷功能(Undo()為撤銷方法,與Excute()相反),程式碼如下:using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameActor { public Transform selfTra; public void Move(Vector3 offset) { selfTra.Translate(offset); } } public class Actor1 : GameActor { } public class Actor2 : GameActor { } public abstract class Command{ public abstract void Excute(GameActor actor);//執行 public abstract void Undo(GameActor actor);//撤銷 } public class MoveCommand : Command { public Vector3 moveOffset; public MoveCommand(Vector3 offset) { moveOffset = offset; } public override void Excute(GameActor actor) { actor.Move(moveOffset); } public override void Undo(GameActor actor) { actor.Move(-moveOffset); } } public class CommandControl : MonoBehaviour { private Command moveCommand; private GameActor actor; private Stack<Command> commandStack; private void Start() { moveCommand = new MoveCommand(Vector3.one); actor = new Actor1(); commandStack = new Stack<Command>(); } private void Update() { Command cmd = HandleInput(); if (cmd != null) { commandStack.Push(cmd); cmd.Excute(actor); } } //需要撤銷操作時呼叫這個函式 public void PlayReverse() { if (commandStack.Count > 0) { commandStack.Pop().Undo(actor); } } //處理使用者輸入 public Command HandleInput() { if (Input.GetKeyDown(KeyCode.A)) { return new MoveCommand(new Vector3(2, 4, 5)); } if (Input.GetKeyDown(KeyCode.B)) { return new MoveCommand(new Vector3(1, 2, 4)); } else { return null; } } }上面程式碼中, 每次產生一個command時就將它存到Stack中,當需要撤銷操作時,就取出Stack頂部的command,並執行它的Undo(),按照這種方法,可以實現多重撤銷。
4、總結
通過上面的例子,我們再看命令模式的定義:將一個請求封裝為一個物件,從而使你可以用不同的請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可撤銷的操作。現在我們差不多明白了命令模式的用法,它優點很明顯,缺點也是有的:第一個優點是類間解耦,呼叫者和接收者之間沒有任何依賴關係,呼叫者在實現功能時只需呼叫Command抽象類的Excute方法即可,不需要關注是哪個接收者執行;第二個優點是可擴充套件性,Command的子類可以很容易地擴充套件;缺點是如果有大量命令,那麼Command的子類將會非常龐大。我們在實際開發中,應該發揮出命令模式的優點,並結合其他模式,減少Command子類龐大的問題。