目录
- 前言
- 一、工厂模式
- 二、单例模式
- 三、观察者模式
- 观察者模式的优势
- 四、状态模式
- 状态模式的优势
- 五、策略模式
- 策略模式的优势
- 策略模式与状态模式有什么区别呢?
- 六、组合模式
- 七、命令模式
- 八、装饰器模式
前言
本文介绍了游戏开发中常用的设计模式,如工厂模式用于创建对象,单例模式确保全局唯一,观察者模式实现对象间事件通知,状态模式管理对象状态转换,策略模式提供行为选择,组合模式构建复杂对象结构,命令模式分离操作与执行,装饰模式动态扩展功能。
- 单例模式:用于确保在游戏中只存在一个实例,例如游戏管理器(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];}
}