命令模式
***将命令封装,与目标行为解耦,使命令由流程概念变为对象数据
既然命令变成了数据,就是可以被传递、存储、重复利用的
命令模式(Command Pattern)是一种行为设计模式,它将一个请求封装为一个对象,从而允许用户使用不同的请求、队列或日志请求来参数化其他对象。命令模式也支持可撤销的操作
***应用场景
- 如果需要通过操作来参数化对象, 可使用命令模式。
- 如果想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式。
- 如果想要实现操作回滚功能, 可使用命令模式。
***实现方式
- Command:定义命令的接口,声明执行操作的方法。
- ConcreteCommand:Command 接口的实现对象,它对应于具体的行为和接收者的绑定。
- Client:创建具体的命令对象,并且设置它的接收者。
- Invoker:要求命令对象执行请求。
- Receiver:知道如何实施与执行一个请求相关的操作。
***优点
- 控制请求处理的顺序:你可以控制请求处理的顺序。
- 单一职责原则:你可对发起操作和执行操作的类进行解耦。
- 开闭原则:你可以在不更改现有代码的情况下在程序中新增处理者
将一个请求封装为一个对象,从而允许使用不同的请求或队列或日志将客户端参数化,同时支持请求操作的撤销与恢复
命令就是一个对象化(实例化)的方法调用,封装在一个对象中的方法调用
可以理解命令是面向对象化的回调
cs
// Avatar类继承自MonoBehaviour,这是一个操作者类,负责处理对象的移动。
public class Avatar : MonoBehaviour {
private Transform mTransform;
void Start () {
mTransform = transform;
}
public void Move(Vector3 DeltaV3)
{
// 使用Transform的Translate方法实现位移。
mTransform.Translate(DeltaV3);
}
}
// Command类是一个抽象基类,定义了命令的接口。
public abstract class Command {
protected float _TheTime; // 命令对应的时间戳。
public float TheTime
{
get { return _TheTime; }
}
public virtual void execute(Avatar avatar) { }
public virtual void undo(Avatar avatar) { }
}
// CommandMove类继承自Command类,实现了具体的移动命令
public class CommandMove : Command {
private Vector3 TransPos;
// 构造函数,接收移动位置和命令执行的时间。
public CommandMove(Vector3 transPos, float time)
{
TransPos = transPos; // 初始化移动目标位置。
_TheTime = time; // 初始化时间。
}
// 实现执行命令的方法。
public override void execute(Avatar avatar)
{
// 调用Avatar的Move方法实现移动。
avatar.Move(TransPos);
}
// 实现撤销命令的方法。
public override void undo(Avatar avatar)
{
// 调用Avatar的Move方法,使用相反的向量实现撤销移动。
avatar.Move(-TransPos);
}
}
// CommandManager类负责管理和调用命令
public class CommandManager : MonoBehaviour
{
public Avatar TheAvatar; // 对Avatar对象的引用。
private Stack<Command> mCommandStack; // 用于存储命令的堆栈。
private float mCallBackTime; // 记录命令执行的累计时间。
public bool IsRun = true; // 标志变量,决定是否运行命令或撤销操作。
void Start()
{
// 初始化命令堆栈和时间。
mCommandStack = new Stack<Command>();
mCallBackTime = 0;
}
void Update()
{
// 根据IsRun变量选择执行命令或运行撤销。
if (IsRun)
{
Control(); // 执行命令控制。
}
else
{
RunCallBack(); // 执行撤销操作。
}
}
private void Control()
{
// 增加命令执行时间。
mCallBackTime += Time.deltaTime;
// 获取当前输入的命令对象。
Command cmd = InputHandler();
// 如果命令对象不为null,则推入堆栈并执行它。
if (cmd != null)
{
mCommandStack.Push(cmd);
cmd.execute(TheAvatar);
}
}
private void RunCallBack()
{
// 减少当前时间。
mCallBackTime -= Time.deltaTime;
// 如果堆栈中存在命令并且当前时间小于堆栈顶部命令的时间,则执行撤销操作。
if (mCommandStack.Count > 0 && mCallBackTime < mCommandStack.Peek().TheTime)
{
// 弹出并撤销顶部命令。
mCommandStack.Pop().undo(TheAvatar);
}
}
private Command InputHandler()
{
// 根据输入生成移动命令。
if (Input.GetKey(KeyCode.W))
{
return new CommandMove(new Vector3(0, Time.deltaTime, 0), mCallBackTime);
}
if (Input.GetKey(KeyCode.S))
{
return new CommandMove(new Vector3(0, -Time.deltaTime, 0), mCallBackTime);
}
if (Input.GetKey(KeyCode.A))
{
return new CommandMove(new Vector3(-Time.deltaTime, 0, 0), mCallBackTime);
}
if (Input.GetKey(KeyCode.D))
{
return new CommandMove(new Vector3(Time.deltaTime, 0, 0), mCallBackTime);
}
return null; // 如果没有按键被检测到,返回null。
}
}
享元模式
***不同的实例共享相同的特性(共性),同时保留自己的特性部分
- ***传递的信息享元化,可以节约计算时间
- ***存储的信息享元化,可以节约占用的空间
- ***所以享元模式可以减少时间与空间上的代价
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来尽量减少内存的使用。它通过将重复使用的对象分离成共享和非共享部分,达到复用的目的,从而有效节省内存。享元模式的核心思想是将具有相同内部状态的对象共享,以减少内存占用
***应用场景
- 当系统中存在大量相似或相同的对象。
- 对象的创建和销毁成本较高。
- 对象的状态可以外部化,即对象的部分状态可以独立于对象本身存在。
- 当需要创建大量相似对象以及共享公共信息的场景,同时也适用于需要考虑性能优化和共享控制的系统。
***实现方式
- 享元工厂(Flyweight Factory):负责创建和管理享元对象,通常包含一个池(缓存)用于存储和复用已经创建的享元对象。
- 具体享元(Concrete Flyweight):实现了抽象享元接口,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态则由客户端传递。
- 抽象享元(Flyweight):定义了具体享元和非共享享元的接口,通常包含了设置外部状态的方法。
- 客户端(Client):使用享元工厂获取享元对象,并通过设置外部状态来操作享元对象。客户端通常不需要关心享元对象的具体实现。
***优点
- 减少内存消耗:通过共享对象,减少了内存中对象的数量。
- 提高效率:减少了对象创建的时间,提高了系统效率。
***缺点
- 增加系统复杂度:需要分离内部状态和外部状态,增加了设计和实现的复杂性。
- 线程安全问题:如果外部状态处理不当,可能会引起线程安全问题
享元模式通过将对象数据切分成两种类型来解决问题。
- 第一种类型数据是那些不属于单一实例对象并且能够被所有对象共享的数据,例如是树木的几何形状和纹理数据等
- 其他数据便是外部状态,对于每一个实例它们都是唯一的。例如每棵树的位置、缩放比例和颜色
c#
//随机大小位置渲染
internal class SpecificAttributes
{
public Matrix4x4 TransMatrix;//转换矩阵
internal Vector3 mPos;
internal Vector3 mScale;
internal Quaternion mRot;
public SpecificAttributes()
{
mPos = UnityEngine.Random.insideUnitSphere * 10;
mRot = Quaternion.LookRotation(UnityEngine.Random.insideUnitSphere);
mScale = Vector3.one * UnityEngine.Random.Range(1, 3);
TransMatrix = Matrix4x4.TRS(mPos, mRot, mScale);//基于位置、旋转和缩放创建转换矩阵
}
}
//用于实例化测试用的对象
public class CubeBase : MonoBehaviour
{
private MeshRenderer mMR;
private MeshFilter mMF;
public Mesh mSharedMesh;
public Material mSharedMaterial;
private Matrix4x4[] TransInfos;
private void Start()
{
Init();
}
//初始化组件和共享材质和网格
public void Init()
{
mMR = GetComponent<MeshRenderer>();
mMF = GetComponent<MeshFilter>();
if (mMR == null)
{
Debug.LogError("MeshRenderer component is missing on the GameObject.");
return;
}
if (mMF == null)
{
Debug.LogError("MeshFilter component is missing on the GameObject.");
return;
}
mSharedMaterial = mMR.sharedMaterial;
mSharedMesh = mMF.sharedMesh;
if (mSharedMesh == null)
{
Debug.LogError("The shared mesh in the MeshFilter is null. Ensure a valid mesh is assigned.");
}
}
//使用Graphics.DrawMeshInstanced方法绘制实例化网格
internal void ObjInstancing(int num)
{
if (mSharedMesh == null)
{
Debug.LogError("Cannot draw instances because the shared mesh is null.");
return;
}
TransInfos = new Matrix4x4[num];
for (int i = 0; i < num; i++)
{
SpecificAttributes sa = new SpecificAttributes();
TransInfos[i] = sa.TransMatrix;
}
Graphics.DrawMeshInstanced(mSharedMesh, 0, mSharedMaterial, TransInfos);
}
}
//享元模式控制类
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class FlyweightManager : MonoBehaviour
{
public GameObject TheObj;
public bool IsFlyweight=true;
private Text StatusText;
private List<Transform> ObjTrs;
void Start()
{
ObjTrs = new List<Transform>();
StatusText=GameObject.Find("StatusText").GetComponent<Text>();
StatusText.text= IsFlyweight?"禁用享元模式":"启用享元模式";
}
void Update()
{
if (IsFlyweight)
{
GenerateObjsWithInstancing(1000);
}
else
{
GenerateObjsWithoutInstancing(1000);
}
}
public void SwitchFlyweight()
{
IsFlyweight = !IsFlyweight;
StatusText.text= IsFlyweight?"禁用享元模式":"启用享元模式";
}
//使用CubeBase脚本的ObjInstancing方法来生成指定数量的实例化对象
public void GenerateObjsWithInstancing(int num)
{
// 遍历 ObjTrs 列表,销毁所有已经存在的游戏对象
for (int i = 0; i < ObjTrs.Count; i++)
{
Destroy(ObjTrs[i].gameObject);
}
// 清空 ObjTrs 列表,移除所有引用
ObjTrs.Clear();
// 调用 CubeBase 脚本的 ObjInstancing 方法来生成指定数量的实例化对象
TheObj.GetComponent<CubeBase>().ObjInstancing(num);
}
//创建非实例化对象
public void GenerateObjsWithoutInstancing(int num)
{
// 遍历 ObjTrs 列表,销毁所有已经存在的游戏对象
for (int i = 0; i < ObjTrs.Count; i++)
{
Destroy(ObjTrs[i].gameObject);
}
// 清空 ObjTrs 列表,移除所有引用
ObjTrs.Clear();
// 循环创建指定数量的新游戏对象
for (int i = 0; i < num; i++)
{
// 创建 SpecificAttributes 实例,用于存储新对象的属性
SpecificAttributes sa = new SpecificAttributes();
// 使用 Instantiate 方法创建 TheObj 的副本,并设置其位置和旋转
Transform tr = Instantiate(TheObj, sa.mPos, sa.mRot).transform;
// 设置新对象的材质颜色为白色
tr.GetComponent<MeshRenderer>().material.color = Color.white;
// 设置新对象的局部缩放
tr.localScale = sa.mScale;
// 将新创建的对象的 Transform 添加到 ObjTrs 列表中
ObjTrs.Add(tr);
}
}
}
考虑这么一个需求:
在游戏中,同时有一万个敌人
暂时不考虑渲染压力,单纯设计一个敌人属性的实现
敌人的属性包括
- 血量
- 身高
- 职级有四种:新兵,中士,上士,上尉
- 血量上限(根据职级不同而变,依次是100,200,500,1000)
- 移动速度( 根据职级不同而变,依次是 1.0f , 2.0f , 3.0f , 4.0f )
- 职级对应名字(根据职级不同而变,依次是新兵,中士,上士,上尉)
最原始的做法就是声明一个Attr类,它包含hp,hpMax,mp,mpMax,name
带来的问题是内存浪费,每一个属性对象都同时开辟了hp,hpMax,mp,mpMax,name
的内存空间
可以把职级做成枚举,职级对应的属性定义一个字典
cs
public enum AttrType : uint
{
// 新兵
Recruit,
// 中士
StaffSergeant,
// 上士
Sergeant,
// 上尉
Captian,
}
public static readonly Dictionary<AttrType, Dictionary<string, float>> BaseAttrDict = new Dictionary<AttrType, Dictionary<string, float>>
{
{AttrType.Recruit , new Dictionary<string, float>
{
{"MaxHp",100.0f},
{"MoveSpeed",1.0f},
}},
{AttrType.Recruit , new Dictionary<string, float>
{
{"MaxHp",200.0f},
{"MoveSpeed",2.0f},
}},
{AttrType.Recruit , new Dictionary<string, float>
{
{"MaxHp",500.0f},
{"MoveSpeed",3.0f},
}}, {AttrType.Recruit , new Dictionary<string, float>
{
{"MaxHp",1000.0f},
{"MoveSpeed",4.0f},
}},
};
public float GetMapHp()
{
return Const.BaseAttrDict[baseAttrType]["MaxHp"];
}
public float GetMoveSpeed()
{
return Const.BaseAttrDict[baseAttrType]["MoveSpeed"];
}
但需求里有一项,要求能访问到职级对应的中文名 为了实现需求,字典的value不能是float
,而应该是object
cs
public static readonly Dictionary<AttrType, Dictionary<string, object>> BaseAttrDict = new Dictionary<AttrType, Dictionary<string, object>>
{
{AttrType.Recruit , new Dictionary<string, object>
{
{"MaxHp",100},
{"MoveSpeed",1.0f},
{"Name","新兵"}
}},
{AttrType.Recruit , new Dictionary<string, object>
{
{"MaxHp",200},
{"MoveSpeed",2.0f},
{"Name","中士"}
}},
{AttrType.Recruit , new Dictionary<string, object>
{
{"MaxHp",500},
{"MoveSpeed",3.0f},
{"Name","上士"}
}}, {AttrType.Recruit , new Dictionary<string, object>
{
{"MaxHp",1000},
{"MoveSpeed",4.0f},
{"Name","上尉"}
}},
};
public string GetName()
{
return (string)Const.BaseAttrDict[baseAttrType]["Name"];
}
public int GetMapHp()
{
return (int)Const.BaseAttrDict[baseAttrType]["MaxHp"];
}
public float GetMoveSpeed()
{
return (float)Const.BaseAttrDict[baseAttrType]["MoveSpeed"];
}
这种写法有一个致命的问题:拆箱封箱(如果可以设置值)带来的性能消耗
在一个字典里存下各种类型的变量,被迫使用了object
,但是再使用的时候,又要把object
拆箱成对应的实际类型,这个配置字典方案只有在不会频繁调用这些属性的情况下才会用
使用享元模式,首先在设计上找到"可共享"的属性,在这个例子中是:maxHp,moveSpeed,name
,将共享属性放到一起
cs
public class FlyweightAttr
{
public int maxHp { get; set; }
public float moveSpeed { get; set; }
public string name { get; set; }
public FlyweightAttr(string name, int maxHp, float moveSpeed)
{
this.name = name;
this.maxHp = maxHp;
this.moveSpeed = moveSpeed;
}
}
士兵的属性类SoldierAttr
持有FlyweightAttr
这个引用,并包含不可共享的属性:hp
和height
cs
public class SoldierAttr
{
public int hp { get; set; }
public float height { get; set; }
public FlyweightAttr flyweightAttr { get; }
public SoldierAttr(FlyweightAttr flyweightAttr, int hp, float height)
{
this.flyweightAttr = flyweightAttr;
this.hp = hp;
this.height = height;
}
public int GetMaxHp()
{
return flyweightAttr.maxHp;
}
public float GetMoveSpeed()
{
return flyweightAttr.moveSpeed;
}
public string GetName()
{
return flyweightAttr.name;
}
}
再增加一个工厂类来让外部容易获取到属性
cs
public class AttrFactory
{
/// <summary>
/// 属性类型枚举
/// </summary>
public enum AttrType : uint
{
// 新兵
Recruit = 0,
// 中士
StaffSergeant,
// 上士
Sergeant,
// 上尉
Captian,
}
// 基础属性缓存
private Dictionary<AttrType, FlyweightAttr> _flyweightAttrDB = null;
public AttrFactory()
{
_flyweightAttrDB = new Dictionary<AttrType, FlyweightAttr>();
_flyweightAttrDB.Add(AttrType.Recruit, new FlyweightAttr("士兵", 100, 1.0f));
_flyweightAttrDB.Add(AttrType.StaffSergeant, new FlyweightAttr("中士", 200, 2.0f));
_flyweightAttrDB.Add(AttrType.Sergeant, new FlyweightAttr("上士", 500, 3.0f));
_flyweightAttrDB.Add(AttrType.Captian, new FlyweightAttr("上尉", 1000, 4.0f));
}
// 创建角色属性
public SoldierAttr CreateSoldierAttr(AttrType type, int hp, float height)
{
if (!_flyweightAttrDB.ContainsKey(type))
{
Debug.LogErrorFormat("{0}属性不存在", type);
return null;
}
FlyweightAttr flyweightAttr = _flyweightAttrDB[type];
SoldierAttr attr = new SoldierAttr(flyweightAttr, hp, height);
return attr;
}
}
static void Main(string[] args){
//测试代码
AttrFactory factory = new AttrFactory();
for (int i = 0; i < _enemy_max; i++)
{
var values = Enum.GetValues(typeof(AttrFactory.AttrType));
AttrFactory.AttrType attrType = (AttrFactory.AttrType)values.GetValue(UnityEngine.Random.Range(0, 3));
SoldierAttr soldierAttr = factory.GetSoldierAttr(attrType, UnityEngine.Random.Range(0, 100), UnityEngine.Random.Range(155.0f, 190.0f));
objectsUseFlyweight.Add(soldierAttr);
}
}
可以使用原始的属性类去对比性能
cs
public class HeavySoldierAttr : MonoBehaviour
{
public int hp { get; set; }
public float height { get; set; }
public int maxHp { get; set; }
public float moveSpeed { get; set; }
public string name { get; set; }
public HeavySoldierAttr(int hp, float height, int maxHp, float moveSpeed, string name)
{
this.hp = hp;
this.height = height;
this.maxHp = maxHp;
this.moveSpeed = moveSpeed;
this.name = name;
}
}
观察者模式
***观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象间的一种一对多的依赖关系,使得当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。这种模式又称为发布-订阅模式
***应用场景
- 当一个对象的改变需要同时改变其他对象时。
- 当需要在不同对象间保持同步时。
- 当一个对象必须在另一个对象状态变化时自动更新时。
***实现方式
- 主题(Subject):也称为"被观察者",它维护一组观察者,提供用于注册和注销观察者的接口,并在状态改变时通知它们。
- 观察者(Observer):提供一个更新接口,用于在主题状态改变时得到通知。
- 具体主题(Concrete Subject):实现主题接口,当其状态改变时,向观察者发送通知。
- 具体观察者(Concrete Observer):实现观察者接口,执行与主题状态变化相关的动作。
***优点
- 实现对象间的松耦合:主题与观察者之间的耦合度低,主题不需要知道观察者的具体实现。
- 支持广播通信:主题可以向所有注册的观察者广播通知,无需知道观察者的具体数量和类型。
- 灵活性和可扩展性:可以动态地添加或删除观察者,而不需要修改主题的代码。
***缺点
- 可能导致性能问题:如果观察者列表很长,通知所有观察者可能会导致性能下降。
- 可能导致循环依赖:如果观察者和主题之间存在循环依赖,可能会导致复杂的状态更新问题
C#将它集成在了语言层面,event
关键字
假设有一个角色血量变化时需要通知其他对象的游戏场景。可以使用观察者模式实现这种场景,让其他对象在角色血量变化时得到通知并作出相应的处理
cs
// 定义可观察的角色对象接口
public interface ICharacterObservable
{
//添加观察者
void AddObserver(ICharacterObserver observer);
//移除观察者
void RemoveObserver(ICharacterObserver observer);
// 通知所有观察者
void NotifyObservers();
}
// 定义观察者对象接口
public interface ICharacterObserver
{
// 角色血量变化时的处理方法
void OnCharacterHealthChanged(int newHealth);
}
// 具体的角色类,实现ICharacterObservable接口
public class GameCharacter : ICharacterObservable
{
//存储实现了ICharacterObserver接口的观察者对象。这个集合用于管理所有对该角色对象感兴趣的观察者,以便在角色状态发生改变时通知它们
private List<ICharacterObserver> observers = new List<ICharacterObserver>();
private int health;
// 角色的血量属性
public int Health
{
get { return health; }
set
{
health = value;
NotifyObservers(); // 血量变化时通知所有观察者
}
}
// 添加观察者
public void AddObserver(ICharacterObserver observer)
{
observers.Add(observer);
}
// 移除观察者
public void RemoveObserver(ICharacterObserver observer)
{
observers.Remove(observer);
}
// 通知所有观察者
public void NotifyObservers()
{
foreach (var observer in observers)
{
observer.OnCharacterHealthChanged(health); // 通知观察者处理角色血量变化
}
}
}
// 具体的观察者类,实现ICharacterObserver接口
public class EnemyAI : ICharacterObserver
{
// 处理角色血量变化的方法
public void OnCharacterHealthChanged(int newHealth)
{
if (newHealth <= 0)
{
// 角色死亡时的处理逻辑
Console.WriteLine("Enemy AI: Character is dead, stop attacking!");
}
else
{
// 角色血量变化时的处理逻辑
Console.WriteLine("Enemy AI: Character health changed to " + newHealth);
}
}
}
// 使用示例
GameCharacter player = new GameCharacter();
EnemyAI enemyAI = new EnemyAI();
player.AddObserver(enemyAI); // 将怪物AI作为观察者添加到角色对象中
player.Health = 10; // 触发通知,输出 "Enemy AI: Character health changed to 10"
player.Health = 0; // 触发通知,输出 "Enemy AI: Character is dead, stop attacking!"
动态内存分配可能是一个问题,可以使用链式列表观察者,可以添加或者删除观察者而不会动态分配内存
为了接收一个通知而去实现整个接口并不符合现在的编程美学,一个更现代的方法是,对于每一个"观察者",它只有一个引用方法或者引用函数
C#的event
就是将观察者变成了一个代理
cs
using System;
public class GameCharacter
{
// 定义一个事件,事件的类型为EventHandler<int>
public event EventHandler<int> HealthChanged;
private int health;
public int Health
{
get { return health; }
set
{
health = value;
OnHealthChanged(health); // 在setter中调用事件触发方法
}
}
// 触发事件的方法
protected virtual void OnHealthChanged(int newHealth)
{
// 通过事件触发器调用事件,传递当前对象和新的血量值
HealthChanged?.Invoke(this, newHealth);
}
}
public class EnemyAI
{
// 订阅GameCharacter对象的HealthChanged事件
public void Subscribe(GameCharacter character)
{
character.HealthChanged += CharacterHealthChanged;
}
// 事件处理方法
public void CharacterHealthChanged(object sender, int newHealth)
{
Console.WriteLine("Enemy AI: Character health changed to " + newHealth);
}
}
// 使用示例
GameCharacter player = new GameCharacter();
EnemyAI enemyAI = new EnemyAI();
enemyAI.Subscribe(player); // 订阅事件
player.Health = 10; // 输出 "Enemy AI: Character health changed to 10"
unity示例:
cs
//触发事件的具体主题
public class Emitter : MonoBehaviour
{
public GameObject Ball;
void Start()
{
//First time wait 1 second for to the EmitBall method, then call at random intervals of 0.5-1.5 seconds
InvokeRepeating("EmitBall", 1f, Random.Range(0.5f, 1.5f));
}
void EmitBall()
{
GameObject go = Instantiate(Ball);
go.GetComponent<Rigidbody>().velocity = Vector3.up * 2f;
if (Radio.Instance.OnEmitEvent != null)//if Radio.Instance.OnEmitEvent is not null
{
Radio.Instance.OnEmitEvent(go.transform);
}
}
}
//提供了一个全局的事件分发机制
public class Radio : MonoBehaviour
{
public delegate void EmitHandler(Transform target);// Define a delegate type for the event
public EmitHandler OnEmitEvent;// Create a new event with the delegate type
public static Radio Instance;
void Awake()
{
Instance = this;
}
}
//观察者
public class Shooter : MonoBehaviour
{
public GameObject Bullet;
private void Start()
{
AddObserver();
}
private void AddObserver()
{
Radio.Instance.OnEmitEvent += Shoot;//subscribe to the event
}
private void Shoot(Transform target)
{
GameObject go = Instantiate(Bullet, transform.position, Quaternion.identity);
go.GetComponent<Rigidbody>().velocity = (target.position - transform.position).normalized * 30f;
}
}
ad-attention
title: 注意
一定要及时删除失效观察者,以及被观察者失效删除对应的观察者对象
观察者模式非常适合于一些不相关的模块之间的通信问题。它不适合于单个紧凑的模块内部的通信
原型模式
***将一个或多个对象当做原型,通过统一的生成器克隆出很多类似原型的对象,同时可以通过配置表更改克隆体属性,制造出很多具有自身个性的对象。
原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制现有的实例来创建新的实例,而不是通过新建。这种模式适用于创建复杂的对象,特别是当对象的创建成本很高时。原型模式允许系统在运行时动态地增加或减少对象的数量
***应用场景
- 当需要创建的对象是复杂的,并且创建成本较高时。
- 当系统应该独立于其产品的创建、组合和表示时。
- 当需要通过指明现有对象来指定创建对象,并通过复制这些对象来创建新的对象时。
***实现方式
- 原型(Prototype):定义一个用于创建克隆的接口或抽象类,通常包含一个克隆方法。
- 具体原型(Concrete Prototype):实现原型接口,提供克隆自身的具体实现。
- 客户端(Client):使用原型接口来创建新的对象实例。
***优点
- 减少创建成本:通过复制现有对象来创建新对象,可以减少创建复杂对象的成本。
- 增加灵活性:可以在运行时动态地增加或减少对象的数量。
- 支持多种复制方式:可以实现深复制或浅复制,具体取决于需求。
***缺点
- 实现复杂性:实现原型模式可能需要编写额外的克隆代码,特别是对于复杂的对象。
- 深复制和浅复制:需要明确区分深复制和浅复制,以避免潜在的问题
比如设置三种怪物,生成它们又需要三种怪物生成器类,这样的设计太多冗余
原型模式提供了一种解决方案。其核心思想是一个对象可以生成与自身相似的其他对象。如果你有一个幽灵,则你可以通过这个幽灵制作出更多的幽灵。如果你有一个魔鬼,那你就能制作出其他魔鬼。任何怪物都能被看作是一个原型,用这个原型就可以复制出更多不同版本的怪物
先设计一个基类Monster
,有一个抽象方法clone
,每一个子类都提供一份特定实现,该实现返回与自身类型和状态相同的对象
一旦所有的Monster
类都实现这些接口,就不再需要为每一个 Monster
类定义一个Spawner
类,只需定义一个类
*Monster Spawner
cs
using UnityEngine;
namespace PrototypePattern.MonsterSpawner
{
public class SpawnController : MonoBehaviour
{
// 每种类型的怪物都有一个对应的原型
private Ghost ghostPrototype;
private Demon demonPrototype;
private Sorcerer sorcererPrototype;
// monsterSpawners 数组保存了所有可用的 Spawner 实例
private Spawner[] monsterSpawners;
private void Start()
{
// 初始化每种怪物类型的原型。
ghostPrototype = new Ghost(15, 3);
demonPrototype = new Demon(11, 7);
sorcererPrototype = new Sorcerer(4, 11);
// 初始化 Spawner 数组,为每种怪物类型提供一个 Spawner monsterSpawners = new Spawner[] {
new(ghostPrototype),
new(demonPrototype),
new(sorcererPrototype),
};
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
var ghostSpawner = new Spawner(ghostPrototype);
var newGhost = ghostSpawner.SpawnMonster() as Ghost;
// 调用新鬼魂的 Talk 方法,输出一条消息到控制台
newGhost.Talk();
// 随机选择一个 Spawner 来生成一个随机类型的怪物
Spawner randomSpawner = monsterSpawners[Random.Range(0, monsterSpawners.Length)];
_Monster randomMonster = randomSpawner.SpawnMonster();
// 调用新生成的随机怪物的 Talk 方法。
randomMonster.Talk();
}
}
}
}
namespace PrototypePattern.MonsterSpawner
{
// 使用原型设计模式,允许从一个原型对象生成新的对象实例
public class Spawner
{
private _Monster prototype; // 保存要克隆的原型怪物
// 构造函数接收一个原型怪物作为参数并保存它。
public Spawner(_Monster prototype)
{
this.prototype = prototype;
}
// SpawnMonster 方法返回原型怪物的一个克隆。
public _Monster SpawnMonster()
{
return prototype.Clone();
}
}
}
namespace PrototypePattern.MonsterSpawner
{
public abstract class _Monster
{
// Clone 方法实现了原型设计模式,派生类应该重写此方法以返回自身的克隆。
public abstract _Monster Clone();
// Talk 方法是一个抽象方法,派生类应根据自己的特性实现不同的对话内容。
public abstract void Talk();
}
}
using UnityEngine;
namespace PrototypePattern.MonsterSpawner
{
public class Sorcerer : _Monster
{
private int health;
private int speed;
private static int sorcererCounter = 0;
public Sorcerer(int health, int speed)
{
this.health = health;
this.speed = speed;
sorcererCounter += 1;
}
public override _Monster Clone()
{
return new Sorcerer(health, speed);
}
public override void Talk()
{
Debug.Log($"Hello this is Sorcerer number {sorcererCounter}. My health is {health} and my speed is {speed}");
}
}
}
using UnityEngine;
namespace PrototypePattern.MonsterSpawner
{
public class Ghost : _Monster
{
private int health;
private int speed;
private static int ghostCounter = 0;
public Ghost(int health, int speed)
{
this.health = health;
this.speed = speed;
ghostCounter += 1;
}
public override _Monster Clone()
{
return new Ghost(health, speed);
}
public override void Talk()
{
Debug.Log($"Hello this is Ghost number {ghostCounter}. My health is {health} and my speed is {speed}");
}
}
}
using UnityEngine;
namespace PrototypePattern.MonsterSpawner
{
public class Demon : _Monster
{
private int health;
private int speed;
private static int demonCounter = 0;
public Demon(int health, int speed)
{
this.health = health;
this.speed = speed;
demonCounter += 1;
}
public override _Monster Clone()
{
return new Demon(health, speed);
}
public override void Talk()
{
Debug.Log($"Hello this is Demon number {demonCounter}. My health is {health} and my speed is {speed}");
}
}
}
*Dragon
Dragons.json:
json
{
"dragons": [
{
"name": "FatDragon",
"scale": {
"x": 4,
"y": 1,
"z": 1
}
},
{
"name": "TallDragon",
"scale": {
"x": 1,
"y": 1,
"z": 3
}
},
{
"name": "LongDragon",
"scale": {
"x": 1,
"y": 4,
"z": 1
}
},
{
"name": "Dragon",
"scale": {
"x": 1,
"y": 1,
"z": 1
}
},
{
"name": "LittleDragon",
"scale": {
"x": 0.5,
"y": 0.5,
"z": 0.5
}
},
{
"name": "HugeDragon",
"scale": {
"x": 3,
"y": 3,
"z": 3
}
}
]
}
DragonPrototype.cs:可以被克隆以创建新的龙对象
cs
using UnityEngine;
[System.Serializable]
public class DragonData
{
public string name;
public Vector3 scale;
}
public abstract class DragonPrototype : MonoBehaviour
{
protected string Name;
protected Vector3 Scale;
public virtual void SetData(DragonData data)
{
Name = data.name;
Scale = data.scale;
transform.localScale = Scale;
gameObject.name = Name;
}
public abstract DragonPrototype Clone();
}
Dragon.cs:继承自 DragonPrototype
并实现 Clone
方法
cs
using UnityEngine;
public class Dragon : DragonPrototype
{
void Start()
{
Invoke(nameof(DestroyThis), 10f);
}
private void DestroyThis()
{
Destroy(gameObject);
}
public override DragonPrototype Clone()
{
return Instantiate(this) as DragonPrototype;
}
void Update()
{
transform.Translate(Vector3.up * (-Time.deltaTime * 2));
}
}
Spawner.cs:进行对象克隆
cs
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class Spawner : MonoBehaviour
{
public DragonPrototype Prototype;
public float GapTime = 1;
private List<DragonData> dragons;
void Start()
{
LoadDragonsFromJson(Application.dataPath + @"\004PrototypePattern\Dragons.json");
InvokeRepeating(nameof(SpawnObj), 0, GapTime);
}
private void LoadDragonsFromJson(string path)
{
string json = File.ReadAllText(path);
var dragonsWrapper = JsonUtility.FromJson<DragonsWrapper>(json);
if (dragonsWrapper != null && dragonsWrapper.dragons != null && dragonsWrapper.dragons.Count > 0)
{
dragons = dragonsWrapper.dragons;
}
else
{
Debug.LogError("Dragons list is empty or null.");
}
}
void SpawnObj()
{
if (dragons == null || dragons.Count == 0) return;
int target = Random.Range(0, dragons.Count);
DragonData dragonData = dragons[target];
DragonPrototype newDragon = Prototype.Clone() as DragonPrototype;
newDragon.SetData(dragonData);
newDragon.transform.position = transform.position;
}
[System.Serializable]
public class DragonsWrapper
{
public List<DragonData> dragons;
}
}
单例模式
***使用单例(Singleton Pattern)意味着这个对象只有一个实例,这个实例是此对象自行构造的,并且可向全局提供
***应用场景
- 当需要确保某个类只有一个实例时。
- 当需要提供一个全局访问点来获取这个实例时。
- 当实例化的成本较高,且只需要一个实例时。
***实现方式
- 私有化构造函数 :确保不能通过
new
关键字创建类的实例。 - 提供一个私有静态变量:用于持有唯一的类实例。
- 提供一个公有静态方法:用于获取类实例。如果实例不存在,则创建一个实例并返回;如果实例已存在,则直接返回。
***优点
- 控制实例数量:确保类只有一个实例,避免资源浪费。
- 快速访问:提供一个全局访问点来获取实例,方便使用。任何其他类都可以通过访问单例,使用它的公开变量和方法
- 延迟初始化:实例化操作可以延迟到真正需要时才进行。
- 减少代码复用,让专门的类处理专门的事情------例如让TimeLog类来记录日志,而不是把StreamWriter的代码写到每一个类里
***缺点
- 滥用单例:因为实现简单,而且使用方便,所以有被滥用的趋势,导致系统设计不合理。
- 测试困难:单例模式可能导致单元测试困难,因为它引入了全局状态。
- 线程安全问题:每个线程都可以访问这个单例,会产生初始化、死锁等一系列问题,在多线程环境中,需要确保线程安全。
- 滥用单例会促进耦合的发生,因为单例是全局可访问的,如果不该访问者访问了单例,就会造成过耦合------例如如果播放器允许单例,那石头碰撞地面后就可以直接调用播放器来播放声音,这在程序世界并不合理,而且会破坏架构
- 如果很多很多类和对象调用了某个单例并做了一系列修改,那想理解具体发生了什么就困难了
书上一直强调不要使用单例,因为它是一个全局变量,全局变量促进了耦合,对并发也不友好,设置全局变量时,我们创建了一段内存,每个线程都能够访问和修改它,而不管它们是否知道其他线程正在操作它。这有可能导致死锁、条件竞争和其他一些难以修复的线程同步的Bug
TimeLogger.cs:实现单例实例
c#
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class TimeLogger : MonoBehaviour
{
public static TimeLogger Instance;
private StreamWriter mSW;// StreamWriter 用于写入文件
void Awake()
{
Instance = this;
LoggerInit(Application.dataPath + @"\005SingletonPattern\Log.txt");
}
void LoggerInit(string path)
{
if (mSW == null)
{
mSW = new StreamWriter(path);
}
}
public void WhiteLog(string info)
{
mSW.Write(DateTime.Now + ": " + info + "\n");
}
private void OnEnable()
{
LoggerInit(Application.dataPath + @"\005SingletonPattern\Log.txt");
}
private void OnDisable()
{
mSW.Close();
}
}
Speaker.cs:
c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Speaker : MonoBehaviour
{
public string SpeakerName;
public float GapTime = 1;
private int index = 1;
void Start()
{
InvokeRepeating("Speak", 0, GapTime);
}
void Speak()
{
string content = "I'm " + SpeakerName + ". (Index : " + index + ")";
Debug.Log(content);
TimeLogger.Instance.WhiteLog(content);
index++;
}
}
状态模式
***现在状态和条件决定对象的新状态,状态决定行为(Unity内AnimationController就是状态机)
状态模式(State Pattern)允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。状态模式将对象的行为封装在不同的状态对象中,将对象的状态从对象中分离出来,客户端无需关心对象的当前状态和状态的转换
***应用场景
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时。
- 当代码中存在大量条件语句,且这些条件语句依赖于对象的状态时。
- 如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话。
***实现方式
- 状态接口(State Interface):定义状态类所需的方法,这些方法描述了对象在该状态下应该执行的行为。
- 具体状态类(Concrete State Classes):实现状态接口,为对象在不同状态下定义具体的行为。
- 上下文类(Context):包含一个状态对象的引用,并在状态改变时更新其行为。
***优点
- 代码结构化:将状态相关的行为封装在具体的状态类中,使得状态转换逻辑清晰。
- 易于维护和扩展:每个状态只需要关心自己内部的实现,而不会影响到其他的,耦合降低。
- 增加新状态操作简单:可以将不同的状态隔离,每个状态都是一个单独的类,可以将各种状态的转换逻辑分布到状态的子类中,减少相互依赖。
***缺点
- 类数目增多:状态类数目较多,增加了系统的复杂性。
- 状态转换逻辑复杂:如果状态转换逻辑复杂,状态类之间的依赖关系可能会变得复杂。
- 结构与实现复杂:状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
LightSwitchCase:Light根据当前状态切换到下一个状态,并设置相应的灯光颜色
cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LightSwitchCase : MonoBehaviour {
public LightState CurrentState = LightState.Off;
private Light mLight;
private Material mMat;
private static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");
public enum LightState
{
Off,
Warm,
White,
WarmWhite
}
private void Awake()
{
mLight = GetComponent<Light>();
mMat = GetComponentInChildren<Renderer>().material;
}
public void OnChangeState()
{
//状态转换
switch (CurrentState)
{
case LightState.Off:
CurrentState = LightState.Warm;
break;
case LightState.Warm:
CurrentState = LightState.WarmWhite;
break;
case LightState.WarmWhite:
CurrentState = LightState.White;
break;
case LightState.White:
CurrentState = LightState.Off;
break;
default:
CurrentState = LightState.Off;
break;
}
//状态行为
switch (CurrentState)
{
case LightState.Off:
mLight.color = Color.black;
mMat.SetColor(EmissionColor, mLight.color);
break;
case LightState.Warm:
mLight.color = new Color(0.8f, 0.5f, 0);
mMat.SetColor(EmissionColor, mLight.color);
break;
case LightState.WarmWhite:
mLight.color = new Color(1, 0.85f, 0.6f);
mMat.SetColor(EmissionColor, mLight.color);
break;
case LightState.White:
mLight.color = new Color(0.8f, 0.8f, 0.8f);
mMat.SetColor(EmissionColor, mLight.color);
break;
default:
mLight.color = Color.black;
mMat.SetColor(EmissionColor, mLight.color);
break;
}
}
}
LightStateClass和TrafficState抽象类:
cs
using UnityEngine;
public class LightStateClass : MonoBehaviour
{
public GameObject GreenLightObj;
public GameObject YellowLightObj;
public GameObject RedLightObj;
//灯光材质
private Material GreenLight;
private Material YellowLight;
private Material RedLight;
//当前灯光状态
private TrafficState TrafficLight;
void Start()
{
GreenLight = GreenLightObj.GetComponent<Renderer>().material;
YellowLight = YellowLightObj.GetComponent<Renderer>().material;
RedLight = RedLightObj.GetComponent<Renderer>().material;
SetState(new Pass());
}
void Update()
{
TrafficLight.ContinuousStateBehaviour(this, GreenLight, YellowLight, RedLight);
}
public void SetState(TrafficState state)
{
TrafficLight = state;
TrafficLight.StateStart(GreenLight, YellowLight, RedLight);
}
}
public abstract class TrafficState
{
public float Duration;
public float Timer;
public static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");//控制材质自发光颜色属性ID
public virtual void StateStart(Material GreenLight, Material YellowLight, Material RedLight)
{
Timer = Time.time;//当前时间
}
public abstract void ContinuousStateBehaviour(LightStateClass mLSC, Material GreenLight, Material YellowLight, Material RedLight);
}
交通灯状态类:Pass,Passblink,Wait,Stop
cs
using UnityEngine;
public class Pass : TrafficState
{
public Pass()
{
Duration = 2;
}
public override void StateStart(Material GreenLight, Material YellowLight, Material RedLight)
{
base.StateStart(GreenLight, YellowLight, RedLight);
GreenLight.SetColor(EmissionColor, Color.green);
YellowLight.SetColor(EmissionColor, Color.black);
RedLight.SetColor(EmissionColor, Color.black);
}
public override void ContinuousStateBehaviour(LightStateClass mLSC, Material GreenLight, Material YellowLight, Material RedLight)
{
if (Time.time > Timer + Duration)
{
mLSC.SetState(new PassBlink());
}
}
}
using UnityEngine;
public class PassBlink : TrafficState
{
private bool On = true;
private float BlinkTimer = 0;
private float BlinkInterval = 0.2f;
public PassBlink()
{
Duration = 1;
}
public override void StateStart(Material GreenLight, Material YellowLight, Material RedLight)
{ base.StateStart(GreenLight, YellowLight, RedLight); }
private static void SwitchGreen(Material GreenLight, bool open)
{
Color color = open ? Color.green : Color.black;
GreenLight.SetColor(EmissionColor, color);
}
public override void ContinuousStateBehaviour(LightStateClass mLSC, Material GreenLight, Material YellowLight, Material RedLight)
{
if (Time.time > Timer + Duration)
{
mLSC.SetState(new Wait());
}
if (Time.time > BlinkTimer+BlinkInterval)
{
On = !On;
BlinkTimer = Time.time;
SwitchGreen(GreenLight,On);
}
}
}
using UnityEngine;
public class Wait : TrafficState
{
public Wait()
{
Duration = 1;
}
public override void StateStart(Material GreenLight, Material YellowLight, Material RedLight)
{
base.StateStart(GreenLight, YellowLight, RedLight);
GreenLight.SetColor(EmissionColor, Color.black);
YellowLight.SetColor(EmissionColor, Color.yellow);
RedLight.SetColor(EmissionColor, Color.black);
}
public override void ContinuousStateBehaviour(LightStateClass mLSC, Material GreenLight, Material YellowLight, Material RedLight)
{
if (Time.time > Timer + Duration)
{
mLSC.SetState(new Stop());
}
}
}
using UnityEngine;
public class Stop : TrafficState
{
public Stop()
{
Duration = 1;
}
public override void StateStart(Material GreenLight, Material YellowLight, Material RedLight)
{
base.StateStart(GreenLight, YellowLight, RedLight);
GreenLight.SetColor(EmissionColor, Color.black);
YellowLight.SetColor(EmissionColor, Color.black);
RedLight.SetColor(EmissionColor, Color.red);
}
public override void ContinuousStateBehaviour(LightStateClass mLSC, Material GreenLight, Material YellowLight, Material RedLight)
{
if (Time.time > Timer + Duration)
{
mLSC.SetState(new Pass());
}
}
}
双缓冲模式
***双缓冲模式(Double Buffering)是一种通过设置两个独立缓冲区来管理数据读写的技术。在双缓冲机制中,系统在读写缓冲区之间进行切换,使得生产者和消费者可以分别操作不同的缓冲区,避免了直接冲突
- ***当一个缓冲准备好后才会被使用------就像一个集装箱装满才会发货一样;当一个缓冲被使用时另一个处于准备状态,就形成了双缓冲
- ***在渲染中广泛使用,一帧准备好后才会被渲染到屏幕上------所以准备时间太长就会导致帧率下降
诸如计算机显示器的显示设备在每一时刻仅绘制一个像素.它会从左到右地扫描屏幕中每行中的像素,从上至下扫描屏幕上的每一行.当扫描至屏幕的右下角时,将重定位至屏幕的左上角并重复上面的行为
绘制所用的像素信息大多从帧缓冲区(framebuffer)中获知.帧缓冲区是RAM中存储像素的一个数组
***应用
- 图形渲染:用于防止显示图形时的闪烁延迟等不良体验。
- 音频处理和数据采集:减少刷新带来的显示闪烁或音频中断,提升系统的响应速度和稳定性。
- 多线程环境:通过分离读写操作,提升系统性能并减少数据竞争。
***数据结构
- 双缓冲区由两个缓冲区组成,分别称为A缓冲区和B缓冲区。在数据传输过程中,发送方先将数据存储在A缓冲区,接收方从B缓冲区中读取数据。当一个缓冲区的数据传输完成后,再通知发送方可以将下一批数据存储在另一个缓冲区中。
***优点
- 减少数据竞争:双缓冲通过将读写分离,确保了生产者和消费者操作不同的缓冲区,从而避免数据竞争。
- 提升性能:通过异步读写的方式减少了等待时间,有效提升了系统的吞吐量。
- 满足实时性需求:在实时系统中,数据的快速更新和显示尤为重要,双缓冲能够减少刷新带来的显示闪烁或音频中断
***示例1:任务调度系统
Schedule:实现任务的调度功能
cs
using System;
using System.Collections.Generic;
using UnityEngine;
public class Schedule : MonoBehaviour
{
// 双缓冲列表,用于防止在计时器回调期间直接修改计时器队列
[SerializeField] private List<Task> _Front; // _Front 用于收集新添加的任务
[SerializeField] private List<Task> _Back; // _Back 用于迭代和处理任务
// 收集需要被移除的 Schedule 对象,避免在迭代过程中直接修改 _Back 列表
[SerializeField] private List<Task> _Garbage;
private static Schedule Instance;
private void Awake()
{
if (Instance == null)
Instance = this;
DontDestroyOnLoad(this);
_Front = new List<Task>();
_Back = new List<Task>();
_Garbage = new List<Task>();
}
// 设置一个新的调度任务
public Task SetSchedule(string bindName, float time, Action<Task> callback, bool usingWorldTimeScale = false
, bool isLoop = false, bool isPause = false)
{
// 创建一个新的 Schedule 实例,并将其添加到 _Front 队列中
var sd = new Task(bindName, time, callback, usingWorldTimeScale, isLoop, isPause);
_Front.Add(sd);
Debug.Log("Schedule: " + _Front.Count + " tasks added.");
return sd;
}
public void SetSchedule(Task sd)
{
if (_Back.Contains(sd))
{
// 如果已经在 _Back 中,则不需要再次添加
return;
}
if (!_Front.Contains(sd))
{
// 如果不在 _Front 中,则添加回去
_Front.Add(sd);
}
}
public void RemoveSchedule(Task sd)
{
if (sd == null) return;
_Front.Remove(sd);
_Back.Remove(sd);
}
// 更新所有调度任务的状态
private void Update()
{
// 将 _Front 中的所有新任务转移到 _Back,然后清空 _Front if (_Front.Count > 0)
{
_Back.AddRange(_Front);
_Front.Clear();
}
float dt = Time.deltaTime; // 获取自上次调用以来的时间差
foreach (Task s in _Back)
{
// 检查任务是否已被取消,若取消则标记为垃圾并跳过
if (s.Canceled)
{
_Garbage.Add(s);
continue;
}
if (s.IsPause)
{
continue;
}
Task sd = s;
// 计算剩余时间,是否使用世界时间比例
float tmpTime = sd.Time;
var InGameTimeScale = 1.0f; // 假设游戏未暂停,时间比例为 1.0f,可以被加快或减慢
tmpTime -= sd.UsingWorldTimeScale ? dt * InGameTimeScale : dt;
// 更新 Schedule 的剩余时间
sd.ResetTime(tmpTime);
if (tmpTime > 0) continue;
// 如果时间已经到了或超过了触发点,则执行回调函数
s.Callback?.Invoke(s);
if (s.IsLoop)
{
s.ResetTime(s.OriginalTime); // 重置时间为原始时间
}
else
{
_Garbage.Add(s); // 标记为垃圾
}
}
// 清理已完成或被取消的任务
Debug.Log("Schedule: " + _Garbage.Count + " garbage tasks removed.");
foreach (Task s in _Garbage)
{
if (_Back.Contains(s))
{
_Back.Remove(s); // 从 _Back 移除垃圾任务
}
}
_Garbage.Clear(); // 清空垃圾列表
}
}
Task:一个序列化类,表示一个单独的任务
cs
using System;
[Serializable]
public class Task
{
public bool Canceled; // 是否取消任务
public float OriginalTime; // 存储原始时间
public float Time; // 任务持续时间
public bool UsingWorldTimeScale; // 是否使用世界时间比例
public bool IsLoop; // 是否循环
public bool IsPause; // 是否暂停
public string TaskName; // 任务名称
public Action<Task> Callback; // 任务完成时的回调函数
public Task(string taskTaskName, float time, Action<Task> callback, bool usingWorldTimeScale = false
, bool isLoop = false, bool isPause = false)
{
Time = time;
OriginalTime = time;
UsingWorldTimeScale = usingWorldTimeScale;
TaskName = taskTaskName;
IsLoop = isLoop;
IsPause = isPause;
Callback = callback;
}
public Task(float t, Action<Task> callback)
{
Time = t;
Callback = callback;
}
public void Cancel()
{
Canceled = true;
}
public void ResetTime(float t)
{
Time = t;
}
}
TaskPanel:Unity 编辑器中的自定义窗口,用来创建用户界面来管理任务,主要用于学习,实际的任务调度应由代码实现
cs
using System;
using UnityEngine;
using UnityEditor;
public class TaskPanel : EditorWindow
{
public string taskName = "New Task";
public float time = 1.0f;
public bool usingWorldTimeScale = false;
public bool isLoop = false;
public bool isPause = false;
public int selectedCallbackIndex = 0; // 用于存储选中的回调函数索引
private Schedule schedule;
private string[] callbackFunctionNames; // 存储回调函数名称的数组
[MenuItem("Tools/Task Panel")]
public static void ShowWindow()
{
GetWindow<TaskPanel>("Task Panel");
}
private void OnEnable()
{
// 初始化回调函数名称数组
callbackFunctionNames = new[] { "Callback1", "Callback2", "Callback3", };
}
private void OnGUI()
{
GUILayout.Label("Task Panel", EditorStyles.boldLabel);
taskName = EditorGUILayout.TextField("Task Name", taskName);
time = EditorGUILayout.FloatField("Time", time);
usingWorldTimeScale = EditorGUILayout.Toggle("Use World Time Scale", usingWorldTimeScale);
isLoop = EditorGUILayout.Toggle("Is Loop", isLoop);
isPause = EditorGUILayout.Toggle("Is Pause", isPause);
// 下拉菜单选择回调函数
selectedCallbackIndex = EditorGUILayout.Popup("Callback Function", selectedCallbackIndex, callbackFunctionNames);
if (GUILayout.Button("Create Task"))
{
CreateTask();
}
}
private void CreateTask()
{
Action<Task> callback = null;
if (selectedCallbackIndex >= 0 && selectedCallbackIndex < callbackFunctionNames.Length)
{
string callbackFunctionName = callbackFunctionNames[selectedCallbackIndex];
callback = (Action<Task>)Delegate.CreateDelegate(typeof(Action<Task>), this, callbackFunctionName);
}
// 这里假设 Schedule 是一个单例或者可以通过其他方式访问
schedule = FindObjectOfType<Schedule>();
if (schedule != null)
{
schedule.SetSchedule(taskName, time, callback, usingWorldTimeScale, isLoop, isPause);
}
else
{
Debug.LogError("Schedule object not found!");
}
}
// 示例回调函数
public void Callback1(Task task)
{
Debug.Log("Callback1: Task " + task.TaskName + " completed!");
}
public void Callback2(Task task)
{
Debug.Log("Callback2: Task " + task.TaskName + " completed!");
}
public void Callback3(Task task)
{
Debug.Log("Callback3: Task " + task.TaskName + " completed!");
}
}
游戏循环
1. FPS依赖于恒定游戏速度
cpp
const int FRAMES_PER_SECOND = 25; //恒定的帧数
const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND;
DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started
int sleep_time = 0;
bool game_is_running = true;
while( game_is_running ) {
update_game();
display_game();
next_game_tick += SKIP_TICKS;
sleep_time = next_game_tick - GetTickCount();
if( sleep_time >= 0 ) {
Sleep( sleep_time );
}
else {
// Shit, we are running behind!
}
}
两个重要变量来控制恒定帧数:
next_game_tick
:这一帧完成的时间点
sleep_time
:若大于0,则目前时间没到完成这一帧的时间点。启用Sleep等待时间点的到达;若小于0,则该帧的工作没完成。
恒定帧数的好处:防止整个游戏因为跳帧而引起画面的撕裂。性能较低的硬件会显得更慢;而性能高的硬件则浪费了硬件资源
2.游戏速度取决于可变FPS
cpp
DWORD prev_frame_tick;
DWORD curr_frame_tick = GetTickCount();
bool game_is_running = true;
while(game_is_running){
prev_frame_tick = curr_frame_tick;
curr_frame_tick = GetTickCount();
update_game(curr_frame_tick - prev_frame_tick);
display_game();
}
两个重要变量来控制恒定帧数:
prev_frame_tick
:上一帧完成的时间点
curr_frame_tick
:目前的时间点
curr_frame_tick
和prev_frame_tick
的差即为一帧所需的时间,根据这一个变量,每一帧的时间是不一样的。FPS也是可变的。
缓慢的硬件有时会导致某些点的某些延迟,游戏变得卡顿。
快速的硬件也会出现问题,帧数可变意味着在计算时不可避免地会有计算误差。
这种游戏循环一见钟情似乎很好,但不要被愚弄。慢速和快速硬件都可能导致游戏出现严重问题。此外,游戏更新功能的实现比使用固定帧速率时更难,为什么要使用它?
3. 具有最大FPS的恒定游戏速度
cpp
const int TICKS_PER_SECOND = 50;
const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
const int MAX_FRAMESKIP = 10;
DWORD next_game_tick = GetTickCount();
int loops;
bool game_is_running = true;
while( game_is_running ) {
loops = 0;
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
update_game();
next_game_tick += SKIP_TICKS;
loops++;
}
display_game();
}
MAX_FRAMESKIP
: 帧数缩小的最低倍数
最高帧数为50帧,当帧数过低时,渲染帧数将降低(display_game()),最低可至5帧(最高帧数缩小10倍),更新帧数保持不变(update_game())
在慢速硬件上,每秒帧数会下降,但游戏本身有望以正常速度运行。
游戏在快速硬件上没有问题,但是像第一个解决方案一样,你浪费了很多宝贵的时钟周期,可以用于更高的帧速率。在快速更新速率和能够在慢速硬件上运行之间找到平衡点至关重要。(最重要的原因是限制了帧数)
缺点与第一种方案相似
4.恒定游戏速度独立于可变FPS
cpp
const int TICKS_PER_SECOND = 25;
const int SKIP_TICKS = 1000 / TICKS_PER_SECOND;
const int MAX_FRAMESKIP = 5;
DWORD next_game_tick = GetTickCount();
int loops;
float interpolation;
bool game_is_running = true;
while( game_is_running ) {
loops = 0;
while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) {
update_game();
next_game_tick += SKIP_TICKS;
loops++;
}
interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick )
/ float( SKIP_TICKS );
display_game( interpolation );
}
渲染帧数是预测与插值实现的。 interpolation
计算等价的帧数
游戏循环比想象更多。有一个应该避免的,这就是变量FPS决定游戏速度的那个。恒定的帧速率对于移动设备来说可能是一个很好而简单的解决方案,但是当你想要获得硬件所拥有的一切时,最好使用FPS完全独立于游戏速度的游戏循环,使用高帧速率的预测功能。如果您不想使用预测功能,可以使用最大帧速率,但为慢速和快速硬件找到合适的游戏更新速率可能会非常棘手
迭代器模式
***迭代器模式(Iterator Pattern)是一种行为型设计模式,它提供了一种方法来顺序访问一个聚合对象中的各个元素,而不暴露其内部的表示
迭代器模式在unity游戏开发中应用非常多
.NET框架提供IEnumerable
和IEnumerator
接口
一个collection
要支持Foreach
进行遍历,就必须实现IEnumerable
,并以某种方式返回迭代器对象:IEnumerable
***应用场景
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
***数据结构
- 迭代器(Iterator) :定义访问和遍历元素的接口,通常包含
hasNext()
、next()
等方法。 - 具体迭代器(ConcreteIterator):实现迭代器接口,并跟踪当前位置,以便在聚合对象中遍历元素。
- 聚合(Aggregate) :定义创建相应迭代器对象的接口,通常包含一个返回迭代器实例的方法,如
iterator()
。 - 具体聚合(ConcreteAggregate):实现创建相应迭代器的接口,返回具体迭代器的一个实例。
***实现方式
- 迭代器模式通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现。其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。
***优点
- 封装性:迭代器模式将遍历集合的责任封装到一个单独的对象中,保护了集合的内部表示,提高了代码的封装性。
- 扩展性:迭代器模式允许在不修改聚合类代码的情况下,增加新的遍历方式。只需要实现一个新的迭代器类即可。
- 灵活性:迭代器模式提供了多种遍历方式,如顺序遍历、逆序遍历、多线程遍历等,可以根据实际需求选择适合的遍历方式
Unity的Transform使用了迭代器模式:
cs
public partial class Transform : Component, IEnumerable
{
// 实现IEnumerable接口的GetEnumerator方法,返回一个枚举器用于遍历Transform的子对象。
public IEnumerator GetEnumerator()
{
return new Transform.Enumerator(this);
}
// 内部类Enumerator实现了IEnumerator接口,用于遍历Transform的子对象。
private class Enumerator : IEnumerator
{
// 引用外部的Transform实例,表示要遍历的父Transform。
Transform outer;
// 当前枚举到的索引,-1表示尚未开始或已结束。
int currentIndex = -1;
// 构造函数接收一个Transform实例作为参数,并初始化outer字段。
internal Enumerator(Transform outer)
{
this.outer = outer;
}
// 返回当前索引位置的子Transform对象。
public object Current
{
get { return outer.GetChild(currentIndex); }
}
// 移动到下一个元素,并返回是否成功移动到一个新的有效元素。
public bool MoveNext()
{
// 获取子对象的数量,并检查当前索引是否在范围内。
int childCount = outer.childCount;
return ++currentIndex < childCount;
}
// 重置枚举器的状态,将currentIndex设置为-1,表示回到起始状态。
public void Reset()
{
currentIndex = -1;
}
}
}
实现迭代器模式:
cs
namespace Iterator
{
public interface IAggregate//IAggregate接口定义了创建相应迭代器的方法
{
void Add(object obj);
void Remove(object obj);
IIterator GetIterator();
}
}
cs
namespace Iterator
{
public interface IIterator//IIterator接口定义了遍历聚合对象所需的方法
{
object First();
object Next();
bool HasNext();
}
}
cs
namespace Iterator
{
public class ConcreteIterator : IIterator//ConcreteIterator 类实现了 IIterator 接口,提供了具体聚合对象的迭代逻辑
{
private List<object> _list = null;
private int _index = -1;
public ConcreteIterator(List<object> list)
{
_list = list;
}
public bool HasNext()
{
return _index < _list.Count - 1;
}
public object First()
{
_index = 0;
return _list[_index];
}
public object Next()
{
if (HasNext())
{
_index++;
return _list[_index];
}
return null;
}
}
}
cs
namespace Iterator
{
public class ConcreteAggregate : IAggregate//ConcreteAggregate 类实现了 IAggregate 接口,封装了一个具体的聚合对象
{
private List<object> _list = new List<object>();
public void Add(object obj)
{
_list.Add(obj);
}
public void Remove(object obj)
{
_list.Remove(obj);
}
public IIterator GetIterator()
{
return new ConcreteIterator(_list);
}
}
}
cs
namespace Iterator
{
public class IteratorPatternDemo
{
static void Main(string[] args)
{
IAggregate aggregate = new ConcreteAggregate();
aggregate.Add("中山大学");
aggregate.Add("华南理工");
aggregate.Add("韶关学院");
Console.WriteLine("聚合的内容有:");
IIterator iterator = aggregate.GetIterator();
while (iterator.HasNext())
{
object obj = iterator.Next();
Console.Write(obj.ToString() + "\t");
}
object firstObj = iterator.First();
Console.WriteLine("\nFirst:" + firstObj.ToString());
}
}
}
责任链模式
***避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接受者的对象连接成一条线,并且沿着这条链传递请求,知道有一个对象能够处理它为止
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过将多个处理器(处理对象)以链式结构连接起来,使得请求沿着这条链传递,直到有一个处理器处理该请求为止。这种模式的主要目的是解耦请求的发送者和接收者,使多个对象都有可能接收请求,而发送者不需要知道哪个对象会处理它
***应用场景
- 多个对象可能处理同一个请求:当请求的处理不是固定的,或者需要动态决定哪个对象处理请求时。
- 处理者对象集合需要动态确定:在运行时根据需要动态调整处理者顺序的场景。
- 增强系统的灵活性:通过责任链模式,可以灵活地增加或移除责任链中的处理者。
***实现方式
- Handler:定义一个处理请求的接口,包含一个设置下一个处理者的方法和一个处理请求的方法。
- ConcreteHandler:Handler的实现对象,可以处理请求或将请求传递给链中的下一个处理者。
- Client:创建处理者对象并设置它们之间的顺序。
***优点
- 降低耦合度:责任链模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者之间的耦合度降低。
- 灵活性和可扩展性:通过责任链模式,可以动态地组合处理对象,灵活地配置处理流程,这种解耦使得系统更加灵活和可扩展
cs
public abstract class AbstractSkillHandler
{
protected AbstractSkillHandler _nextSkill;
public AbstractSkillHandler(AbstractSkillHandler nextSkill)
{
_nextSkill = nextSkill;
}
public abstract bool CanHandle(int energy);
public virtual void Handle(int energy)
{
_nextSkill?.Handle(energy); //如果下一个技能存在,则调用下一个Handle方法
}
}
组件模式
***组件模式(Component Pattern),它将一个大型的对象拆分成多个小型的组件,每个组件都有独立的职责和行为,并可以互相组合以构建出复杂系统。这种模式允许开发者将游戏中的各个功能模块(如主角状态模块、背包模块、装备模块、技能模块及战斗模块)独立开发和维护,从而提高代码的可重用性和可维护性。
***应用场景
- 游戏开发和模拟中,其中游戏实体(如角色、物品)可以具有动态的能力或状态。
- 需要高模块化的系统以及实体可能需要在运行时更改行为而无需继承层次结构的系统。
- 当一个类越来越庞大,越来越难以开发时。
***数据结构
- 组件类:定义了组件的接口和行为。
- 组合类:将多个组件组合在一起,形成更复杂的对象。
***实现方式
- 组件模式通过为图形和声音创建单独的组件类来解决复杂系统的问题,允许进行灵活和独立的开发。这种模块化方法增强了可维护性和可扩展性。
***优点
- 灵活性和可复用性:组件可以在不同的实体中复用,使得添加新功能或修改现有功能更加容易。
- 解耦:减少了游戏实体状态和行为之间的依赖关系,便于进行更改和维护。
- 动态组合:实体可以通过添加或删除组件在运行时改变其行为,为游戏设计提供了极大的灵活性
cs
using System;
using UnityEngine;
namespace ComponentPattern
{
public class Player : MonoBehaviour
{
private JumpComponent jumpComponent;
private MoveComponent moveComponent;
private void Awake()
{
jumpComponent = gameObject.AddComponent<JumpComponent>();
moveComponent = gameObject.AddComponent<MoveComponent>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
jumpComponent.Jump();
if (Input.GetKey(KeyCode.A))
moveComponent.Move(false, 1);
if (Input.GetKey(KeyCode.D))
moveComponent.Move(true, 1);
}
}
}
using UnityEngine;
namespace ComponentPattern
{
public class MoveComponent : MonoBehaviour
{
public void Move(bool isRight, float speed)
{
float direction = isRight ? 1f : -1f;
transform.Translate(Vector3.right * (direction * speed * Time.deltaTime), Space.World);
}
}
}
using UnityEngine;
namespace ComponentPattern
{
public class JumpComponent : MonoBehaviour
{
public float JumpVelocity = 1f;
public Rigidbody rb;
private void Start()
{
rb = gameObject.GetComponent<Rigidbody>();
}
public void Jump()
{
rb.velocity = Vector3.up * JumpVelocity;
}
}
}
子类沙盒模式
***子类沙盒模式(SubClassSandbox)是一种设计思想,它通过在基类中定义一个抽象的沙盒方法和一些预定义的操作集合,来允许子类根据这些操作定义自己的行为。这些操作通常被设置为受保护的状态,以确保它们仅供子类使用
***应用场景
- 你有一个带有大量子类的基类。
- 基类能够提供所有子类可能需要执行的操作集合。
- 在子类之间有重叠的代码,你希望在它们之间更简便地共享代码。
- 你希望使这些继承类与程序其他代码之间的耦合最小化。
***实现方式
- 在子类沙盒模式中,基类提供了一系列受保护的方法,这些方法被子类用来实现特定的行为。子类通过重写基类中的抽象沙盒方法来组合这些操作,从而实现自己的功能。
***优点
- 减少耦合:通过将操作封装在基类中,子类之间的耦合被减少,因为每个子类仅与基类耦合。
- 代码复用:子类可以复用基类提供的操作,减少了代码冗余。
- 易于维护:当游戏系统的某部分改变时,修改基类即可,而不需要修改众多的子类。
***缺点
- 脆弱的基类问题:由于子类通过基类接触游戏的剩余部分,基类最后和子类需要的每个系统耦合。这种蛛网耦合让你很难在不破坏什么的情况下改变基类
cs
using UnityEngine;
namespace SubclassSandboxPattern
{
public class GameController : MonoBehaviour
{
private SkyLaunch skyLaunch;
private SpeedLaunch speedLaunch;
private void Start()
{
skyLaunch = new SkyLaunch();
speedLaunch = new SpeedLaunch();
}
private void Update() //可以结合状态模式来控制超级能力是否可以使用
{
if (Input.GetKeyDown(KeyCode.Space))
{
skyLaunch.Activate();
}
if (Input.GetKeyDown(KeyCode.S))
{
speedLaunch.Activate();
}
}
}
}
using UnityEngine;
namespace SubclassSandboxPattern
{
// Superpower 抽象类是所有超级能力的基础
public abstract class Superpower
{
// 子类必须实现这个方法,它表示激活超级能力时的行为
public abstract void Activate();
protected void Move(string where)
{
Debug.Log($"Moving towards {where}");
}
protected void PlaySound(string sound)
{
Debug.Log($"Playing sound {sound}");
}
protected void SpawnParticles(string particles)
{
Debug.Log($"Firing {particles}");
}
}
}
namespace SubclassSandboxPattern
{
public class SkyLaunch : Superpower
{
// 重写父类的 Activate 方法,定义当 SkyLaunch 被激活时的行为。
public override void Activate()
{
PlaySound("Launch Sky sound"); // 播放发射声音
SpawnParticles("Fly"); // 生成尘埃粒子效果
Move("sky"); // 移动到天空
}
}
}
namespace SubclassSandboxPattern
{
public class SpeedLaunch : Superpower
{
public override void Activate()
{
PlaySound("Launch Speed Launch");
SpawnParticles("dust");
Move("quickly");
}
}
}
类型对象模式
***类型对象模式(Type Object Pattern)允许通过创建一个带有表示对象"类型"的字段的类来创建灵活且可重复使用的对象。这种设计模式对于在需要提前定义未定义的类型或需要修改或添加类型的场景非常有价值。它将部分类型系统从继承中抽离出来,放到可配置数据中
***应用场景
- 当你需要定义不同"种"事物,但是语言自身的类型系统过于僵硬时。
- 当你不知道后面还需要什么类型,或者想不改变代码或者重新编译就能修改或添加新类型时。
***实现方式
- 类型对象模式通过定义类型对象类和有类型的对象类来实现。每个类型对象实例代表一种不同的逻辑类型。每种有类型的对象保存对描述它类型的类型对象的引用。实例相关的数据被存储在有类型对象的实例中,被同种类分享的数据或者行为存储在类型对象中。
***优点
- 运行时创建新的类型对象:允许在运行时动态创建新的类型对象。
- 避免子类膨胀:通过类型对象模式,可以避免子类的过度膨胀。
- 客户程序无需了解实例与类型的分离:客户程序不需要了解实例与类型的分离。
- 可以动态的更改类型:类型对象模式允许动态地更改类型
cs
using UnityEngine;
namespace TypeObjectController
{
public class TypeObjectController : MonoBehaviour
{
private void Start()
{
// 创建各种动物实例,并设置它们是否能飞。
Bird ostrich = new Bird("ostrich", canFly: false);
Bird pigeon = new Bird("pigeon", canFly: true);
Mammal rat = new Mammal("rat", canFly: false);
Mammal bat = new Mammal("bat", canFly: true);
Fish flyingFish = new Fish("flying fish", canFly: true);
Fish goldFish = new Fish("goldfish", canFly: false);
// 调用 Talk 方法来输出每种动物是否能飞的信息。
ostrich.Talk();
pigeon.Talk();
rat.Talk();
bat.Talk();
flyingFish.Talk();
goldFish.Talk();
}
}
}
namespace TypeObjectController
{
//定义了一个可以飞行的类型的契约,任何实现了此接口的类都需要提供 CanIFly() 方法的实现,该方法返回布尔值以表示该类型是否能够飞行
public interface IFlyingType
{
bool CanIFly();
}
}
namespace TypeObjectController
{
//实现了 IFlyingType 接口的具体类,分别代表了能飞和不能飞的类型
public class ICanFly : IFlyingType
{
public bool CanIFly()
{
return true;
}
}
public class ICantFly : IFlyingType
{
public bool CanIFly()
{
return false;
}
}
}
namespace TypeObjectController
{
public abstract class Animal
{
protected string name;
protected IFlyingType flyingType;
protected Animal(string name, bool canFly)
{
this.name = name;
flyingType = canFly ? new ICanFly() : new ICantFly();
}
public abstract void Talk();
}
}
using UnityEngine;
namespace TypeObjectController
{
public class Bird : Animal
{
public Bird(string name, bool canFly) : base(name, canFly) { }
public override void Talk()
{
string canFlyString = flyingType.CanIFly() ? "can" : "can't";
Debug.Log($"Hello this is {name}, I'm a bird, and I {canFlyString} fly!");
}
}
}
using UnityEngine;
namespace TypeObjectController
{
public class Mammal : Animal
{
public Mammal(string name, bool canFly) : base(name, canFly) { }
public override void Talk()
{
string canFlyString = flyingType.CanIFly() ? "can" : "can't";
Debug.Log($"Hello this is {name}, I'm a mammal, and I {canFlyString} fly!");
}
}
}
using UnityEngine;
namespace TypeObjectController
{
public class Fish : Animal
{
public Fish(string name, bool canFly) : base(name, canFly) { }
public override void Talk()
{
string canFlyString = flyingType.CanIFly() ? "can" : "can't";
Debug.Log($"Hello this is {name}, I'm a fish, and I {canFlyString} fly!");
}
}
}
命令队列模式
***命令队列模式(Command Queue Pattern)是用于管理和执行一系列命令。这种模式通常用于确保命令按特定顺序执行,并且一次只有一个命令在运行。它在游戏开发、事件处理系统和其他需要命令调度的场景中非常有用。
***应用场景
- 当需要按特定顺序执行多个命令时。
- 当需要确保一次只有一个命令在运行时。
- 当需要一个灵活的命令调度机制时。
***实现方式
- 命令队列模式通过定义命令接口(ICommand)、具体命令类、命令队列类(CommandQueue)和执行者(Invoker)来实现。每个具体命令类实现命令接口,包含执行命令的具体逻辑。命令队列类管理一个命令队列,确保命令按顺序执行。执行者负责将命令添加到队列中,并触发命令的执行。
***优点
- 确保命令顺序执行:命令队列模式确保命令按特定顺序执行。
- 避免并发执行问题:通过确保一次只有一个命令在运行,避免并发执行问题。
- 提高系统的灵活性和可扩展性:命令队列模式提供了一种灵活且可扩展的命令调度机制。
- 简化命令的管理和执行:通过集中管理命令的执行,简化了命令的管理和执行。
cs
using UnityEngine;
namespace DecouplingPatterns.CommandQueue
{
public class GameController : MonoBehaviour
{
public Popup firstPopUp, secondPopup, thirdPopup;
private CommandQueue _commandQueue;
private void Start()
{
// create a command queue
_commandQueue = new CommandQueue();
StartCommands();
}
public void StartCommands()
{
_commandQueue.Clear();
// add commands
_commandQueue.Enqueue(new FirstCmd(this));
_commandQueue.Enqueue(new SecondCmd(this));
_commandQueue.Enqueue(new ThirdCmd(this));
Debug.Log("Commands enqueued");
}
}
}
using System.Collections.Generic;
namespace DecouplingPatterns.CommandQueue
{
//该类管理一个命令队列,确保命令按顺序执行,并且一次只有一个命令正在运行
public class CommandQueue
{
private readonly Queue<ICommand> _queue;
public bool _isPending; // it's true when a command is running
public CommandQueue()
{
_queue = new Queue<ICommand>();
_isPending = false;
}
public void Enqueue(ICommand cmd)
{
_queue.Enqueue(cmd);
if (!_isPending) // if no command is running, start to execute commands
DoNext();
}
public void DoNext()
{
if (_queue.Count == 0)
return;
ICommand cmd = _queue.Dequeue();
// setting _isPending to true means this command is running
_isPending = true;
// listen to the OnFinished event
cmd.OnFinished += OnCmdFinished;
cmd.Execute();
}
private void OnCmdFinished()
{
// current command is finished
_isPending = false;
// run the next command
DoNext();
}
public void Clear()
{
_queue.Clear();
_isPending = false;
}
}
}
using System;
namespace DecouplingPatterns.CommandQueue
{
// The command interface defines the methods that all commands must implement
public interface ICommand
{
Action OnFinished { get; set; }
void Execute();
}
}
using System;
using UnityEngine;
namespace DecouplingPatterns.CommandQueue
{
//Represents a user interface component that can be displayed and closed
public class Popup : MonoBehaviour
{
public Action onClose;
public void Close()
{
onClose?.Invoke();
}
}
}
using System;
namespace DecouplingPatterns.CommandQueue
{
public class BaseCommand : ICommand
{
public Action OnFinished { get; set; }
protected readonly GameController _owner;
protected BaseCommand(GameController owner)
{
_owner = owner;
}
public virtual void Execute() { }
protected virtual void OnClose() { }
}
}
using UnityEngine;
namespace DecouplingPatterns.CommandQueue
{
public class FirstCmd : BaseCommand
{
public FirstCmd(GameController owner) : base(owner) { }
public override void Execute()
{
_owner.firstPopUp.gameObject.SetActive(true); //Activate the first pop-up window and subscribe to its closing event
_owner.firstPopUp.onClose += OnClose;
Debug.Log("Executing First Command");
}
protected override void OnClose()
{
_owner.firstPopUp.onClose -= OnClose; //Remove subscriptions to close events to prevent memory leaks
_owner.firstPopUp.gameObject.SetActive(false);
OnFinished?.Invoke();
Debug.Log("First Command Finished");
}
}
}
using UnityEngine;
namespace DecouplingPatterns.CommandQueue
{
public class SecondCmd : BaseCommand
{
public SecondCmd(GameController owner) : base(owner) { }
public override void Execute()
{
_owner.secondPopup.gameObject.SetActive(true);
_owner.secondPopup.onClose += OnClose;
Debug.Log("Second command executed");
}
protected override void OnClose()
{
_owner.secondPopup.onClose -= OnClose;
_owner.secondPopup.gameObject.SetActive(false);
OnFinished?.Invoke();
Debug.Log("Second command finished");
}
}
}
using UnityEngine;
namespace DecouplingPatterns.CommandQueue
{
public class ThirdCmd : BaseCommand
{
public ThirdCmd(GameController owner) : base(owner) { }
public override void Execute()
{
_owner.thirdPopup.gameObject.SetActive(true);
_owner.thirdPopup.onClose += OnClose;
Debug.Log("Third command executed");
}
protected override void OnClose()
{
_owner.thirdPopup.onClose -= OnClose;
_owner.thirdPopup.gameObject.SetActive(false);
OnFinished?.Invoke();
Debug.Log("Third command finished");
}
}
}
事件队列模式
***事件队列模式(Event Queue Pattern)是一种设计模式,用于管理和处理一系列事件。这种模式通常用于确保事件按特定顺序处理,并且可以异步地处理事件。它在图形用户界面(GUI)编程、游戏开发和其他需要事件驱动机制的场景中非常有用。
***应用场景
- 当需要按特定顺序处理多个事件时。
- 当需要异步处理事件,以避免阻塞主线程时。
- 当需要一个灵活的事件处理机制时。
***实现方式
- 事件队列模式通过定义事件接口、具体事件类、事件队列类(EventQueue)和事件处理器(EventHandler)来实现。每个具体事件类实现事件接口,包含事件的具体数据和处理逻辑。事件队列类管理一个事件队列,确保事件按顺序处理。事件处理器负责从队列中取出事件并处理。
***优点
- 确保事件顺序处理:事件队列模式确保事件按特定顺序处理。
- 支持异步事件处理:通过异步处理事件,避免阻塞主线程。
- 提高系统的灵活性和可扩展性:事件队列模式提供了一种灵活且可扩展的事件处理机制。
- 简化事件的管理和处理:通过集中管理事件的处理,简化了事件的管理和处理
cs
namespace DecouplingPatterns.EventQueue
{
using System.Collections.Generic;
using UnityEngine;
public class EventQueue : MonoBehaviour
{
public GameObject PosIndicator;
public Holder Holder;
private Queue<Transform> _destinationQueue;
private Transform _destination;
public float Speed = 10;
private void Start()
{
MouseInputManager.Instance.OnMouseClick += AddDestination;
_destinationQueue = new Queue<Transform>();
}
private void OnDestroy()
{
MouseInputManager.Instance.OnMouseClick -= AddDestination;
}
private void AddDestination()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); //将屏幕坐标转换为世界坐标射线
/*
*? 在三维空间中,射线是一个从某个起点开始并沿着某个方向无限延伸的线。Unity 中的Ray 结构体有两个主要属性:
*? origin:射线的起点,通常是一个三维坐标 (x, y, z) *? direction:射线的方向,也是一个三维向量,表示射线延伸的方向
*? GetPoint(float distance)方法接受一个浮点数参数,它指定了从射线起点出发的距离。该方法会计算并返回射线上距离起点distance单位长度的那个点的坐标
* GetPoint(10)返回的是距离射线起点(即相机位置)10 个单位远的点。这意味着无论在屏幕上哪里点击,都会得到一个与相机保持固定距离的新位置作为目的地
* 择 10 个单位作为一个固定值是为了确保生成的目的地不会太近或太远
* 如果距离设置得太小,可能会导致目标点出现在相机前非常靠近的地方;
* 而如果太大,则可能使目标点超出合理范围或难以控制。
*/ Vector3 destination = ray.GetPoint(10);
GameObject indicator = Instantiate(PosIndicator, destination, Quaternion.identity);
_destinationQueue.Enqueue(indicator.transform);
}
private void Update()
{
if (_destination == null && _destinationQueue.Count > 0)
{
_destination = _destinationQueue.Dequeue();
}
if (_destination == null) return;
Vector3 startPosition = Holder.transform.position;
Vector3 destinationPosition = _destination.position;
float distance = Vector3.Distance(startPosition, destinationPosition); //计算当前位置与目标位置之间的距离
//使用 Lerp 方法平滑地向目标位置移动
startPosition = Vector3.Lerp(startPosition, destinationPosition, Mathf.Clamp01((Time.deltaTime * Speed) / distance));
Holder.transform.position = startPosition;
Holder.ExecuteEvent(destinationPosition);
if (distance < 0.01f)
{
Destroy(_destination.gameObject);
_destination = null;
}
}
}
}
using UnityEngine;
namespace DecouplingPatterns.EventQueue
{
public interface IHolder
{
public void ExecuteEvent(Vector3 targetPos);
}
}
using UnityEngine;
namespace DecouplingPatterns.EventQueue
{
public class Holder : MonoBehaviour, IHolder
{
public virtual void ExecuteEvent(Vector3 targetPos) { }
}
}
using UnityEngine;
public class MouseInputManager : MonoBehaviour
{
public delegate void MouseInputeHandler();
public MouseInputeHandler OnMouseClick;
public static MouseInputManager Instance;
private void Awake()
{
Instance = this;
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
OnMouseClick();
}
}
}
using UnityEngine;
namespace DecouplingPatterns.EventQueue
{
public class Dragon : Holder
{
public override void ExecuteEvent(Vector3 targetPos)
{
Vector3 direction = targetPos - transform.position; // 计算方向向量
Quaternion targetRotation = Quaternion.LookRotation(direction); // 计算旋转角度
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 5f);
}
}
}
服务定位器模式
***服务定位器模式是一种设计模式,用于帮助应用程序查找和获取所需的服务对象。它提供了一种间接的方式来访问服务,将服务的具体创建和查找逻辑封装在一个中央定位器组件中。
***应用场景
- 当应用程序需要以一种灵活且可维护的方式访问多个服务时。
- 当需要解耦服务调用者和具体的服务实现时。
***实现方式
- 服务定位器模式通过定义服务定位器接口、服务定位器实现、服务接口和具体服务来实现。服务定位器管理服务的注册和查找,客户端通过服务定位器来访问所需的服务。
***优点
- 解耦服务访问:将服务访问逻辑与使用服务的业务逻辑分离。
- 集中管理:服务的访问点集中管理,便于维护和扩展。
- 灵活性:易于添加、修改或替换服务。
- 延迟实例化:服务实例可以在首次请求时进行实例化(Lazy Instantiation),从而节省资源。
***缺点
- 性能问题:服务定位器可能引入性能开销,特别是在每次服务请求都进行查找时。
- 隐藏依赖:服务定位器通过全局访问点提供服务,可能导致依赖关系不清晰,使得代码的可读性和可测试性下降。
***使用建议
- 考虑使用服务定位器模式时,应避免过度依赖服务定位器,因为它可能掩盖系统的依赖关系,使得调试和优化变得困难
cs
using UnityEngine;
namespace ServiceLocatorPatterns.AudioServiceLocator
{
public class GameController : MonoBehaviour
{
private void Start()
{
// 创建一个 ConsoleAudio 实例,并通过 Locator 注册该实例作为当前游戏的音频服务提供者。
// 如果希望禁用音频功能,可以传递 null 给 Provide 方法。
var consoleAudio = new ConsoleAudio();
Locator.Provide(consoleAudio);
//Locator.Provide(null); // 用于测试 NullAudio 的行为
}
private void Update()
{
// 从 Locator 获取当前的音频服务提供者。
Audio locatorAudio = Locator.GetAudio();
if (Input.GetKeyDown(KeyCode.P))
{
locatorAudio.PlaySound(23);
}
else if (Input.GetKeyDown(KeyCode.S))
{
locatorAudio.StopSound(23);
}
else if (Input.GetKeyDown(KeyCode.Space))
{
locatorAudio.StopAllSounds();
}
}
}
}
namespace ServiceLocatorPatterns.AudioServiceLocator
{
// 定义了一个抽象类 Audio,规定所有音频服务必须实现的方法
public abstract class Audio
{
public abstract void PlaySound(int soundID);
public abstract void StopSound(int soundID);
public abstract void StopAllSounds();
}
}
namespace ServiceLocatorPatterns.AudioServiceLocator
{
//作为服务定位器,负责持有当前活动的 Audio 服务实例
public class Locator
{
private static NullAudio nullService;
private static Audio service;
// 静态构造函数,确保在第一次访问 Locator 类之前初始化默认的服务实例
static Locator()
{
nullService = new NullAudio();
// 初始化时设置为 nullService,以防止忘记注册实际的 Audio 实现
service = nullService;
}
// 返回当前注册的音频服务实例
public static Audio GetAudio()
{
return service;
}
// 允许外部代码通过此方法注册或替换当前的音频服务实例
public static void Provide(Audio _service)
{
// 如果传入 null,则使用 nullService 来代替,这通常用于禁用音频功能
service = _service ?? nullService;
}
}
}
using UnityEngine;
namespace ServiceLocatorPatterns.AudioServiceLocator
{
public class ConsoleAudio : Audio
{
// 在控制台输出一条信息表示开始播放指定 ID 的声音。
public override void PlaySound(int soundID)
{
Debug.Log($"Sound {soundID} has started");
}
// 在控制台输出一条信息表示停止播放指定 ID 的声音。
public override void StopSound(int soundID)
{
Debug.Log($"Sound {soundID} has stopped");
}
// 在控制台输出一条信息表示停止所有正在播放的声音。
public override void StopAllSounds()
{
Debug.Log("All sounds have stopped");
}
}
}
using UnityEngine;
namespace ServiceLocatorPatterns.AudioServiceLocator
{
// NullAudio 类提供了不执行任何操作的音频服务实现。
// 这种设计允许系统在缺少有效音频服务时不会崩溃,而是可以优雅地忽略所有的音频请求。
public class NullAudio : Audio
{
public override void PlaySound(int soundID)
{
Debug.Log("Do nothing - PlaySound");
}
public override void StopSound(int soundID)
{
Debug.Log("Do nothing - StopSound");
}
public override void StopAllSounds()
{
Debug.Log("Do nothing - StopAllSounds");
}
}
}
***另一种实现:
cs
using System;
using System.Collections.Concurrent;
using UnityEngine;
namespace ServiceLocatorPatterns.AnotherImplementation
{
// 服务定位器类,使用静态上下文来存储已经注册的对象,并允许从任何地方解析(获取)这些对象。
public static class ServiceLocator
{
// 使用 ConcurrentDictionary 来存储已注册的服务实例,确保线程安全和高效查找。
private static readonly ConcurrentDictionary<Type, object> _registeredServices;
// 静态构造函数,在第一次使用该类的静态成员或创建该类的第一个实例之前自动执行,不需要显式地调用静态构造函数
static ServiceLocator()
{
_registeredServices = new ConcurrentDictionary<Type, object>();
}
// Register 方法用来向服务定位器中注册一个服务实例。
// T 是泛型类型参数,限定只能是 class 类型。
public static bool Register<T>(T service) where T : class
{
try
{
if (_registeredServices.ContainsKey(typeof(T)))
throw new Exception($"ServiceLocator: {typeof(T)} has already been registered."); // 已经注册过该类型,抛出异常
_registeredServices[typeof(T)] = service;
Debug.Log($"ServiceLocator: Registered {typeof(T)}");
return true;
} catch (Exception e)
{
Debug.LogError($"ServiceLocator: Failed to register {typeof(T)}: {e.Message}");
return false;
}
}
// Unregister 方法从服务定位器中注销一个具体的服务实例。
// 如果该实例不再需要或对象被销毁时调用。
public static bool Unregister<T>(T instance) where T : class
{
if (!_registeredServices.TryGetValue(typeof(T), out object obj)) return false;
if (!ReferenceEquals(obj, instance)) return false; //比较两个引用类型的对象是否指向同一个内存地址。对于引用类型来说,这意味着它们实际上是指向同一个对象实例
if (!_registeredServices.TryRemove(typeof(T), out _)) return false;
Debug.Log($"ServiceLocator: Unregistered {typeof(T)}");
return true;
}
// Resolve 方法尝试解析指定类型的服务实例。
// 如果找到则返回该实例,否则返回 null。
public static T Resolve<T>() where T : class
{
if (_registeredServices.TryGetValue(typeof(T), out object obj))
{
return (T)obj;
}
return null;
}
}
}
using UnityEngine;
namespace ServiceLocatorPatterns.AnotherImplementation
{
public class GameController : MonoBehaviour
{
private void Start()
{
var firstService = ServiceLocator.Resolve<FirstService>();
var secondService = ServiceLocator.Resolve<SecondService>();
var thirdService = ServiceLocator.Resolve<ThirdService>();
if (firstService != null)
{
firstService.SayHi();
}
if (secondService != null)
{
secondService.SimpleMethod();
}
if (thirdService != null)
{
thirdService.Foo();
}
ServiceLocator.Unregister(firstService);
ServiceLocator.Unregister(secondService);
ServiceLocator.Unregister(thirdService);
}
}
}
using UnityEngine;
namespace ServiceLocatorPatterns.AnotherImplementation
{
public class FirstService : MonoBehaviour
{
private void Awake()
{
ServiceLocator.Register(this);
//ServiceLocator.Register(this);//抛出异常,因为已经注册过了
}
private void OnDestroy()
{
ServiceLocator.Unregister(this);
}
public void SayHi()
{
Debug.Log("Hi, this is the " + nameof(FirstService));
}
}
}
using UnityEngine;
namespace ServiceLocatorPatterns.AnotherImplementation
{
public class SecondService : MonoBehaviour
{
private void Awake()
{
ServiceLocator.Register(this);
}
private void OnDestroy()
{
ServiceLocator.Unregister(this);
}
public void SimpleMethod()
{
Debug.Log("Hey, this is just a simple method from " + nameof(SecondService));
}
}
}
字节码模式
***应用场景
- 当需要定义很多行为并且游戏的实现语言不合适时,请使用字节码模式,因为:
- 它的等级太低,使得编程变得乏味或容易出错。
- 字节码模式涉及将行为编码为虚拟机的指令,这些指令通常是非常底层的操作。这种底层操作可能会导致编程变得繁琐且容易出错,因为开发者需要处理大量的细节和低级操作
- 由于编译时间慢或其他工具问题,迭代它需要很长时间。
- 在游戏开发中,使用像C++这样的重型语言时,代码的每次更新都需要编译。由于代码量庞大,编译时间可能会很长。字节码模式通过将可变内容从核心代码中剥离出来,减少每次更改后需要重新编译的次数,从而缓解这个问题
- 它有太多的信任。如果您想确保定义的行为不会破坏游戏,您需要将其与代码库的其余部分进行沙箱化。
- 字节码模式定义的行为可能对游戏的其他部分有太多的依赖。为了确保这些行为不会破坏游戏,需要将它们与代码库的其余部分隔离开来,即进行沙箱化处理。这样可以防止行为的执行对游戏的其他部分产生不良影响
- 它的等级太低,使得编程变得乏味或容易出错。
ad-note
沙箱化(Sandboxing)是一种计算机安全机制,主要用于隔离程序运行环境,以防止恶意代码或应用程序对系统和数据造成破坏
***实现方式
- 字节码模式通过定义一个虚拟机类来实现。虚拟机将指令作为输入并执行它们以提供游戏对象行为。它使用一个栈来管理操作数,并根据不同的指令执行不同的操作,如设置健康值、智慧值、敏捷值,播放声音和生成粒子等。
***优点
- 高密度和线性执行:字节码是连续的二进制数据块,不会浪费任何一个字节。线性执行程度高,除了控制流跳转,其他的指令都是顺序执行的。
- 底层执行:其执行指令是不可分割的,最简单的一个执行单元。
cs
using System.Collections.Generic;
using UnityEngine;
namespace BytecodePattern
{
public class Wizard
{
public int Health { get; set; } = 0;
public int Wisdom { get; set; } = 0;
public int Agility { get; set; } = 0;
}
public class GameController : MonoBehaviour
{
private Dictionary<int, Wizard> wizards = new();
private void Start()
{
wizards.Add(0, new Wizard());
wizards.Add(1, new Wizard());
var bytecode = new[] {
(int)Instruction.INST_LITERAL, 0, // 加载索引 0 到栈顶
(int)Instruction.INST_LITERAL, 75, // 加载常量值 75 到栈顶
(int)Instruction.INST_SET_HEALTH, // 设置健康值
(int)Instruction.INST_LITERAL, 1, // 加载索引 1 到栈顶
(int)Instruction.INST_LITERAL, 50, // 加载常量值 50 到栈顶
(int)Instruction.INST_SET_WISDOM, // 设置智慧值
(int)Instruction.INST_LITERAL, 1, // 加载索引 1 到栈顶
(int)Instruction.INST_LITERAL, 60, // 加载常量值 60 到栈顶
(int)Instruction.INST_SET_AGILITY, // 设置敏捷值
(int)Instruction.INST_LITERAL, 0, // 加载索引 0 到栈顶
(int)Instruction.INST_GET_HEALTH, // 获取健康值并推送到栈顶
(int)Instruction.INST_LITERAL, 5, // 加载常量值 5 到栈顶
(int)Instruction.INST_ADD, // 执行加法操作
};
var vm = new VM(this);
vm.Interpret(bytecode);
}
public void SetHealth(int wizardID, int amount)
{
wizards[wizardID].Health = amount;
Debug.Log($"Wizard {wizardID} sets health {amount}");
}
public void SetWisdom(int wizardID, int amount)
{
wizards[wizardID].Wisdom = amount;
Debug.Log($"Wizard {wizardID} sets wisdom {amount}");
}
public void SetAgility(int wizardID, int amount)
{
wizards[wizardID].Agility = amount;
Debug.Log($"Wizard {wizardID} sets agility {amount}");
}
public int GetHealth(int wizardID)
{
Debug.Log($"Wizard {wizardID} has health {wizards[wizardID].Health}");
return wizards[wizardID].Health;
}
public int GetWisdom(int wizardID)
{
Debug.Log($"Wizard {wizardID} has wisdom {wizards[wizardID].Wisdom}");
return wizards[wizardID].Wisdom;
}
public int GetAgility(int wizardID)
{
Debug.Log($"Wizard {wizardID} has agility {wizards[wizardID].Agility}");
return wizards[wizardID].Agility;
}
}
}
namespace BytecodePattern
{
// 编程语言中可以选择的指令
public enum Instruction
{
//Write stats
INST_SET_HEALTH,
INST_SET_WISDOM,
INST_SET_AGILITY,
//字面量
INST_LITERAL,
//Read stats
INST_GET_HEALTH,
INST_GET_WISDOM,
INST_GET_AGILITY,
//Arithmetic
INST_ADD,
INST_SUBTRACT,
INST_MULTIPLY,
INST_DIVIDE,
INST_MODULO,
}
}
using System.Collections.Generic;
using UnityEngine;
namespace BytecodePattern
{
public class VM
{
private GameController gameController;
private Stack<int> stackMachine = new(); //Will store values for later use in the switch statement
private const int MAX_STACK = 128; //The max size of the stack
public VM(GameController gameController)
{
this.gameController = gameController;
}
public void Interpret(int[] bytecode)
{
stackMachine.Clear();
// Read and execute the instructions
for (var i = 0; i < bytecode.Length; i++)
{
// Convert from int to enum
var instruction = (Instruction)bytecode[i];
switch (instruction)
{
case Instruction.INST_SET_HEALTH:
{
int amount = Pop();
int wizard = Pop();
gameController.SetHealth(wizard, amount);
break;
}
case Instruction.INST_SET_WISDOM:
{
int amount = Pop();
int wizard = Pop();
gameController.SetWisdom(wizard, amount);
break;
}
case Instruction.INST_SET_AGILITY:
{
int amount = Pop();
int wizard = Pop();
gameController.SetAgility(wizard, amount);
break;
}
case Instruction.INST_LITERAL:
{
Push(bytecode[++i]);
break;
}
case Instruction.INST_GET_HEALTH:
{
int wizard = Pop();
Push(gameController.GetHealth(wizard));
break;
}
case Instruction.INST_GET_WISDOM:
{
int wizard = Pop();
Push(gameController.GetWisdom(wizard));
break;
}
case Instruction.INST_GET_AGILITY:
{
int wizard = Pop();
Push(gameController.GetAgility(wizard));
break;
}
case Instruction.INST_ADD:
{
int b = Pop();
int a = Pop();
Push(a + b);
break;
}
case Instruction.INST_SUBTRACT:
{
int b = Pop();
int a = Pop();
Push(a - b);
break;
}
case Instruction.INST_MULTIPLY:
{
int b = Pop();
int a = Pop();
Push(a * b);
break;
}
case Instruction.INST_DIVIDE:
{
int b = Pop();
int a = Pop();
if (b != 0)
{
Push(a / b);
}
else
{
Debug.LogError("Division by zero error");
}
break;
}
case Instruction.INST_MODULO:
{
int b = Pop();
int a = Pop();
if (b != 0)
{
Push(a % b);
}
else
{
Debug.LogError("Modulo by zero error");
}
break;
}
default:
{
Debug.Log($"The VM couldn't find the instruction {instruction} :(");
break;
}
}
}
}
private int Pop()
{
if (stackMachine.Count == 0)
{
Debug.LogError("The stack is empty :(");
}
return stackMachine.Pop();
}
private void Push(int number)
{
//检查栈溢出,有人制作一个试图破坏你的游戏的模组时很有用
if (stackMachine.Count + 1 > MAX_STACK)
{
Debug.LogError("Stack overflow is not just a place where you copy and paste code!");
}
stackMachine.Push(number);
}
}
}
脏标志
***脏标志通过使用一个布尔变量(脏标记),表示自上次处理或更新某个对象或资源以来,该对象或资源是否已被修改。当对对象进行更改时,该标记设置为"脏"(true),当对象被处理或更新时,设置为"干净"(false)
***应用场景
- 计算和同步任务:脏标志模式主要应用在"计算"和"同步"两个任务中。适用于原始数据的变化速度远高于导出数据的使用速度,且增量更新十分困难的情况。
- 性能优化:只在性能问题足够大时,再使用这一模式增加代码的复杂度。脏标志在两种任务上应用:"计算"和"同步"。
- 避免不必要的工作:将工作推迟到必要时进行,以避免不必要的工作。
***注意事项
- 脏标记的设置和清除:每次状态的改变就必须设置标识,在项目中要统一修改数据的接口。得到之前的推导数据保存在内存中,这个模式需要内存中保存推导数据,以防在使用。以空间换时间。
- 避免卡顿:延期到需要结果的时候执行,但是当这个工作本身就很耗时,就会造成卡顿。
cs
using UnityEngine;
using UnityEngine.UI;
namespace DirtyFlag
{
public class UnsavedChangesController : MonoBehaviour
{
// 私有布尔变量,用作"脏"标志(dirty flag),用来表示自上次保存以来是否发生了更改。
private bool isSaved = true;
private readonly float speed = 5f;
public Button saveBtn;
public GameObject warningMessage;
private void Start()
{
saveBtn.onClick.AddListener(Save);
}
private void Update()
{
if (Input.GetKey(KeyCode.W))
{
transform.Translate(speed * Time.deltaTime * Vector3.up);
// 标记为已修改(未保存)
isSaved = false;
}
if (Input.GetKey(KeyCode.S))
{
transform.Translate(speed * Time.deltaTime * -Vector3.up);
// 标记为已修改(未保存)
isSaved = false;
}
if (Input.GetKey(KeyCode.A))
{
transform.Translate(speed * Time.deltaTime * -Vector3.right);
// 标记为已修改(未保存)
isSaved = false;
}
if (Input.GetKey(KeyCode.D))
{
transform.Translate(speed * Time.deltaTime * Vector3.right);
// 标记为已修改(未保存)
isSaved = false;
}
warningMessage.SetActive(!isSaved); // 如果isSaved为false,显示警告信息,提示用户有未保存的更改
}
private void Save()
{
Debug.Log("Game was saved");
isSaved = true;
}
}
}
对象池
***对象池(Object Pool Pattern)是一种创建型设计模式,它用于管理和复用一组预先创建的对象。这种模式的主要目的是为了提高性能和节省资源,尤其适用于创建对象成本较高,而对象使用频繁的场景。
***应用场景:
- 当应用程序需要频繁创建和销毁对象,且对象的创建成本较高时。
- 当需要限制资源的使用,如数据库连接或网络连接时。
- 在内存中数量受限的对象,如线程或数据库连接。
***实现方式:
- 对象池模式通过定义一个对象池管理器来实现,该管理器负责对象的创建、存储、分配和回收。
- 对象池管理器预先创建一定数量的对象,并在池中维护这些对象。
- 当需要对象时,可以从池中获取一个对象;使用完毕后,将对象归还到池中,而不是销毁。
***优点:
- 资源复用:减少对象创建和销毁的开销,尤其适用于创建成本高的对象。
- 性能优化:通过减少对象创建和垃圾回收的时间来提高应用性能。
- 管理便利性:集中管理对象的生命周期,可以灵活控制资源的使用。
[unity三种对象池实现(simple,optimized,unityNative)](file:///D:/StudyProject/unity/GameDesignPattern_U3D_Version/Assets/ObjectPool)
单独的对象池类实现:
Simple:
cs
using System.Collections.Generic;
using UnityEngine;
namespace ObjectPool.Simple
{
// 简单的对象池实现
public class BulletObjectPoolSimple : ObjectPoolBase
{
// 子弹预制件,用来实例化新的子弹
public MoveBullet bulletPrefab;
// 保存已池化的子弹对象
private readonly List<GameObject> bullets = new();
private void Start()
{
if (bulletPrefab == null)
{
Debug.LogError("需要子弹预制件的引用");
}
// 实例化新子弹并放入列表供以后使用
for (var i = 0; i < INITIAL_POOL_SIZE; i++)
{
GenerateBullet();
}
}
// 生成单个新子弹并将其放入列表
private void GenerateBullet()
{
GameObject newBullet = Instantiate(bulletPrefab.gameObject, transform);
newBullet.SetActive(false); // 停用子弹,准备放入池中
bullets.Add(newBullet); // 将子弹添加到池中
}
// 从池中获取一个子弹
public GameObject GetBullet()
{
// 尝试找到一个未激活的子弹
foreach (GameObject bullet in bullets)
{
if (!bullet.activeInHierarchy) // 检查子弹是否处于非活动状态
{
bullet.SetActive(true); // 激活子弹
return bullet; // 返回激活的子弹
}
}
// 如果没有可用的子弹并且池子还没有满,就创建一个新的子弹
if (bullets.Count < MAX_POOL_SIZE)
{
GenerateBullet();
// 新子弹是列表中的最后一个元素
GameObject lastBullet = bullets[^1];
lastBullet.SetActive(true);
return lastBullet;
}
// 如果池子满了或者找不到可用的子弹,返回null
return null;
}
}
}
Optimized:
cs
using System.Collections.Generic;
using UnityEngine;
namespace ObjectPool.Optimized
{
// 这个对象池实现更加复杂但性能更好
public class BulletObjectPoolOptimized : ObjectPoolBase
{
// 子弹预制件,用来实例化新的子弹
public MoveBulletOptimized bulletPrefab;
// 保存已池化的子弹
private readonly List<MoveBulletOptimized> bullets = new();
// 第一个可用的子弹,不用遍历列表查找
// 创建一个链表,所有未使用的子弹链接在一起
private MoveBulletOptimized firstAvailable;
private void Start()
{
if (bulletPrefab == null)
{
Debug.LogError("需要子弹预制件的引用");
}
// 实例化新子弹并放入列表供以后使用
for (var i = 0; i < INITIAL_POOL_SIZE; i++)
{
GenerateBullet();
}
// 构建链表
firstAvailable = bullets[0];
// 每个子弹指向下一个
for (var i = 0; i < bullets.Count - 1; i++)
{
bullets[i].next = bullets[i + 1];
}
// 最后一个子弹终止链表
bullets[^1].next = null;
}
// 生成单个新子弹并放入列表
private void GenerateBullet()
{
MoveBulletOptimized newBullet = Instantiate(bulletPrefab, transform);
newBullet.gameObject.SetActive(false);
bullets.Add(newBullet);
newBullet.objectPool = this; // 设置子弹的对象池引用
}
// 子弹被停用时添加到链表中
public void ConfigureDeactivatedBullet(MoveBulletOptimized deactivatedObj)
{
// 将其作为链表的第一个元素,避免检查第一个是否为null
deactivatedObj.next = firstAvailable;
firstAvailable = deactivatedObj;
}
// 获取一个子弹
public GameObject GetBullet()
{
// 不是遍历列表查找不活动对象,而是直接获取firstAvailable对象
if (firstAvailable == null)
{
// 如果没有更多子弹可用了,我们根据最大池大小决定是否实例化新的子弹
if (bullets.Count < MAX_POOL_SIZE)
{
GenerateBullet();
MoveBulletOptimized lastBullet = bullets[^1];
ConfigureDeactivatedBullet(lastBullet);
}
else
{
return null;
}
}
// 从链表中移除
MoveBulletOptimized newBullet = firstAvailable;
firstAvailable = newBullet.next;
GameObject newBulletGO = newBullet.gameObject;
newBulletGO.SetActive(true);
return newBulletGO;
}
}
}
namespace ObjectPool.Optimized
{
// 继承自 BulletBase 类,用于优化子弹移动逻辑
public class MoveBulletOptimized : BulletBase
{
// 用于优化对象池性能,创建一个链表结构
[System.NonSerialized] public MoveBulletOptimized next;
// 对象池引用,以便在子弹停用时通知对象池
[System.NonSerialized] public BulletObjectPoolOptimized objectPool;
private void Update()
{
MoveBullet(); // 调用基类方法移动子弹
// 如果子弹超出有效距离,则停用它
if (IsBulletDead())
{
// 告诉对象池此子弹已被停用
objectPool.ConfigureDeactivatedBullet(this);
gameObject.SetActive(false); // 停用子弹的游戏对象
}
}
}
}
Unity自带:
cs
using UnityEngine;
using UnityEngine.Pool;
namespace ObjectPool.UnityNative
{
// 使用Unity的原生对象池系统
public class BulletObjectPoolUnity : ObjectPoolBase
{
// 子弹预制件,用来实例化新的子弹
public MoveBulletUnity bulletPrefab;
// Unity的原生对象池
private ObjectPool<MoveBulletUnity> allBullets;
private void Start()
{
if (bulletPrefab == null)
{
Debug.LogError("需要子弹预制件的引用");
}
// 创建一个新的对象池
allBullets = new ObjectPool<MoveBulletUnity>(
CreatePooledItem, // 创建池中对象的回调函数
OnTakeFromPool, // 从池中取出对象时的回调函数
OnReturnedToPool, // 将对象返回到池中时的回调函数
OnDestroyPoolObject, // 当池达到容量并有对象被返回时销毁对象的回调函数
true, // 是否启用集合检查(防止重复释放)
INITIAL_POOL_SIZE, // 初始池大小
MAX_POOL_SIZE); // 最大池大小
}
private void Update()
{
// 调试信息:显示当前池状态
Debug.Log($"In pool: {allBullets.CountAll}, Active: {allBullets.CountActive}, Inactive: {allBullets.CountInactive}");
// 按下K键清空对象池
if (Input.GetKeyDown(KeyCode.K))
{
allBullets.Clear(); // 清空对象池而不是Dispose(),因为Dispose()会完全摧毁池子。
}
}
// 创建新项并添加到池中
private MoveBulletUnity CreatePooledItem()
{
GameObject newBullet = Instantiate(bulletPrefab.gameObject, transform);
var moveBulletScript = newBullet.GetComponent<MoveBulletUnity>();
moveBulletScript.objectPool = allBullets; // 设置子弹的对象池引用以便在子弹死亡时返回池中
return moveBulletScript;
}
// 当从池中获取对象时调用
private void OnTakeFromPool(MoveBulletUnity bullet)
{
bullet.gameObject.SetActive(true); // 激活子弹
}
// 当对象被返回到池中时调用
private void OnReturnedToPool(MoveBulletUnity bullet)
{
bullet.gameObject.SetActive(false); // 停用子弹
}
// 如果池容量已满,则任何返回的对象将被销毁
private void OnDestroyPoolObject(MoveBulletUnity bullet)
{
Debug.Log("销毁池化对象");
Destroy(bullet.gameObject); // 销毁子弹
}
// 从池中获取一个子弹
public GameObject GetBullet()
{
// 从池中获取一个实例。如果池为空,则创建一个新实例
return allBullets.Get().gameObject;
}
}
}
using UnityEngine.Pool;
namespace ObjectPool.UnityNative
{
public class MoveBulletUnity : BulletBase
{
// 对象池引用,用于停用子弹时将其返回池中
public IObjectPool<MoveBulletUnity> objectPool;
private void Update()
{
MoveBullet(); // 移动子弹
// 如果子弹超出有效距离,则停用它
if (IsBulletDead())
{
// 返回实例到池中
objectPool.Release(this);
}
}
}
}
空间分区模式
***空间分区模式(Spatial Partition Pattern)是一种优化模式,它通过将空间划分为不重叠的区域来有效地管理和查询空间数据。这种模式在计算机图形学、游戏开发和地理信息系统中被广泛使用,特别是用于优化诸如碰撞检测、射线投射和最近邻搜索等操作。
***应用场景
- 当需要高效地定位和访问空间中的对象时。
- 在游戏开发中,用于管理游戏对象,如敌人、子弹、障碍物等。
- 在计算机图形学中,用于快速确定哪些对象需要渲染。
***实现方式
- 固定网格:将空间划分为固定大小的网格,每个网格存储该区域内的对象。
- 四叉树:递归地将空间划分为四个象限,直到满足特定条件(如对象数量)。
- k-d树:在每个维度上递归地划分空间,适用于多维数据的快速检索。
- BSP树:类似于四叉树,但用于三维空间,常用于游戏开发中的可见性判断。
***优点
- 提高效率:通过减少需要检查的对象数量来提高查询效率。
- 简化查询:使得空间查询更加直观和易于实现。
- 优化性能:通过减少计算量来提升性能。
***缺点
- 内存使用:可能需要额外的内存来存储空间数据结构。
- 更新开销:当对象移动时,需要更新空间数据结构。
- 实现复杂性:对于动态变化的空间数据,实现可能较为复杂。
***使用建议
- 在对象数量较多且需要频繁进行空间查询的场景下使用。
- 根据应用场景选择合适的空间数据结构,如固定网格适合静态场景,四叉树适合动态变化的场景。
- 注意平衡内存使用和查询效率,避免过度优化导致资源浪费。
CS
using System.Collections.Generic;
using UnityEngine;
namespace SpatialPartition.Scripts
{
// 单位可以互相战斗,它们会改变颜色,并且颜色会保持一段时间以便观察效果
public class GameController : MonoBehaviour
{
public Unit unitPrefab;
public Transform unitParentTrans;
private Grid grid;
private const int NUMBER_OF_UNITS = 100; // 地图上移动的单位数量
private HashSet<Unit> allUnits = new(); // 用于跟踪所有单位以便移动它们
private Material gridMaterial;
private Mesh gridMesh;
private void Start()
{
grid = new Grid();
float battlefieldWidth = Grid.NUM_CELLS * Grid.CELL_SIZE;
for (var i = 0; i < NUMBER_OF_UNITS; i++)
{
float randomX = Random.Range(0f, battlefieldWidth);
float randomZ = Random.Range(0f, battlefieldWidth);
var randomPos = new Vector3(randomX, 0f, randomZ);
Unit newUnit = Instantiate(unitPrefab, unitParentTrans);
// 初始化单位,这会将其添加到网格中
newUnit.InitUnit(grid, randomPos);
// 将单位添加到所有单位的集合中
allUnits.Add(newUnit);
}
}
private void Update()
{
foreach (Unit unit in allUnits)
{
unit.Move(Time.deltaTime);
}
grid.HandleMelee();
}
private void LateUpdate()
{
// 显示网格
if (gridMaterial == null)
{
// 创建网格材质并设置颜色为黑色
gridMaterial = new Material(Shader.Find("Unlit/Color"));
gridMaterial.color = Color.black;
}
if (grid == null)
{
return;
}
if (gridMesh == null)
{
// 初始化网格网格对象
gridMesh = InitGridMesh();
}
// 绘制网格
Graphics.DrawMesh(gridMesh, Vector3.zero, Quaternion.identity, gridMaterial, 0, Camera.main, 0);
}
private Mesh InitGridMesh()
{
// 生成顶点
var lineVertices = new List<Vector3>();
float battlefieldWidth = Grid.NUM_CELLS * Grid.CELL_SIZE;
Vector3 linePosX = Vector3.zero;
Vector3 linePosY = Vector3.zero;
for (var x = 0; x <= Grid.NUM_CELLS; x++)
{
lineVertices.Add(linePosX);
lineVertices.Add(linePosX + (Vector3.right * battlefieldWidth));
lineVertices.Add(linePosY);
lineVertices.Add(linePosY + (Vector3.forward * battlefieldWidth));
linePosX += Vector3.forward * Grid.CELL_SIZE;
linePosY += Vector3.right * Grid.CELL_SIZE;
}
// 生成索引
var indices = new List<int>();
for (var i = 0; i < lineVertices.Count; i++)
{
indices.Add(i);
}
// 生成网格
var gridMesh = new Mesh();
gridMesh.SetVertices(lineVertices);
gridMesh.SetIndices(indices, MeshTopology.Lines, 0);
return gridMesh;
}
}
}
using UnityEngine;
namespace SpatialPartition.Scripts
{
// 网格类,同时处理战斗
public class Grid
{
public const int NUM_CELLS = 6;
public const int CELL_SIZE = 6;
private Unit[] cells;
// 网格上有多少单位,这比遍历所有单元格并计数更快
public int unitCount { get; private set; }
public Grid()
{
cells = new Unit[NUM_CELLS * NUM_CELLS];
for (var x = 0; x < NUM_CELLS; x++)
{
for (var y = 0; y < NUM_CELLS; y++)
{
cells[GetIndex(x, y)] = null;
}
}
}
private int GetIndex(int x, int y)
{
return (y * NUM_CELLS) + x;
}
// 添加单位到网格
// 当单位已经在网格中并移动到新单元格时也会使用此方法
public void Add(Unit newUnit, bool isNewUnit = false)
{
// 确定单位所在的网格单元格
Vector2Int cellPos = ConvertFromWorldToCell(newUnit.transform.position);
// 将单位添加到该单元格列表的前面
newUnit.prev = null;
newUnit.next = cells[GetIndex(cellPos.x, cellPos.y)];
// 将单元格与该单位关联
cells[GetIndex(cellPos.x, cellPos.y)] = newUnit;
// 如果该单元格中已经有单位,它应该指向新单位
if (newUnit.next != null)
{
newUnit.next.prev = newUnit;
}
if (isNewUnit)
{
unitCount += 1;
}
}
// 移动网格上的单位 = 查看它是否改变了单元格
// 确保 newPos 是网格内的有效位置
public void Move(Unit unit, Vector3 oldPos, Vector3 newPos)
{
// 查看它之前所在的单元格
Vector2Int oldCellPos = ConvertFromWorldToCell(oldPos);
// 查看它移动到的单元格
Vector2Int newCellPos = ConvertFromWorldToCell(newPos);
// 如果它没有改变单元格,我们已完成
if (oldCellPos.x == newCellPos.x && oldCellPos.y == newCellPos.y)
{
return;
}
// 从旧单元格的链表中取消链接
UnlinkUnit(unit);
// 如果该单位是该单元格链表的头,移除它
if (cells[GetIndex(oldCellPos.x, oldCellPos.y)] == unit)
{
cells[GetIndex(oldCellPos.x, oldCellPos.y)] = unit.next;
}
// 将其添加回网格中的新单元格
Add(unit);
}
// 从链表中取消链接单位
private void UnlinkUnit(Unit unit)
{
if (unit.prev != null)
{
// 前一个单位应该获得一个新的下一个单位
unit.prev.next = unit.next;
}
if (unit.next != null)
{
// 下一个单位应该获得一个新的前一个单位
unit.next.prev = unit.prev;
}
}
// 帮助方法,将 Vector3 转换为单元格位置
public Vector2Int ConvertFromWorldToCell(Vector3 pos)
{
// 将坐标除以单元格大小,从世界空间转换为单元格空间
// 转换为 int 类型,从单元格空间转换为单元格索引
int cellX = Mathf.FloorToInt(pos.x / CELL_SIZE);
int cellY = Mathf.FloorToInt(pos.z / CELL_SIZE); // z 而不是 y,因为 y 是 Unity 坐标系中的向上轴
var cellPos = new Vector2Int(cellX, cellY);
return cellPos;
}
// 测试位置是否为有效位置(= 是否在网格内)
public bool IsPosValid(Vector3 pos)
{
Vector2Int cellPos = ConvertFromWorldToCell(pos);
return cellPos.x is >= 0 and < NUM_CELLS && cellPos.y is >= 0 and < NUM_CELLS;
}
// 战斗处理
public void HandleMelee()
{
for (var x = 0; x < NUM_CELLS; x++)
{
for (var y = 0; y < NUM_CELLS; y++)
{
HandleCell(x, y);
}
}
}
// 处理单个单元格的战斗
private void HandleCell(int x, int y)
{
Unit unit = cells[GetIndex(x, y)];
// 让该单元格中的每个单位与其他单位战斗一次
while (unit != null)
{
// 尝试与该单元格中的其他单位战斗
HandleUnit(unit, unit.next);
// 我们还应该尝试与周围8个单元格中的单位战斗,因为它们可能也在攻击范围内
// 但我们不能检查所有8个单元格,因为这样有些单位可能会战斗两次,所以我们只检查一半(哪一半不重要)
// 我们还必须检查是否有周围的单元格,因为当前单元格可能是边界
// 这假设攻击距离小于单元格大小,否则我们可能需要检查更多单元格
if (x > 0 && y > 0)
{
HandleUnit(unit, cells[GetIndex(x - 1, y - 1)]);
}
if (x > 0)
{
HandleUnit(unit, cells[GetIndex(x - 1, y - 0)]);
}
if (y > 0)
{
HandleUnit(unit, cells[GetIndex(x - 0, y - 1)]);
}
if (x > 0 && y < NUM_CELLS - 1)
{
HandleUnit(unit, cells[GetIndex(x - 1, y + 1)]);
}
unit = unit.next;
}
}
// 处理单个单位与链表中的单位的战斗
private void HandleUnit(Unit unit, Unit other)
{
while (other != null)
{
// 如果它们的位置相似,则让它们战斗 - 使用平方距离因为它更快
if ((unit.transform.position - other.transform.position).sqrMagnitude < Unit.ATTACK_DISTANCE * Unit.ATTACK_DISTANCE)
{
HandleAttack(unit, other);
}
//更新 other 变量,使其指向链表中的下一个单位。这样在下一次循环迭代时,将检查当前单位与下一个单位之间的距离
other = other.next;
}
}
// 处理两个单位之间的攻击
private void HandleAttack(Unit one, Unit two)
{
// 插入战斗机制
one.StartFighting();
two.StartFighting();
}
}
}
using System.Collections;
using UnityEngine;
namespace SpatialPartition.Scripts
{
public class Unit : MonoBehaviour
{
public GameObject unitBody;
private Grid grid;
[System.NonSerialized] public Unit prev;
[System.NonSerialized] public Unit next;
private MeshRenderer meshRenderer;
private Color unitColor = Color.white;
private float unitSpeed;
public const float ATTACK_DISTANCE = 1.0f;
private void Awake()
{
meshRenderer = unitBody.GetComponent<MeshRenderer>();
if (meshRenderer == null)
{
Debug.LogError("MeshRenderer not found on unit body.");
}
unitSpeed = Random.Range(1f, 5f);
meshRenderer.material.color = unitColor;
}
public void InitUnit(Grid grid, Vector3 startPos)
{
this.grid = grid ?? throw new System.ArgumentNullException(nameof(grid), "Grid cannot be null when initializing a unit.");
transform.position = startPos;
prev = null;
next = null;
grid.Add(this, true);
transform.rotation = GetRandomDirection();
}
public void Move(float dt)
{
Vector3 oldPos = transform.position;
Vector3 newPos = oldPos + (transform.forward * (unitSpeed * dt));
bool isValid = grid != null && grid.IsPosValid(newPos); // 新位置是否是网格内的有效位置
if (isValid)
{
transform.position = newPos;
grid.Move(this, oldPos, newPos); // 更新网格,因为单位可能已经改变了单元格
}
else
{
// 尝试找到一个新的有效方向
for (var i = 0; i < 10; i++) // 尝试最多10次
{
transform.rotation = GetRandomDirection();
newPos = oldPos + (transform.forward * (unitSpeed * dt));
if (grid.IsPosValid(newPos))
{
transform.position = newPos;
grid.Move(this, oldPos, newPos);
break;
}
}
}
}
private Quaternion GetRandomDirection()
{
return Quaternion.Euler(new Vector3(0f, Random.Range(0f, 360f), 0f));
}
public void StartFighting()
{
StopAllCoroutines(); // 停止所有 FightCooldown 协程
StartCoroutine(FightCooldown());
}
private IEnumerator FightCooldown()
{
meshRenderer.sharedMaterial.color = Color.red;
yield return new WaitForSeconds(0.2f);
meshRenderer.sharedMaterial.color = unitColor;
}
}
}
装饰模式
***装饰模式(Decorator Pattern)允许在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。它通过创建一个包装对象(即装饰器)来包裹真实的对象,从而在运行时根据需要动态地添加或撤销功能。
***应用场景
- 动态扩展功能:当需要在运行时决定是否给对象添加额外的行为时,装饰模式非常适合。例如,在系统中根据用户的权限级别动态地赋予不同的访问权限。
- 灵活组合对象行为:装饰模式允许通过组合不同的装饰器来给对象添加多种行为。这使得可以根据需要构造出各种各样的行为组合,满足不同的业务需求。
- 避免类爆炸:当使用继承来扩展对象功能时,可能会导致类数量的急剧增加(即类爆炸)。而装饰模式提供了一种替代方案,通过组合而不是继承来扩展功能,从而简化了类结构。
***优点
- 动态扩展:可以在运行时动态地添加或撤销对象的职责,而不需要修改对象的代码。
- 灵活性:可以自由地组合和排列不同的装饰器,以创建出各种定制化的对象。
- 简化类结构:通过使用装饰模式,可以避免为了支持各种组合而创建大量的子类。
***缺点
- 装饰模式增加了许多小类,如果过度使用会使程序变得很复杂。
- 不易调试:由于装饰器模式涉及到多个对象的交互,调试可能会变得相对困难。特别是当装饰器链很长时,追踪请求和响应的路径可能会变得复杂
cs
using DecoratorPattern.Extras;
using UnityEngine;
namespace DecoratorPattern
{
// 装饰者模式控制器,管理订单逻辑。
public class OrderSystemController : MonoBehaviour
{
private void Start()
{
// Order 1: 为 Roadster 添加 Draco 推进器
_Car roadster = new Roadster(); // 创建基础 Roadster 实例
roadster = new DracoThruster(roadster, 5); // 为 Roadster 添加 Draco 推进器
Debug.Log($"You ordered: {roadster.GetDescription()} and have to pay ${roadster.Cost()}");
// Order 2: 为 Cybertruck 添加 Draco 推进器和弹射座椅
_Car cybertruck = new Cybertruck(); // 创建基础 Cybertruck 实例
cybertruck = new DracoThruster(cybertruck, 2); // 为 Cybertruck 添加 Draco 推进器
cybertruck = new EjectionSeat(cybertruck, 1); // 再为 Cybertruck 添加弹射座椅
Debug.Log($"You ordered: {cybertruck.GetDescription()} and have to pay ${cybertruck.Cost()}");
// Order 3: 订购 Model S 没有添加任何额外配件
_Car modelS = new ModelS(); // 创建基础 Model S 实例
Debug.Log($"You ordered: {modelS.GetDescription()} and have to pay ${modelS.Cost()}");
// 模拟一段时间后,某些价格发生了变化。
PriceList.dracoThruster -= 20;
PriceList.cybertruck -= 100f;
PriceList.modelS += 30f;
Debug.Log("Price changes!");
// 打印出更新后的订单价格
Debug.Log($"You ordered: {roadster.GetDescription()} and have to pay ${roadster.Cost()}");
Debug.Log($"You ordered: {cybertruck.GetDescription()} and have to pay ${cybertruck.Cost()}");
Debug.Log($"You ordered: {modelS.GetDescription()} and have to pay ${modelS.Cost()}");
}
}
}
namespace DecoratorPattern
{
// 价格列表类用于存储车辆基础价格和额外配件的价格。
public static class PriceList
{
// 基础车型价格
public static float cybertruck = 150f; // Cybertruck 的基础价格
public static float modelS = 200f; // Model S 的基础价格
public static float roadster = 350f; // Roadster 的基础价格
// 额外配件价格
public static float dracoThruster = 20f; // Draco 推进器的价格
public static float ejectionSeat = 200f; // 弹射座椅的价格
}
}
namespace DecoratorPattern
{
// 抽象类 _Car 是所有具体汽车类的基础,定义了获取描述和计算成本的方法。
public abstract class _Car
{
protected string description; // 存储汽车的描述信息
// 获取汽车的描述信息,由子类实现。
public abstract string GetDescription();
// 计算汽车的成本,由子类实现。
public abstract float Cost();
}
}
namespace DecoratorPattern
{
public class Roadster : _Car
{
public Roadster()
{
description = "Roadster";
}
public override string GetDescription()
{
return description;
}
public override float Cost()
{
return PriceList.roadster;
}
}
}
namespace DecoratorPattern
{
public class Cybertruck : _Car
{
public Cybertruck()
{
description = "Cybertruck";
}
public override string GetDescription()
{
return description;
}
public override float Cost()
{
return PriceList.cybertruck;
}
}
}
namespace DecoratorPattern
{
public class ModelS : _Car
{
public ModelS()
{
description = "Model S";
}
public override string GetDescription()
{
return description;
}
public override float Cost()
{
return PriceList.modelS;
}
}
}
namespace DecoratorPattern.Extras
{
// 抽象类 _CarExtras 用作所有汽车配件的基础类,它继承自 _Car 类
// 这个类允许我们创建具体的装饰器来为汽车对象动态地添加行为
public abstract class _CarExtras : _Car
{
protected int howMany; // 配件的数量
protected _Car prevCarPart; // 被装饰的汽车对象引用
// 构造函数接收一个要装饰的 _Car 对象以及配件的数量
public _CarExtras(_Car prevCarPart, int howMany)
{
this.prevCarPart = prevCarPart;
this.howMany = howMany;
}
}
}
namespace DecoratorPattern.Extras
{
// 具体的装饰器类 DracoThruster 继承自 _CarExtras 类,用来为汽车添加 Draco 推进器
public class DracoThruster : _CarExtras
{
public DracoThruster(_Car prevCarPart, int howMany) : base(prevCarPart, howMany) { }
public override string GetDescription()
{
return $"{prevCarPart.GetDescription()}, {howMany} Draco Thruster";
}
public override float Cost()
{
return (PriceList.dracoThruster * howMany) + prevCarPart.Cost();
}
}
}
namespace DecoratorPattern.Extras
{
// 具体的装饰器类 EjectionSeat 继承自 _CarExtras 类,用来为汽车添加弹射座椅。
public class EjectionSeat : _CarExtras
{
public EjectionSeat(_Car prevCarPart, int howMany) : base(prevCarPart, howMany) { }
public override string GetDescription()
{
return $"{prevCarPart.GetDescription()}, {howMany} Ejection Seat";
}
public override float Cost()
{
return (PriceList.ejectionSeat * howMany) + prevCarPart.Cost();
}
}
}
工厂模式
***工厂模式(Factory Pattern)是一种创建型设计模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。工厂模式可以分为以下几种:
-
简单工厂模式(Simple Factory Pattern)
简单工厂模式不是一个正式的设计模式,它只是通过一个工厂类来创建对象,但不涉及接口或抽象类。它通常包含一个工厂方法,根据输入参数返回不同的对象实例。
-
工厂方法模式(Factory Method Pattern)
工厂方法模式定义了一个创建对象的接口,但允许子类改变实例化的类型。工厂方法让类把实例化推迟到子类。
-
抽象工厂模式(Abstract Factory Pattern)
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而不需要指定它们的具体类。抽象工厂模式强调一系列相关对象的创建,而不是单个对象。
***应用场景
- 当一个类不知道它所必须创建的对象的类的时候。
- 当一个类希望其子类来指定它所创建的对象的时候。
- 当创建复杂对象时,需要保证对象的一致性或复用性时。
***优点
- 单一职责原则:工厂模式将对象的创建和使用分离,降低了系统的复杂度。
- 开闭原则:系统可以不修改现有代码的情况下引入新的产品,只要引入新的具体工厂即可。
- 灵活性和扩展性:工厂模式提高了系统的灵活性和扩展性,通过配置不同的工厂可以得到不同的产品。
*汽车工厂:使用了之前的装饰模式代码
cs
using System.Collections.Generic;
using DecoratorPattern;
using UnityEngine;
namespace FactoryPattern.CarFactory
{
// 展示工厂模式和装饰者模式的结合使用,用于创建不同配置的汽车对象。
public class CarFactoryController : MonoBehaviour
{
// Start 方法是 Unity 的生命周期方法之一,在场景加载并初始化后立即调用。
private void Start()
{
// 创建两个不同的工厂实例,分别代表美国工厂和中国工厂。
_CarFactory US_Factory = new USFactory(); // 美国工厂实例
_CarFactory China_Factory = new ChinaFactory(); // 中国工厂实例
// 使用美国工厂制造 Model S 型号的汽车,并添加 EjectionSeat 配件。
_Car order1 = US_Factory.ManufactureCar(new CarInfo(CarModels.ModelS, new List<CarExtras> { CarExtras.EjectionSeat, }, 1));
FinalizeOrder(order1); // 处理完成的订单
// 使用中国工厂制造 Cybertruck 型号的汽车,并添加 DracoThruster 配件。
_Car order2 = China_Factory.ManufactureCar(
new CarInfo(CarModels.Cybertruck, new List<CarExtras> { CarExtras.DracoThruster, }, 1));
FinalizeOrder(order2); // 处理完成的订单
// 再次使用美国工厂制造 Roadster 型号的汽车,并添加多个DracoThruster配件
_Car order3 = US_Factory.ManufactureCar(new CarInfo(CarModels.Roadster, new List<CarExtras> { CarExtras.DracoThruster, }, 2));
FinalizeOrder(order3); // 处理完成的订单
}
// FinalizeOrder 方法用于处理已完成的订单,打印出汽车描述和价格信息
private void FinalizeOrder(_Car finishedCar)
{
Debug.Log(finishedCar == null // 如果成品车为空,则表示无法制造订单
? $"Sorry but we can't manufacture your order, please try again!" // 输出错误信息
: $"Your order: {finishedCar.GetDescription()} is ready for delivery as soon as you pay ${finishedCar.Cost()}"); // 输出汽车描述和需要支付的金额
}
}
public struct CarInfo
{
public CarModels Model;
public List<CarExtras> Extras;
public int ExtrasNumber;
public CarInfo(CarModels model, List<CarExtras> extras, int number)
{
Model = model;
Extras = extras;
ExtrasNumber = number;
}
}
// CarModels 枚举定义了可用的汽车型号
public enum CarModels
{
ModelS,
Roadster,
Cybertruck,
}
// CarExtras 枚举定义了可选的汽车配件
public enum CarExtras
{
EjectionSeat, // 弹射座椅
DracoThruster, // 推进器
}
}
using DecoratorPattern;
namespace FactoryPattern.CarFactory
{
// 抽象工厂类,定义了一个制造汽车的方法
public abstract class _CarFactory
{
// 这个方法是所谓的"工厂方法",它用于创建特定类型的汽车
public abstract _Car ManufactureCar(CarInfo carInfo);
}
}
using System.Collections.Generic;
using DecoratorPattern;
using DecoratorPattern.Extras;
using UnityEngine;
namespace FactoryPattern.CarFactory
{
public class ChinaFactory : _CarFactory
{
public override _Car ManufactureCar(CarInfo carInfo)
{
_Car car = null;
// 根据传入的型号参数创建相应的汽车实例。
if (carInfo.Model == CarModels.ModelS)
{
car = new ModelS();
}
else if (carInfo.Model == CarModels.Roadster)
{
car = new Roadster();
}
// 注意:Cybertruck 模型在这里没有被处理,所以无法由中国工厂生产!
if (car == null)
{
Debug.Log("Sorry but this factory can't manufacture this model :(");
return car;
}
// 为汽车添加额外配置项。
foreach (CarExtras carExtra in carInfo.Extras)
{
// 根据配置项类型创建相应的装饰器,并将其应用到汽车上。
if (carExtra == CarExtras.DracoThruster)
{
car = new DracoThruster(car, carInfo.ExtrasNumber);
}
else if (carExtra == CarExtras.EjectionSeat)
{
car = new EjectionSeat(car, carInfo.ExtrasNumber);
}
else
{
Debug.Log("Sorry but this factory can't add this car extra :(");
}
}
return car;
}
}
}
using System.Collections.Generic;
using DecoratorPattern;
using DecoratorPattern.Extras;
using UnityEngine;
namespace FactoryPattern.CarFactory
{
public class USFactory : _CarFactory
{
public override _Car ManufactureCar(CarInfo carInfo)
{
_Car car = null;
if (carInfo.Model == CarModels.Cybertruck)
{
car = new Cybertruck();
}
else if (carInfo.Model == CarModels.ModelS)
{
car = new ModelS();
}
else if (carInfo.Model == CarModels.Roadster)
{
car = new Roadster();
}
if (car == null)
{
Debug.Log("Sorry but this factory can't manufacture this model :(");
return car;
}
foreach (CarExtras carExtra in carInfo.Extras)
{
if (carExtra == CarExtras.DracoThruster)
{
car = new DracoThruster(car, carInfo.ExtrasNumber);
}
else if (carExtra == CarExtras.EjectionSeat)
{
Debug.Log("Sorry but this factory can't add this car extra :(");
}
else
{
Debug.Log("Sorry but this factory can't add this car extra :(");
}
}
return car;
}
}
}
*声音工厂:工厂方法模式
cs
using UnityEngine;
namespace FactoryPattern.SoundFactory
{
// SoundFactoryController 类是用于管理声音系统的控制器。
// 它使用工厂方法模式来创建不同类型的音效系统实例。
public class SoundFactoryController : MonoBehaviour
{
private void Start()
{
// 创建一个软件实现的声音系统,并播放 ID 为 1 的声音。
ISoundSystem soundSystemSoftware = SoundSystemFactory.CreateSoundSystem(SoundSystemFactory.SoundSystemType.SoundSoftware);
soundSystemSoftware.PlaySound(1);
// 创建一个硬件实现的声音系统,并播放 ID 为 2 的声音。
ISoundSystem soundSystemHardware = SoundSystemFactory.CreateSoundSystem(SoundSystemFactory.SoundSystemType.SoundHardware);
soundSystemHardware.PlaySound(2);
// 创建一个其他类型的声音系统,并播放 ID 为 3 的声音。
ISoundSystem soundSystemOther = SoundSystemFactory.CreateSoundSystem(SoundSystemFactory.SoundSystemType.SoundSomethingElse);
soundSystemOther.PlaySound(3);
// 停止播放所有之前启动的声音。
soundSystemSoftware.StopSound(1);
soundSystemHardware.StopSound(2);
soundSystemOther.StopSound(3);
}
}
}
namespace FactoryPattern.SoundFactory
{
// SoundSystemFactory 类负责创建具体的声音系统对象。
public class SoundSystemFactory
{
// SoundSystemType 枚举定义了可以创建的不同类型的声音系统。
public enum SoundSystemType
{
SoundSoftware, // 软件声音系统
SoundHardware, // 硬件声音系统
SoundSomethingElse // 其他类型的声音系统
}
// CreateSoundSystem 是一个静态方法,它根据提供的类型参数返回一个新的声音系统实例。
public static ISoundSystem CreateSoundSystem(SoundSystemType type)
{
ISoundSystem soundSystem = null; // 初始化为空
// 根据传入的类型参数选择并创建相应的声音系统实例。
switch (type)
{
case SoundSystemType.SoundSoftware:
soundSystem = new SoundSystemSoftware(); // 如果是软件声音系统,则创建该类的新实例
break;
case SoundSystemType.SoundHardware:
soundSystem = new SoundSystemHardware(); // 如果是硬件声音系统,则创建该类的新实例
break;
case SoundSystemType.SoundSomethingElse:
soundSystem = new SoundSystemOther(); // 如果是其他类型的声音系统,则创建该类的新实例
break;
default:
// 如果类型不匹配任何预定义的选项,则返回 null。
// 这种情况下,可能需要抛出异常或处理错误情况。
break;
}
return soundSystem; // 返回创建的声音系统实例
}
}
}
namespace FactoryPattern.SoundFactory
{
// 定义 ISoundSystem 接口,声明了所有具体声音系统必须实现的方法。
public interface ISoundSystem
{
bool PlaySound(int soundId);
bool StopSound(int soundId);
}
}
using UnityEngine;
namespace FactoryPattern.SoundFactory
{
public class SoundSystemSoftware : ISoundSystem
{
public bool PlaySound(int soundId)
{
Debug.Log($"Played the sound with id {soundId} on the software");
return true; // 假设总是成功播放声音。
}
public bool StopSound(int soundId)
{
Debug.Log($"Stopped the sound with id {soundId} on the software");
return true; // 假设总是成功停止播放声音。
}
}
}
using UnityEngine;
namespace FactoryPattern.SoundFactory
{
public class SoundSystemOther : ISoundSystem
{
public bool PlaySound(int soundId)
{
Debug.Log($"Played the sound with id {soundId} on some other system");
return true;
}
public bool StopSound(int soundId)
{
Debug.Log($"Stopped the sound with id {soundId} on some other system");
return true;
}
}
}
using UnityEngine;
namespace FactoryPattern.SoundFactory
{
public class SoundSystemHardware : ISoundSystem
{
public bool PlaySound(int soundId)
{
Debug.Log($"Played the sound with id {soundId} on the hardware");
return true;
}
public bool StopSound(int soundId)
{
Debug.Log($"Stopped the sound with id {soundId} on the hardware");
return true;
}
}
}
*UI工厂:抽象工厂模式
cs
using UnityEngine;
namespace FactoryPattern.UIFactory
{
public class UIFactoryController:MonoBehaviour
{
private IUIButton _button;
private IUITextField _textField;
private void Start()
{
// 选择你想要使用的工厂
IUIFactory factory = GetSelectedFactory();
// 使用选定的工厂创建UI元素
_button = factory.CreateButton();
_textField = factory.CreateTextField();
// 渲染UI元素
_button.Render();
_textField.Render();
}
private IUIFactory GetSelectedFactory()
{
// 这里可以根据某些条件选择不同的工厂
return new ModernIUIFactory(); // 或者 new RetroGuiFactory(); }
}
}
namespace FactoryPattern.UIFactory
{
public interface IUIButton
{
void Render();
}
public interface IUITextField
{
void Render();
}
}
using UnityEngine;
namespace FactoryPattern.UIFactory
{
public class ModernUIButton : MonoBehaviour, IUIButton
{
public void Render()
{
Debug.Log("Rendering a modern button.");
}
}
}
using UnityEngine;
namespace FactoryPattern.UIFactory
{
public class ModernUITextField : MonoBehaviour, IUITextField
{
public void Render()
{
Debug.Log("Rendering a modern text field.");
}
}
}
using UnityEngine;
namespace FactoryPattern.UIFactory
{
public class RetroUIButton : MonoBehaviour, IUIButton
{
public void Render()
{
Debug.Log("Rendering a retro button.");
}
}
}
using UnityEngine;
namespace FactoryPattern.UIFactory
{
public class RetroUITextField : MonoBehaviour, IUITextField
{
public void Render()
{
Debug.Log("Rendering a retro text field.");
}
}
}
namespace FactoryPattern.UIFactory
{
public interface IUIFactory
{
IUIButton CreateButton();
IUITextField CreateTextField();
}
}
using UnityEngine;
namespace FactoryPattern.UIFactory
{
public class ModernIUIFactory : IUIFactory
{
public IUIButton CreateButton()
{
return new GameObject("ModernButton").AddComponent<ModernUIButton>();
}
public IUITextField CreateTextField()
{
return new GameObject("ModernTextField").AddComponent<ModernUITextField>();
}
}
}
using UnityEngine;
namespace FactoryPattern.UIFactory
{
public class RetroIUIFactory : IUIFactory
{
public IUIButton CreateButton()
{
return new GameObject("RetroButton").AddComponent<RetroUIButton>();
}
public IUITextField CreateTextField()
{
return new GameObject("RetroTextField").AddComponent<RetroUITextField>();
}
}
}
外观模式
***外观模式(Facade Pattern)是一种结构型设计模式,其核心思想是为子系统中的一组接口提供一个一致的界面。这种模式通过引入一个外观类来简化客户端与子系统之间的交互,从而隐藏系统的复杂性。
***定义
外观模式定义了一个高层接口,使得子系统更容易使用。它允许客户端通过一个统一的入口点来与系统进行通信,而不需要了解系统内部的具体细节和复杂性。
***适用场景
- 当需要简化复杂系统的使用和理解时,外观模式是一个强大的工具。它可以将系统的复杂性隐藏在背后,提供一个简洁的接口给客户端。
- 当一个系统由多个子系统组成,并且客户端需要通过不同的接口与这些子系统进行交互时,外观模式可以将这些交互整合在一个统一的接口中,方便客户端的使用。
***优点
- 简化了调用过程,无需了解深入子系统,防止带来风险。
- 减少系统依赖、松散耦合。
- 更好的划分访问层次。
- 符合迪米特法则,即最少知道原则。
***缺点
- 增加子系统、扩展子系统行为容易引入风险。
- 不符合开闭原则。
***实现方式
- 创建外观类:定义一个外观类,作为客户端与子系统之间的中介
- 封装子系统操作:外观类将复杂的子系统操作封装成简单的方法
cs
using UnityEngine;
namespace FacadePattern
{
//通过 RandomNumberFacade 类来获取随机数,而不需要直接与具体的随机数生成器交互
public class RandomNumbersController : MonoBehaviour
{
private void Start()
{
// 初始化随机数生成器的种子
RandomNumberFacade.InitSeed(0);
Debug.Log("Float: 0 -> 1");
// 打印5个0到1之间的随机浮点数
for (var i = 0; i < 5; i++)
{
Debug.Log(RandomNumberFacade.GetRandom01());
}
Debug.Log("Float: -1 -> 2");
// 打印10个-1到2之间的随机浮点数
for (var i = 0; i < 10; i++)
{
Debug.Log(RandomNumberFacade.GetRandom(-1f, 2f));
}
Debug.Log("Integer: -10 -> 20");
// 打印10个-10到20之间的随机整数
for (var i = 0; i < 10; i++)
{
Debug.Log(RandomNumberFacade.GetRandom(-10, 21));
}
}
}
}
namespace FacadePattern
{
//外观角色,它为客户端提供了一个简化的接口来生成随机数
public class RandomNumberFacade
{
private static IRandomNumberGenerator rng;
static RandomNumberFacade()
{
//可以选择不同的随机数生成器
rng = new RandomNumbersUnity();
//rng = new RandomNumbersSystem();
}
// 初始化随机数生成器的种子
public static void InitSeed(int seed)
{
rng.InitSeed(seed);
}
// 获取0到1之间的随机浮点数
public static float GetRandom01()
{
return rng.GetRandom01();
}
// 获取指定范围[min, max]内的随机浮点数
public static float GetRandom(float min, float max)
{
return rng.GetRandom(min, max);
}
// 获取指定范围[min, max)内的随机整数
public static int GetRandom(int min, int max)
{
return rng.GetRandom(min, max);
}
}
}
namespace FacadePattern
{
public interface IRandomNumberGenerator
{
// 初始化随机数生成器的种子
void InitSeed(int seed);
// 获取0到1之间的随机浮点数
float GetRandom01();
// 获取指定范围[min, max]内的随机浮点数
float GetRandom(float min, float max);
// 获取指定范围[min, max)内的随机整数
int GetRandom(int min, int max);
}
}
using UnityEngine;
namespace FacadePattern
{
public class RandomNumbersUnity : IRandomNumberGenerator
{
public void InitSeed(int seed)
{
Random.InitState(seed);
}
public float GetRandom01()
{
return Random.Range(0f, 1f);
}
public float GetRandom(float min, float max)
{
return Random.Range(min, max);
}
public int GetRandom(int min, int max)
{
return Random.Range(min, max);
}
}
}
namespace FacadePattern
{
public class RandomNumbersNative : IRandomNumberGenerator
{
private System.Random rng = new();
public void InitSeed(int seed)
{
rng = new System.Random(seed);
}
public float GetRandom01()
{
return (float)rng.NextDouble();
}
public float GetRandom(float min, float max)
{
return (float)((rng.NextDouble() * (max - min)) + min);
}
public int GetRandom(int min, int max)
{
return rng.Next(min, max);
}
}
}
模板方法模式
***模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法骨架,而将一些步骤的实现延迟到子类中。这种模式允许子类在不改变算法结构的情况下重新定义算法的某些特定步骤。
***定义
模板方法模式定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现。这使得子类可以在不改变算法结构的前提下,重新定义算法的某些步骤。
***主要解决的问题
模板方法模式解决在多个子类中重复实现相同的方法的问题,通过将通用方法抽象到父类中来避免代码重复。
***使用场景
- 当存在一些通用的方法,可以在多个子类中共用时。
- 算法的整体步骤很固定,但其中个别部分易变时。
***实现方式
- 定义抽象父类:包含模板方法和一些抽象方法或具体方法。
- 实现子类:继承抽象父类并实现抽象方法,不改变算法结构。
***关键代码
- 模板方法:在抽象父类中定义,调用抽象方法和具体方法。
- 抽象方法:由子类实现,代表算法的可变部分。
- 具体方法:在抽象父类中实现,代表算法的不变部分。
***优点
- 封装不变部分:算法的不变部分被封装在父类中。
- 扩展可变部分:子类可以扩展或修改算法的可变部分。
- 提取公共代码:减少代码重复,便于维护。
***缺点
- 增加复杂性:类数量增加,增加了系统复杂性。
- 继承缺点:模板方法主要通过继承实现,继承关系自身就有缺点
cs
using System.Collections.Generic;
using FactoryPattern.CarFactory;
using UnityEngine;
namespace TemplateMethodPattern
{
// AssembleCarsController 是控制类,它负责初始化装配线并开始组装不同类型的汽车。
public class AssembleCarsController : MonoBehaviour
{
private void Start()
{
// 初始化两条装配线:一条用于Cybertruck,另一条用于Model S
var cybertruck = new AssembleCybertruck();
var modelS = new AssembleModelS();
// 根据订单组装汽车,包括选择特定的额外配置(CarExtras)
cybertruck.AssembleCar(new List<CarExtra>() { new(CarExtras.DracoThruster, 1), new(CarExtras.EjectionSeat, 1), });
modelS.AssembleCar(new List<CarExtra>() { new(CarExtras.DracoThruster, 1), new(CarExtras.EjectionSeat, 2), });
// 组装没有额外配置的Model S
modelS.AssembleCar(null);
}
}
}
using System.Collections.Generic;
using FactoryPattern.CarFactory;
using UnityEngine;
namespace TemplateMethodPattern
{
// _AssemblyLine 是抽象类,定义了汽车装配的基本框架和步骤。
public abstract class _AssemblyLine
{
// AssembleCar 方法是模板方法,定义了组装汽车的步骤,子类不能覆盖这个方法。
public void AssembleCar(List<CarExtra> carExtras)
{
InitAssemblyProcess(); // 初始化装配过程
if (!CanManufactureCar()) // 检查是否可以制造汽车
return;
GetCarBase(); // 获取汽车底盘
GetCarBattery(); // 获取电池
GetCarBody(); // 获取车身
CoffeeBreak(); // 工人休息时间
GetWheels(); // 获取轮子
GetCarExtras(carExtras); // 获取额外配置
}
// CoffeeBreak 和 InitAssemblyProcess 是具体方法,实现了所有子类共用的行为。
protected void CoffeeBreak()
{
Debug.Log("Take a coffee break");
}
protected void InitAssemblyProcess()
{
Debug.Log("Init" + GetType().Name);
}
// GetCarExtras 方法根据提供的额外配置列表来获取相应的配件。
protected void GetCarExtras(List<CarExtra> carExtras)
{
if (carExtras == null) // 如果没有额外配置,则输出提示信息
{
Debug.Log("This car comes with no extras");
return;
}
foreach (CarExtra extra in carExtras) // 遍历额外配置列表,并分别处理每个配置项
{
switch (extra.Extra)
{
case CarExtras.DracoThruster:
Debug.Log($"Get {extra.Number} Draco Thruster");
break;
case CarExtras.EjectionSeat:
Debug.Log($"Get {extra.Number} Ejection Seat");
break;
}
}
}
// 抽象方法,由子类实现以提供具体的实现细节。
protected abstract void GetCarBody();
protected abstract void GetCarBase();
protected abstract void GetCarBattery();
protected abstract void GetWheels();
// CanManufactureCar 是钩子方法,允许子类决定是否能够制造汽车。
protected virtual bool CanManufactureCar()
{
return true;
}
}
}
using UnityEngine;
namespace TemplateMethodPattern
{
public class AssembleModelS : _AssemblyLine
{
// 实现抽象方法,提供针对Model S的具体实现。
protected override void GetCarBase()
{
Debug.Log("Get Model S base");
}
protected override void GetCarBattery()
{
Debug.Log("Get Model S battery");
}
protected override void GetCarBody()
{
Debug.Log("Get Model S body");
}
protected override void GetWheels()
{
Debug.Log("Get Model S wheels");
}
}
}
using UnityEngine;
namespace TemplateMethodPattern
{
public class AssembleCybertruck : _AssemblyLine
{
// 实现抽象方法,提供针对Cybertruck的具体实现。
protected override void GetCarBase()
{
Debug.Log("Get Cybertruck base");
}
protected override void GetCarBattery()
{
Debug.Log("Get Cybertruck battery");
}
protected override void GetCarBody()
{
Debug.Log("Get Cybertruck body");
}
protected override void GetWheels()
{
Debug.Log("Get Cybertruck wheels");
}
// 重写 CanManufactureCar 方法,返回 false 表示 Cybertruck 不能被制造。
protected override bool CanManufactureCar()
{
Debug.Log("Sorry but the Cybertruck is still a prototype so we can't manufacture it!");
return false;
}
}
}
备忘录模式
***备忘录模式(Memento Pattern)是一种行为型设计模式,它允许在不破坏对象封装性的前提下,捕获并保存对象的内部状态,以便在将来恢复到之前的状态。这种模式特别适用于实现撤销(Undo)操作的功能。
***定义
- 发起人(Originator):创建并恢复备忘录的对象,负责定义哪些属于备份范围的状态。
- 备忘录(Memento):存储发起人内部状态的快照,并且防止其他对象访问备忘录。备忘录通常只有发起人可以访问其内容。
- 管理者(Caretaker):负责保存和恢复备忘录,但不能检查或操作备忘录的内容。
***应用场景
- 文本编辑器:实现撤销和重做功能。
- 游戏开发:保存游戏进度,允许玩家在失败时重新开始。
- 数据库事务:保存事务开始前的状态,以便在事务失败时回滚。
- 图形界面:保存画布或绘图状态,以便用户可以撤销之前的绘图操作。
***实现方式
- 发起人(Originator)类,它负责维护内部状态,并可以创建备忘录对象和从备忘录对象中恢复状态。
- 备忘录(Memento)类,用于存储发起人的内部状态。备忘录类应该提供方法来获取和设置状态。
- 管理者(Caretaker)类,它负责管理备忘录对象。通常,管理者会维护一个备忘录列表,可以添加和检索备忘录对象。
- 在发起人类中添加方法来创建备忘录对象和从备忘录对象中恢复状态。
***优点
- 提供了一种可以恢复状态的机制,方便地将数据恢复到某个历史状态。
- 实现了内部状态的封装,除了创建它的发起人之外,其他对象都不能访问这些状态信息。
- 简化了发起人类,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理。
ad-info
*命令模式是方法的面向对象实现,备忘录模式是存储状态的面向对象实现*
cs
using UnityEngine;
namespace MementoPattern
{
public class MementoController : MonoBehaviour
{
public Apple apple;
private MementoCaretaker _mc;
private void Start()
{
_mc = new MementoCaretaker();
var builder = new AppleBuilder("Apple"); // 创建AppleBuilder实例
apple = builder.Generate(); // 使用AppleBuilder生成Apple实例
_mc.AddMemento(apple.CreateMemento()); // 保存Apple的初始状态
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Return))
{
_mc.AddMemento(apple.CreateMemento());
Debug.Log($"存储成功,当前队列数量{_mc.GetMementoCount()}");
}
int offset = -1;
if (Input.GetKeyDown(KeyCode.Alpha0))
{
offset = 0;
}
else if (Input.GetKeyDown(KeyCode.Alpha1))
{
offset = 1;
}
else if (Input.GetKeyDown(KeyCode.Alpha2))
{
offset = 2;
}
else if (Input.GetKeyDown(KeyCode.Alpha3))
{
offset = 3;
}
else if (Input.GetKeyDown(KeyCode.Alpha4))
{
offset = 4;
}
else if (Input.GetKeyDown(KeyCode.Alpha5))
{
offset = 5;
}
else if (Input.GetKeyDown(KeyCode.Alpha6))
{
offset = 6;
}
else if (Input.GetKeyDown(KeyCode.Alpha7))
{
offset = 7;
}
else if (Input.GetKeyDown(KeyCode.Alpha8))
{
offset = 8;
}
else if (Input.GetKeyDown(KeyCode.Alpha9))
{
offset = 9;
}
// 如果设置了有效的offset值,则尝试回退到指定的历史状态
if (offset == -1) return;
int idx = _mc.GetMementoCount() - offset;
AppleMemento memento = _mc.GetMemento(idx - 1);
if (null != memento)
{
Debug.Log($"后退{offset}步到到{idx}");
apple.Restore(memento); // 恢复到指定的memento状态
}
else
{
Debug.LogError("No Memento");
}
}
}
// 提供了一种构建Apple对象的方法
public class AppleBuilder
{
private string prefab;
/// <summary>
/// 构造函数,接收预制体名称
/// </summary>
/// <param name="prefab">预制体资源</param>
public AppleBuilder(string prefab)
{
this.prefab = prefab;
}
// 从资源中加载预制体,并实例化一个新的Apple对象
public Apple Generate()
{
GameObject appleObj = Object.Instantiate(Resources.Load<GameObject>(prefab));
var apple = appleObj.GetComponent<Apple>();
return apple;
}
}
}
using System.Collections.Generic;
namespace MementoPattern
{
// 管理多个AppleMemento对象,维护了一个备忘录列表
public class MementoCaretaker
{
private readonly List<AppleMemento> _mementoList = new();
//添加一个新的备忘录到列表中
internal void AddMemento(AppleMemento memento)
{
_mementoList.Add(memento);
}
//根据索引获取备忘录
internal AppleMemento GetMemento(int index)
{
if (index >= 0 && index < _mementoList.Count)
{
return _mementoList[index];
}
return null;
}
//返回备忘录列表中的元素数量
internal int GetMementoCount()
{
return _mementoList.Count;
}
}
}
using UnityEngine;
namespace MementoPattern
{
// 作为Apple对象状态的快照,保存了特定时刻的位置、缩放和旋转
internal class AppleMemento
{
private readonly Vector3 _startPos;
private readonly Vector3 _scale;
private readonly Quaternion _rotation;
//接收一个Apple对象并保存它的状态
public AppleMemento(Apple apple)
{
_startPos = apple.GetStartPos();
_scale = apple.GetScale();
_rotation = apple.GetRotation();
}
// 获取保存的位置
public Vector3 GetStartPos()
{
return _startPos;
}
// 获取保存的缩放
public Vector3 GetScale()
{
return _scale;
}
// 获取保存的旋转
public Quaternion GetRotation()
{
return _rotation;
}
}
}
using UnityEngine;
namespace MementoPattern
{
public class Apple : MonoBehaviour
{
private float speed = 5f;
private float rotationSpeed = 30f;
private void Start()
{
GetComponent<Renderer>().material.color = Color.red;
}
private void Update()
{
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
{
HandleRotation();
}
else
{
HandleMovement();
}
}
private void HandleMovement()
{
// 初始化水平和垂直移动量为0
var horizontal = 0f;
var vertical = 0f;
if (Input.GetKey(KeyCode.A))
{
horizontal -= 1;
}
if (Input.GetKey(KeyCode.D))
{
horizontal += 1;
}
if (Input.GetKey(KeyCode.W))
{
vertical += 1;
}
if (Input.GetKey(KeyCode.S))
{
vertical -= 1;
}
Vector3 movement = new Vector3(horizontal, vertical, 0) * (speed * Time.deltaTime);
transform.Translate(movement, Space.World);
}
private void HandleRotation()
{
// 初始化水平和垂直旋转量为0
var horizontal = 0f;
var vertical = 0f;
if (Input.GetKey(KeyCode.A))
{
horizontal -= 1;
}
if (Input.GetKey(KeyCode.D))
{
horizontal += 1;
}
if (Input.GetKey(KeyCode.W))
{
vertical += 1;
}
if (Input.GetKey(KeyCode.S))
{
vertical -= 1;
}
// 计算旋转向量
Vector3 rotation = new Vector3(vertical, 0, horizontal) * (rotationSpeed * Time.deltaTime);
// 更新物体的旋转
transform.Rotate(rotation, Space.World);
}
// 获取当前位置
public Vector3 GetStartPos()
{
return transform.position;
}
// 获取当前缩放
public Vector3 GetScale()
{
return transform.lossyScale;
}
// 获取当前旋转
public Quaternion GetRotation()
{
return transform.rotation;
}
// 创建一个包含当前状态的备忘录
internal AppleMemento CreateMemento()
{
return new AppleMemento(this);
}
// 根据提供的备忘录恢复状态
internal void Restore(AppleMemento memento)
{
transform.position = memento.GetStartPos();
transform.localScale = memento.GetScale();
transform.rotation = memento.GetRotation();
}
}
}
策略模式
***策略模式(Strategy Pattern)是一种行为型设计模式,它使你能在运行时改变对象的行为。策略模式通过定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户。
***定义
- 策略模式的核心在于定义算法族,分别封装起来,让它们之间可以互相替换。策略模式让算法的变化,不会影响到使用算法的客户。换言之,策略模式使动态选择算法成为可能。
***主要角色
- 策略(Strategy):定义所有支持的算法的公共接口。
- 具体策略(Concrete Strategy):实现策略接口的具体算法。
- 上下文(Context):使用策略的类,可以动态地改变其行为。
***应用场景
- 需要在运行时选择算法或行为的场景。
- 需要封装算法,使得算法可以互换的场景。
- 需要避免使用多重条件(if-else 或 switch-case)语句来选择算法的场景。
***实现步骤
- 定义策略接口,声明执行操作的方法。
- 创建具体策略类,实现策略接口,提供不同的算法实现。
- 定义上下文类,持有一个策略对象的引用。
- 根据需要,客户端代码可以在运行时更改上下文所持有的策略引用。
***优点
- 算法可以独立于使用它的客户而变化。
- 策略模式使算法的变化独立于使用算法的客户。
- 可以方便地增加新的算法。
- 使算法可替换、可组合。
cs
using UnityEngine;
namespace StrategyPattern
{
public class StrategyController : MonoBehaviour
{
private void Start()
{
// 定义起始位置和结束位置
Vector3 startPos = transform.position;
var endPos = new Vector3(10, 0, 10);
// 创建一个飞行NPC,并设置其导航规划器为飞行路径规划器
var theFlyNPC = new NPC();
theFlyNPC.SetNavPlanner(new FlyNavPlanner());
foreach (Vector3 navPath in theFlyNPC.GetNavPath(startPos, endPos))
{
Debug.Log($"flyNpc规划的路径是:{navPath}");
}
// 创建一个游泳NPC,并设置其导航规划器为游泳路径规划器
var theSwimNPC = new NPC();
theSwimNPC.SetNavPlanner(new SwimNavPlanner());
foreach (Vector3 navPath in theSwimNPC.GetNavPath(startPos, endPos))
{
Debug.Log($"swimNpc规划的路径是:{navPath}");
}
// 创建一个跳跃NPC,并设置其导航规划器为跳跃路径规划器
var theJumpNPC = new NPC();
theJumpNPC.SetNavPlanner(new JumpNavPlanner());
foreach (Vector3 navPath in theJumpNPC.GetNavPath(startPos, endPos))
{
Debug.Log($"jumpNpc规划的路径是:{navPath}");
}
// 创建一个直线移动的NPC,并设置其导航规划器为直线路径规划器
var theStraightNPC = new NPC();
theStraightNPC.SetNavPlanner(new StraightNavPlanner());
foreach (Vector3 navPath in theStraightNPC.GetNavPath(startPos, endPos))
{
Debug.Log($"straightNpc规划的路径是:{navPath}");
}
var pos = new Vector3(1, 5, 1);
// 创建一个特定于游泳的NPC,并为其设置特殊的采样策略
var swimNPC2 = new LambdaNPC();
swimNPC2.SetSampleStrategy(_ => new Vector3(pos.x, 0, pos.z)); // 强制Y轴为0,模拟游泳行为
// 测试游泳NPC的采样策略
pos = new Vector3(1, 5, 1);
Debug.Log($"swimNPC2 samplePos {pos}->{swimNPC2.SamplePos(pos)}");
pos = new Vector3(2, 5, 2);
Debug.Log($"swimNPC2 samplePos {pos}->{swimNPC2.SamplePos(pos)}");
}
}
}
using UnityEngine;
namespace StrategyPattern
{
public class NPC
{
private AbstractNavPlanner _navPlanner;
// 设置NPC的导航规划器
public void SetNavPlanner(AbstractNavPlanner navPlanner)
{
_navPlanner = navPlanner;
}
// 根据当前导航规划器获取从startPos到endPos的导航路径
public Vector3[] GetNavPath(Vector3 startPos, Vector3 endPos)
{
return _navPlanner.GetNavPath(startPos, endPos);
}
}
}
using System;
using UnityEngine;
namespace StrategyPattern
{
// LambdaNPC类,允许通过Lambda表达式设置采样策略
public class LambdaNPC
{
private Func<Vector3, Vector3> _sampleStrategy; // 采样策略
// 设置LambdaNPC的采样策略
public void SetSampleStrategy(Func<Vector3, Vector3> sampleStrategy)
{
_sampleStrategy = sampleStrategy;
}
// 应用采样策略到指定的位置上
public Vector3 SamplePos(Vector3 pos)
{
return _sampleStrategy(pos);
}
}
}
using UnityEngine;
namespace StrategyPattern
{
// 抽象导航规划器类,所有具体的导航规划器都继承自这个类
public abstract class AbstractNavPlanner
{
// 获得从startPos到endPos的导航路径
public abstract Vector3[] GetNavPath(Vector3 startPos, Vector3 endPos);
}
}
using UnityEngine;
namespace StrategyPattern
{
public class FlyNavPlanner : AbstractNavPlanner
{
public override Vector3[] GetNavPath(Vector3 startPos, Vector3 endPos)
{
// 返回一个包含起始点、中间高点和终点的路径
return new[] { startPos, startPos + (endPos / 2) + (Vector3.up * 5), endPos, };
}
}
}
using UnityEngine;
namespace StrategyPattern
{
public class SwimNavPlanner : AbstractNavPlanner
{
public override Vector3[] GetNavPath(Vector3 startPos, Vector3 endPos)
{
// 返回一个包含起始点、水中某一点和终点的路径
return new[] { startPos, startPos + (endPos / 2) + (Vector3.down * 5), endPos, };
}
}
}
using UnityEngine;
namespace StrategyPattern
{
public class StraightNavPlanner : AbstractNavPlanner
{
public override Vector3[] GetNavPath(Vector3 startPos, Vector3 endPos)
{
// 返回一条简单的直线路径,仅包含起始点和终点
return new[] { startPos, endPos, };
}
}
}
using UnityEngine;
namespace StrategyPattern
{
public class JumpNavPlanner : AbstractNavPlanner
{
public override Vector3[] GetNavPath(Vector3 startPos, Vector3 endPos)
{
// 返回一个包含起始点、跳跃高点和终点的路径
return new[] { startPos, startPos + (endPos / 2) + Vector3.up, endPos, };
}
}
}
ad-hint
+ *可复用的算法,使用面向对象版本的策略模式*
+ *不可复用的算法,用Lambda表达式版本的策略模式*
建造者模式
***建造者模式(Builder Pattern)是一种创建型设计模式,它允许你分步骤构建一个复杂对象。这种模式将对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
***主要角色
- 产品(Product):要构建的复杂对象,它应该包含多个部分。
- 抽象建造者(Builder):定义了构建产品的抽象接口,包括构建产品的各个部分的方法。
- 具体建造者(Concrete Builder):实现抽象建造者接口,具体确定如何构建产品的各个部分,并负责返回最终构建的产品。
- 指导者(Director):负责调用建造者的方法来构建产品,指导者并不了解具体的构建过程,只关心产品的构建顺序和方式。
***应用场景
- 当创建对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
- 当需要创建一个复杂对象,但是它的部分构造过程必须有一定的顺序时。
- 当需要在对象创建过程中进行更加精细的控制时。
***实现步骤
- 定义产品类,表示被构建的复杂对象。
- 定义建造者接口,包含构建对象步骤和设置属性的方法。
- 创建具体建造者类,实现建造者接口,并实现构建对象的具体步骤。
- 创建指导者类,负责协调构建过程,并控制建造者的顺序。
- 在客户端代码中使用指导者构建对象。
***优点
- 将对象的构建与表示分离,使得同样的构建过程可以创建不同的表示,增加了程序的灵活性。
- 将复杂对象的构造代码与表示代码分开,易于维护。
- 隐藏了对象构建的细节,使得构建代码与表示代码分离,提高了代码的可读性和可维护性。
- 可以对不同的具体建造者进行更换和扩展
cs
using UnityEngine;
namespace BuilderPattern
{
public class BuilderController : MonoBehaviour
{
private void Start()
{
// 创建一个没有武器的怪物
var buildParam1001 = new MonsterBuilderParam("monster_1001", "Monster", 100, 100, 0, 50);
var monsterBuilder = new MonsterBuilder(buildParam1001);
var monster = (Monster)monsterBuilder.ConstructFull();
monster.transform.position = new Vector3(0, 0, 0);
// 创建携带武器weapon002的怪物
var buildParam1002 = new MonsterBuilderParam("monster_1002", "Monster", 100, 100, 20, 50, "weapon002");
monsterBuilder.Reset(buildParam1002);
var monster2 = (Monster)monsterBuilder.ConstructFull();
monster2.transform.position = new Vector3(2, 0, 0);
// 创建一个最基础的怪物,不设置属性
var buildParam1003 = new MonsterBuilderParam("monster_1003", "Monster");
monsterBuilder.Reset(buildParam1003);
var monster3 = (Monster)monsterBuilder.ConstructMinimal();
monster3.transform.position = new Vector3(4, 0, 0);
// 创建一只跟随monster3的宠物
var petBuilderParam = new PetBuilderParam("pet_1001", "Pet", monster3.gameObject.transform);
var petBuilder = new PetBuilder(petBuilderParam);
//petBuilder.AddWeapon(); // 给宠物添加武器,报错
var pet = (Pet)petBuilder.ConstructFull();
pet.transform.position = new Vector3(5, 0, 0);
}
}
}
namespace BuilderPattern
{
// MonsterBuilderParam 类继承自 ActorBuilderParam,为怪物提供特定参数
public class MonsterBuilderParam : ActorBuilderParam
{
public float hp;
public float maxHp;
public float mp;
public float maxMp;
public string weaponId;
public MonsterBuilderParam(string id, string prefabName, float hp, float maxHp, float mp, float maxMp, string weaponId = null)
{
this.id = id;
this.prefabName = prefabName;
this.hp = hp;
this.maxHp = maxHp;
this.mp = mp;
this.maxMp = maxMp;
this.weaponId = weaponId;
}
public MonsterBuilderParam(string id, string prefabName)
{
this.id = id;
this.prefabName = prefabName;
}
}
}
namespace BuilderPattern
{
// 定义了一个抽象类,用于封装所有需要传递给建造者的参数
public abstract class ActorBuilderParam
{
// 每个游戏对象的唯一标识符和预制体名称
public string id;
public string prefabName;
}
}
using UnityEngine;
namespace BuilderPattern
{
// PetBuilderParam 类继承自 ActorBuilderParam,为宠物提供特定参数
public class PetBuilderParam : ActorBuilderParam
{
// 宠物跟随的目标Transform
public Transform followTarget;
public PetBuilderParam(string id, string prefabName, Transform followTarget = null)
{
this.id = id;
this.prefabName = prefabName;
this.followTarget = followTarget;
}
}
}
using UnityEngine;
namespace BuilderPattern
{
// 抽象建造者类,定义了构建游戏对象的一般流程。
public abstract class ActorBuilder
{
protected Actor actor; // 游戏中的Actor组件
protected GameObject gameObject; // 当前正在构建的游戏对象
// 设置基础属性
public abstract void SetBaseAttr();
public abstract Component AddWeapon();
public abstract Component AddAI();
public abstract Component AddBag();
public abstract Actor AddActor();
public abstract GameObject LoadModel();
public virtual bool HasBag()
{
return false;
}
public virtual bool HasWeapon()
{
return true;
}
// 重置建造者状态
public virtual void Reset(ActorBuilderParam buildParam)
{
actor = null;
}
public virtual Component AddOnClickBehaviour()
{
var com = gameObject.AddComponent<OnClickBehaviour>();
return com;
}
// 构建完整的游戏对象,包括所有可能的组件
public Actor ConstructFull()
{
GameObject go = LoadModel();
actor = AddActor();
SetBaseAttr();
// 根据钩子方法判断是否添加特定组件
if (HasWeapon())
{
AddWeapon();
}
AddAI();
AddOnClickBehaviour();
if (HasBag())
{
AddBag();
}
return actor;
}
// 构建最小化的游戏对象,仅包含必要组件。
public Actor ConstructMinimal()
{
GameObject go = LoadModel();
actor = AddActor();
SetBaseAttr();
return actor;
}
}
}
using UnityEngine;
namespace BuilderPattern
{
// MonsterBuilder 类继承自 ActorBuilder,实现具体怪物的构建逻辑
public class MonsterBuilder : ActorBuilder
{
private MonsterBuilderParam buildParam;
public MonsterBuilder(MonsterBuilderParam buildParam)
{
this.buildParam = buildParam;
}
public override Component AddAI()
{
BaseAI com = AIFactory.CreateAI<MonsterAI>(gameObject);
return com;
}
public override Component AddWeapon()
{
Weapon com = WeaponFactory.CreateWeapon(gameObject, buildParam.weaponId);
com.weaponId = buildParam.weaponId;
return com;
}
public override bool HasBag()
{
return false;
}
public override bool HasWeapon()
{
return buildParam.weaponId != null;
}
public override Component AddBag()
{
throw new System.NotImplementedException();
}
public override void SetBaseAttr()
{
actor.id = buildParam.id;
}
public override GameObject LoadModel()
{
gameObject = (GameObject)Object.Instantiate(Resources.Load(buildParam.prefabName));
gameObject.name = buildParam.id;
return gameObject;
}
public override Actor AddActor()
{
var com = gameObject.AddComponent<Monster>();
com.hp = buildParam.hp;
com.mp = buildParam.mp;
com.maxHp = buildParam.maxHp;
com.maxMp = buildParam.maxMp;
return com;
}
// 重写Reset方法,允许使用新的参数重新配置建造者
public override void Reset(ActorBuilderParam buildParam)
{
base.Reset(buildParam);
this.buildParam = (MonsterBuilderParam)buildParam;
}
}
}
using UnityEngine;
namespace BuilderPattern
{
// PetBuilder 类继承自 ActorBuilder,实现具体宠物的构建逻辑。
public class PetBuilder : ActorBuilder
{
private PetBuilderParam buildParam;
public PetBuilder(PetBuilderParam buildParam)
{
this.buildParam = buildParam;
}
public override Component AddAI()
{
BaseAI com = AIFactory.CreateAI<PetAI>(gameObject);
return com;
}
public override Component AddBag()
{
var com = gameObject.AddComponent<Bag>();
return com;
}
public override bool HasBag()
{
return true;
}
public override bool HasWeapon()
{
return false;
}
public override Component AddWeapon()
{
throw new System.NotImplementedException();
}
public override void SetBaseAttr()
{
actor.id = buildParam.id;
}
public override GameObject LoadModel()
{
gameObject = (GameObject)Object.Instantiate(Resources.Load(buildParam.prefabName));
gameObject.name = buildParam.id;
return gameObject;
}
// 添加Pet组件,并设置跟随目标
public override Actor AddActor()
{
var com = gameObject.AddComponent<Pet>();
com.followTransform = buildParam.followTarget;
return com;
}
}
}
using UnityEngine;
namespace BuilderPattern
{
// Pet 类继承自 Actor,增加了跟随目标的特性。
public class Pet : Actor
{
public Transform followTransform; // 宠物要跟随的目标
// 宠物特有的初始化或更新逻辑可以写在这里
}
}
namespace BuilderPattern
{
public class PetAI : BaseAI
{
// 宠物特定的AI逻辑可以在这里实现
}
}
namespace BuilderPattern
{
public class Monster : Actor
{
public float hp; // 当前生命值
public float maxHp; // 最大生命值
public float mp; // 当前魔法值
public float maxMp; // 最大魔法值
// 怪物特有的初始化或更新逻辑可以写在这里
}
}
namespace BuilderPattern
{
// MonsterAI 继承自 BaseAI,实现了怪物的人工智能行为。
public class MonsterAI : BaseAI
{
// 怪物特定的AI逻辑可以在这里实现
}
}
using UnityEngine;
namespace BuilderPattern
{
// 点击行为组件,响应点击事件。
public class OnClickBehaviour : MonoBehaviour
{
// 响应点击事件的逻辑可以写在这里
}
}
using UnityEngine;
namespace BuilderPattern
{
public class BaseAI : MonoBehaviour
{
// 通用的AI逻辑可以写在这里
}
}
using UnityEngine;
namespace BuilderPattern
{
public class Bag : MonoBehaviour
{
// 背包相关的逻辑可以写在这里
}
}
using UnityEngine;
namespace BuilderPattern
{
// 武器组件,表示一个游戏中的武器
public class Weapon : MonoBehaviour
{
public string weaponId;
private void Start()
{ // 初始化逻辑
}
private void Update() { // 每帧更新逻辑
}
}
}
using UnityEngine;
namespace BuilderPattern
{
// 武器工厂用于创建武器实例,可以扩展为根据不同的参数创建不同类型的武器
public class WeaponFactory : MonoBehaviour
{
public static Weapon CreateWeapon(GameObject go, string weaponId)
{
var weapon = go.AddComponent<Weapon>();
weapon.weaponId = weaponId;
return weapon;
}
}
}
using UnityEngine;
namespace BuilderPattern
{
// AI 工厂用于创建不同类型的AI组件。
public class AIFactory
{
//可以在此处实现具体的AI创建逻辑。
public static BaseAI CreateAI<T>(GameObject go) where T : BaseAI
{
return go.AddComponent<T>();
}
}
}
using UnityEngine;
namespace BuilderPattern
{
public class Actor : MonoBehaviour
{
public string id;
// public Actor(string id,string prefabName,float hp,float maxHp,float mp,float maxMp,float hasBag,string weaponId,Transform followTransform)
// {
// } public Actor()
{
}
}
}
数据局部性模式
***数据局部性模式(Data Locality Pattern)是一种优化计算机程序性能的技术,其目的是最大化数据处理的速度。该模式基于这样一个事实:处理器访问距离其近的内存(如缓存)要比访问远处的存储(如RAM或硬盘)更快。数据局部性模式的核心在于组织数据结构,使得经常一起使用的数据能够被存储在物理上相近的位置。
***数据局部性的类型
- 时间局部性(Temporal Locality):最近被访问的数据在不久的将来可能再次被访问。
- 空间局部性(Spatial Locality):如果一个数据项被访问,那么与它相邻的数据项不久后也可能被访问。
***为什么数据局部性模式重要
- 数据局部性模式对于提升程序性能至关重要,尤其是在处理大量数据或高性能计算任务时。通过优化数据存储,可以减少缓存未命中(Cache Miss)的情况,从而提高程序的运行效率
ad-note
*使用这个模式获得良好的缓存命中率的时候,也会在其他方面有所损失。比如说,我们就不能用各种接口与指针了。因为我们必须保证他们在内存中是一起的,并且直接访问内存块,而不是通过指针跳来跳去。所以,越是良好的数据局部性模式,越需要抛弃掉抽象之类的东西*
cs
//在for循环中遍历了指针,但指针又指向另外的内存,这就引发了缓存的低命中率
private void Update(){
for(int i=0;i<numEntities;i++){
entities[i].comAI.Update();
}
for(int i=0;i<numEntities;i++){
entities[i].render.Renderer();
}
for(int i=0;i<numEntities;i++){
entities[i].fsm.Update();
}
}
//利用数据局部性特性
private void Update(){
for(int i=0;i<numEntities;i++){
comAIs[i].Update();
}
for(int i=0;i<numEntities;i++){
renders[i].Renderer();
}
for(int i=0;i<numEntities;i++){
fsms[i].Update();
}
}