目錄
- 前言
- 一、工廠模式
- 二、單例模式
- 三、觀察者模式
- 觀察者模式的優勢
- 四、狀態模式
- 狀態模式的優勢
- 五、策略模式
- 策略模式的優勢
- 策略模式與狀態模式有什么區別呢?
- 六、組合模式
- 七、命令模式
- 八、裝飾器模式
前言
本文介紹了游戲開發中常用的設計模式,如工廠模式用于創建對象,單例模式確保全局唯一,觀察者模式實現對象間事件通知,狀態模式管理對象狀態轉換,策略模式提供行為選擇,組合模式構建復雜對象結構,命令模式分離操作與執行,裝飾模式動態擴展功能。
- 單例模式:用于確保在游戲中只存在一個實例,例如游戲管理器(Game Manager)或資源管理器(Resource Manager)。
- 工廠模式:用于創建對象實例,例如創建不同類型的敵人(Enemy)或武器(Weapon)。
- 觀察者模式:用于實現對象間的事件通知,例如實現角色(Character)與任務(Quest)的交互。
- 狀態模式:用于管理游戲中對象的狀態轉換,例如角色在游戲中的狀態(生命值、能量等)。
- 策略模式:用于實現不同的算法和行為,例如實現不同的AI(Artificial Intelligence)策略。
- 組合模式:用于創建和管理游戲中的復雜對象結構,例如實現游戲中的菜單(Menu)或場景(Scene)。
- 命令模式:用于將操作(操作)與其執行分離,例如實現游戲中的鍵盤快捷鍵。
- 裝飾器模式:通過創建一個包裝對象,即裝飾器,來包裹真正的對象,并且在保持接口的前提下,為它提供額外的功能。
一、工廠模式
工廠模式是一種常用的設計模式,用于創建對象,它能夠隱藏創建對象的復雜性,并且使代碼更加靈活。在游戲開發中,工廠模式通常用于創建游戲對象、敵人、道具等。
//首先我們定義一個接口,表示我們要創建的對象:
public interface IGameObject
{void Update();
}//創建具體的游戲對象類:
public class Player : IGameObject
{public void Update(){Console.WriteLine("Player is updating.");}
}public class Enemy : IGameObject
{public void Update(){Console.WriteLine("Enemy is updating.");}
}//創建一個工廠類,用于創建游戲對象
public class GameObjectFactory
{public IGameObject CreateGameObject(string type){switch (type){case "Player":return new Player();case "Enemy":return new Enemy();default:throw new ArgumentException($"Invalid game object type: {type}");}}
}//使用工廠類來創建游戲對象:
GameObjectFactory factory = new GameObjectFactory();IGameObject player = factory.CreateGameObject("Player");
player.Update();IGameObject enemy = factory.CreateGameObject("Enemy");
enemy.Update();
二、單例模式
單例模式是一種常用的設計模式,用于確保一個類只有一個實例,并提供全局訪問點。在游戲開發中,單例模式通常用于管理全局狀態、資源池等。
public class GameManager
{private static GameManager _instance;// 私有構造函數,確保只能在類內部創建實例private GameManager(){// 初始化游戲管理器Console.WriteLine("GameManager initialized.");}// 全局訪問點public static GameManager Instance{get{if (_instance == null){_instance = new GameManager();///懶漢式}return _instance;}}// 游戲管理器的功能public void StartGame(){Console.WriteLine("Game started.");}
}
GameManager gameManager = GameManager.Instance;gameManager.StartGame(); // Output: "Game started."GameManager gameManager2 = GameManager.Instance; // 和 gameManager 引用同一個對象
三、觀察者模式
觀察者模式在游戲開發中通常用于紅點系統,實現觀察者模式時要注意具體目標對象和具體觀察者對象之間不能直接調用,否則將使兩者之間緊密耦合起來,這違反了面向對象的設計原則。
觀察者模式的主要角色如下:
- 抽象主題(Subject)角色:也叫抽象目標類,它提供了一個用于保存觀察者對象的聚集類和增加、刪除觀察者對象的方法,以及通知所有觀察者的抽象方法。
- 具體主題(Concrete Subject)角色:也叫具體目標類,它實現抽象目標中的通知方法,當具體主題的內部狀態發生改變時,通知所有注冊過的觀察者對象。
- 抽象觀察者(Observer)角色:它是一個抽象類或接口,它包含了一個更新自己的抽象方法,當接到具體主題的更改通知時被調用。
- 具體觀察者(Concrete Observer)角色:實現抽象觀察者中定義的抽象方法,以便在得到目標的更改通知時更新自身的狀態。
using System.Collections.Generic;
using UnityEngine;//抽象類 觀察者
public interface Observer
{void response(); //反應
}//被觀察者
public class ConcreteSubject
{public static ConcreteSubject _instance = null;protected List<Observer> observers = new List<Observer>();public void Init(){}public static ConcreteSubject Instance(){if (_instance == null){_instance = new ConcreteSubject();}return _instance;}//增加觀察者方法public void add(Observer observer){observers.Add(observer);}//刪除觀察者方法public void remove(Observer observer){observers.Remove(observer);}public void notifyObserver(){Debug.Log("具體目標發生改變...");foreach(Observer obs in observers){obs.response();}}
}//具體觀察者1
public class ConcreteObserver1 : MonoBehaviour , Observer
{private void Start(){ConcreteSubject.Instance().add(this);}public void response(){Debug.Log("具體觀察者1作出反應!");}private void OnDestroy(){ConcreteSubject.Instance().remove(this);}
}//具體觀察者2
public class ConcreteObserver2 : MonoBehaviour, Observer
{private void Start(){ConcreteSubject.Instance().add(this);}public void response(){Debug.Log("具體觀察者2作出反應!");}private void OnDestroy(){ConcreteSubject.Instance().remove(this);}}
觀察者模式的優勢
- 松散耦合:觀察者模式允許構建松散耦合的類關系,這在游戲開發中非常重要,因為它可以降低系統各部分之間的耦合度。
- 提高系統的靈活性和可維護性:觀察者模式不僅能夠降低系統各部分之間的耦合度,還能提高系統的靈活性和可維護性。
- 解耦和事件驅動:觀察者模式特別適用于需要響應UI事件或進行成就系統設計的場景,它允許完全解耦控制邏輯和UI事件處理。
四、狀態模式
狀態模式(State Pattern)是一種行為設計模式,它允許一個對象在其內部狀態改變時改變其行為。在游戲開發中,狀態模式常用于實現角色的不同行為狀態切換,例如玩家角色的行走、奔跑、跳躍、攻擊等不同狀態。
// 定義抽象狀態類
public abstract class CharacterState
{protected Character character;public void SetCharacter(Character _character){this.character = _character;}// 抽象方法,子類需要實現具體行為public abstract void Update();
}// 具體狀態類:IdleState
public class IdleState : CharacterState
{public override void Update(){Debug.Log("角色處于閑置狀態");// 檢查是否應該轉換到其他狀態,如按下移動鍵則切換至MoveStateif (Input.GetKey(KeyCode.W)){character.ChangeState(new MoveState());}}
}// 具體狀態類:MoveState
public class MoveState : CharacterState
{public override void Update(){Debug.Log("角色正在移動");// 檢查是否應返回閑置狀態或切換至其他狀態if (!Input.GetKey(KeyCode.W)){character.ChangeState(new IdleState());}}
}// 角色類持有當前狀態并處理狀態切換
public class Character : MonoBehaviour
{private CharacterState currentState;public void ChangeState(CharacterState newState){if (currentState != null){currentState.SetCharacter(null);}currentState = newState;currentState.SetCharacter(this);}void Update(){currentState.Update();}
}
狀態模式的優勢
- 封裝狀態轉換:狀態模式將狀態轉換的邏輯封裝到狀態類內部,使得狀態之間的切換變得明確和集中。
- 簡化復雜條件邏輯:通過將不同狀態的行為分割開來,狀態模式減少了對象間的相互依賴,提高了可維護性和可擴展性。
- 清晰的狀態管理:特別是在Unity引擎中,狀態模式幫助游戲場景的切換和管理變得更加清晰。
五、策略模式
如何在Unity中實現策略模式以優化角色行為和AI策略?
在Unity中實現策略模式以優化角色行為和AI策略,可以按照以下步驟進行:
- 定義策略類:首先,將不同的行為或算法封裝成獨立的類(策略)。每個策略類代表一種特定的行為或算法。例如,可以為角色攻擊、移動、防御等行為分別創建一個策略類。
- 使用接口或抽象類:為了使策略類之間可以互相替換,建議使用接口或抽象類來定義每種策略需要實現的方法。這樣可以確保所有策略類都遵循相同的協議。
- 動態選擇和切換策略:在運行時根據需要動態選擇和切換不同的策略。這可以通過檢查游戲中的某些條件或事件來實現。例如,當敵人接近玩家時,可以選擇攻擊策略;當敵人遠離玩家時,可以選擇逃跑策略。
- 避免條件語句過多:使用策略模式可以有效減少代碼中的條件語句,從而避免代碼變得臃腫和難以維護。通過將具體算法實現從具體的業務邏輯中分離出來,可以讓算法的變化獨立于使用算法的客戶端。
- 示例代碼:以下是一個簡單的示例代碼,展示了如何在Unity中實現策略模式:
// 攻擊策略類
public class AttackStrategy : IStrategy
{public void PerformAction(){Debug.Log("Attacking");}
}// 移動策略類
public class MoveStrategy : IStrategy
{public void PerformAction(){Debug.Log("Moving");}
}// 防御策略類
public class DefenseStrategy : IStrategy
{public void PerformAction(){Debug.Log("防御");}
}// 策略選擇器
public class StrategySelector
{private IStrategy _strategy;public void SetStrategy(IStrategy strategy){_strategy = strategy;}public void PerformAction(){_strategy.PerformAction();}
}// 主腳本
public class Player : MonoBehaviour
{private StrategySelector _selector;void Start(){_selector = new StrategySelector();_selector.SetStrategy(new AttackStrategy());_selector.PerformAction(); // 輸出:Attacking// 根據條件切換策略if (playerHealth < 50){_selector.SetStrategy(new DefenseStrategy());_selector.PerformAction(); // 輸出:防御}}
}
策略模式的優勢
- 算法獨立性:策略模式使得算法可以獨立于使用它的客戶端變化。這意味著可以根據不同的游戲狀態、角色類型或玩家選擇,動態地改變游戲的行為。
- 靈活性和多態性:通過將算法封裝在獨立的策略類中,策略模式提供了一種更靈活的方式來處理多態行為。這使得算法的變化不會影響到使用這些算法的客戶。
- 簡化復雜條件邏輯:策略模式能夠減少對象間的相互依賴,并且將與特定狀態相關的行為局部化到一個狀態中,從而滿足單一職責原則。游戲開發設計模式之策略模式
策略模式與狀態模式有什么區別呢?
現在我們知道,狀態模式和策略模式的結構是相似的,但它們的意圖不同。讓我們重溫一下它們的主要不同之處:
- 策略模式封裝了一組相關算法,它允許Client在運行時使用可互換的行為;狀態模式幫助一個類在不同的狀態顯示不同的行為。
- 狀態模式封裝了對象的狀態,而策略模式封裝算法或策略。因為狀態是跟對象密切相關的,它不能被重用;而通過從Context中分離出策略或算法,我們可以重用它們。
- 在狀態模式中,每個狀態通過持有Context的引用,來實現狀態轉移;但是每個策略都不持有Context的引用,它們只是被Context使用。
- 策略實現可以作為參數傳遞給使用它的對象,例如Collections.sort(),它的參數包含一個Comparator策略。另一方面,狀態是Context對象自己的一部分,隨著時間的推移,Context對象從一個狀態轉移到另一個狀態。
- 雖然它們都符合OCP原則,策略模式也符合SRP原則(單一職責原則),因為每個策略都封裝自己的算法,且不依賴其他策略。一個策略的改變,并不會導致其他策略的變化。
- 另一個理論上的不同:策略模式定義了對象“怎么做”的部分。例如,排序對象怎么對數據排序。狀態模式定義了對象“是什么”和“什么時候做”的部分。例如,對象處于什么狀態,什么時候處在某個特定的狀態。
- 狀態模式中很好的定義了狀態轉移的次序;而策略模式并無此需要:Client可以自由的選擇任何策略。
- 一些常見的策略模式的例子是封裝算法,例如排序算法,加密算法或者壓縮算法。如果你看到你的代碼需要使用不同類型的相關算法,那么考慮使用策略模式吧。而識別何時使用狀態模式是很簡單的:如果你需要管理狀態和狀態轉移,但不想使用大量嵌套的條件語句,那么就是它了。
最后但最重要的一個不同之處是,策略的改變由Client完成;而狀態的改變,由Context或狀態自己。
參考文檔:設計模式之:狀態模式和策略模式的區別
六、組合模式
組合模式一般適用于對象的部分-整體層次分明。比如游戲中的文件夾目錄結構的管理。
樹狀結構圖
using System.Collections;
using System.Collections.Generic;
using UnityEngine;/// <summary>
/// 組合模式
/// </summary>
public class CompositeMode : MonoBehaviour
{private void Start(){INode root = new CompositeNode("Character");INode leftHand = new LeafNode("LeftHand");INode body = new CompositeNode("Body");INode rightHand = new LeafNode("RightHand");root.AddChildNode(leftHand, body, rightHand);INode leftFoot = new LeafNode("LeftFoot");INode rightFoot = new LeafNode("RightFoot");body.AddChildNode(leftFoot, rightFoot);ShowAllNode(root);}/// <summary>/// 顯示節點和其所有子節點/// </summary>private void ShowAllNode(INode node){Debug.Log(node.Name);List<INode> childNodeList = node.ChildNodeList;if (node == null || childNodeList == null){return;}foreach (INode item in childNodeList){ShowAllNode(item);}}
}/// <summary>
/// 節點抽象類
/// </summary>
public abstract class INode
{protected string mName;public string Name { get { return mName; } }protected List<INode> mChildNodeList;public List<INode> ChildNodeList { get { return mChildNodeList; } }public INode(string name){mChildNodeList = new List<INode>();mName = name;}//添加、移除、獲取子節點public abstract void AddChildNode(INode node);//如果我們想可以一次添加多個子節點,就可以這樣寫public abstract void AddChildNode(params INode[] nodes);public abstract void RemoveChildNode(INode node);public abstract INode GetChildNode(int index);}
/// <summary>
/// 葉子節點
/// </summary>
public class LeafNode : INode
{public LeafNode(string name):base(name){}//葉子節點無子節點public override void AddChildNode(INode node){throw new System.NotImplementedException();}public override void AddChildNode(params INode[] nodes){throw new System.NotImplementedException();}public override INode GetChildNode(int index){throw new System.NotImplementedException();}public override void RemoveChildNode(INode node){throw new System.NotImplementedException();}
}
/// <summary>
/// 非葉子節點
/// </summary>
public class CompositeNode : INode
{public CompositeNode(string name): base(name){}public override void AddChildNode(INode node){mChildNodeList.Add(node);}public override void AddChildNode(params INode[] nodes){foreach (INode node in nodes){mChildNodeList.Add(node);}}public override void RemoveChildNode(INode node){if (mChildNodeList.Contains(node) == false){Debug.LogError(node + "在子節點中不存在");return;}mChildNodeList.Remove(node);}public override INode GetChildNode(int index){if ((index>=0 && index<mChildNodeList.Count)==false){Debug.LogError(index + "下標不存在");return null;}return mChildNodeList[index];}
}