Command 和 Query
Command 负责 增|删|改, Query 负责 查, 但是 Query 并不是必选的;
有的时候计算量不是那么重的时候可以直接通过 Model 直接查询数据, 但是复杂跨 Model 数据就依赖 Query.
比如当前有 玩家模型(PlayerModel) 和 敌人模型(EnemyModel), 你需要战斗计算敌人的真实攻击数值;
这个真实攻击数值等于 敌人攻击力减去玩家防御值 的情况, 这时候就需要跨模型查询出 玩家防御值 - 敌人攻击值 抵消的伤害:
/// <summary>
/// 计算敌人真实伤害
/// </summary>
public class EnemyAttackValueQuery : AbstractQuery<int>
{
protected override int OnDo()
{
// 获取敌人攻击 - 玩家防御之后真实数值
var v = this.GetModel<EnemyModel>().Atk -
this.GetModel<PlayerModel>().Def;
return v > 0 ? v : 1; // 最小攻击伤害 1
}
}
这种复杂查询的情况就适合 Query 进行发挥;
对于网络游戏来说, 如果需要从服务器同步数据则通过 Query 内部拉取, 而增删改同步到服务器则需要在 Command 做处理
MonoBehaviour 单例
using System;
using UnityEngine;
// 单例实现
namespace Utility
{
/// <summary>
/// MonoBehaviour全局单例
/// </summary>
/// <typeparam name="T">Component</typeparam>
public abstract class MonoSingleton<T> : MonoBehaviour where T : Component
{
private static T _instance;
public bool SingletonEnabled { get; private set; } = false;
/// <summary>
/// 静态句柄(懒加载, 调用后才会执行初始化)
/// </summary>
public static T Instance
{
get
{
if (_instance != null) return _instance;
_instance = FindObjectOfType<T>();
if (_instance != null) return _instance;
var go = new GameObject();
_instance = go.AddComponent<T>();
return _instance;
}
}
/// <summary>
/// 组件唤醒回调
/// </summary>
protected virtual void Awake()
{
if (!Application.isPlaying)
{
return;
}
if (_instance == null)
{
_instance = this as T;
DontDestroyOnLoad(transform.gameObject);
SingletonEnabled = true;
}
else
{
if (this != _instance)
{
Destroy(gameObject);
}
}
}
/// <summary>
/// 退出清理
/// </summary>
private void OnApplicationQuit()
{
if (_instance == null) return;
Destroy(_instance.gameObject);
_instance = null;
}
/// <summary>
/// 组件销毁
/// </summary>
private void OnDestroy()
{
_instance = null;
}
}
}
这里之所以要先说明采用静态全局扩展是因为很多时候游戏 Controller 功能是必须要带有全局唯一状态,
比如之前定义玩家数据模型在调用时候可能需要生成全局管理唯一 监控器 方便处理.
GameObject 快速状态切换
using UnityEngine;
// Unity扩展
namespace Utility
{
public static class Unity
{
/// <summary>
/// 扩展GameObject快速切换 Active 状态
/// </summary>
/// <param name="go">Unity组件</param>
/// <param name="active">切换状态</param>
public static void SetActiveFast(this GameObject go, bool active)
{
if (go.activeSelf != active)
{
go.SetActive(active);
}
}
}
}
如果后续 GameObject 直接通过 gameObject.SetActiveFast(bool) 就能快速切换状态.
有限状态机(FSM)
using System;
using System.Collections.Generic;
using UnityEngine;
// 自定义扩展状态机
// ReSharper disable once CheckNamespace
namespace Utility
{
/// <summary>
/// 有限状态机接口
/// </summary>
/// ReSharper disable once InconsistentNaming
public interface IFSM
{
bool IsActive(); // 确定是否可用
void OnEnter(); // 进入状态机回调
void OnUpdate(); // 逻辑帧更新回调
void OnFixedUpdate(); // 物理帧更新回调
void OnLateUpdate();// 后处理更新帧
void OnExit(); // 退出状态机回调
}
/// <summary>
/// 状态机运行时, 负责保存实现状态机对象
/// </summary>
/// ReSharper disable once InconsistentNaming
public class FSM:IFSM
{
#region 状态机可用
/// <summary>
/// 状态机是否可用
/// </summary>
private bool _isActive;
/// <summary>
/// 设置可用
/// </summary>
/// <param name="isActive">可用状态</param>
/// <returns>FSM</returns>
public FSM SetActive(bool isActive)
{
_isActive = isActive;
return this;
}
/// <summary>
/// 确认状态机是否可用
/// </summary>
/// <returns>bool</returns>
public bool IsActive()
{
return _isActive;
}
#endregion
#region 状态进入回调
/// <summary>
/// 状态进入回调成员
/// </summary>
private Action _onEnter;
/// <summary>
/// 设置回调
/// </summary>
/// <param name="onEnter">事件回调</param>
/// <returns>FSM</returns>
public FSM OnEnter(Action onEnter)
{
_onEnter = onEnter;
return this;
}
/// <summary>
/// 唤起事件回调
/// </summary>
public void OnEnter()
{
_onEnter?.Invoke();
}
#endregion
#region 状态逻辑帧更新回调
/// <summary>
/// 逻辑帧回调成员
/// </summary>
private Action _onUpdate;
/// <summary>
/// 设置回调
/// </summary>
/// <param name="onUpdate">设置回调对象</param>
/// <returns>FSM</returns>
public FSM OnUpdate(Action onUpdate)
{
_onUpdate = onUpdate;
return this;
}
/// <summary>
/// 逻辑帧回调唤醒
/// </summary>
public void OnUpdate()
{
_onUpdate?.Invoke();
}
#endregion
#region 状态物理帧更新回调
/// <summary>
/// 物理帧事件对象
/// </summary>
private Action _onFixedUpdate;
/// <summary>
/// 设置物理帧回调
/// </summary>
/// <param name="onFixedUpdate">物理帧回调</param>
/// <returns>FSM</returns>
public FSM OnFixedUpdate(Action onFixedUpdate)
{
_onFixedUpdate = onFixedUpdate;
return this;
}
/// <summary>
/// 唤醒物理帧回调
/// </summary>
public void OnFixedUpdate()
{
_onFixedUpdate?.Invoke();
}
#endregion
#region 后处理更新帧
/// <summary>
/// 后处理更新帧时间
/// </summary>
private Action _onLateUpdate;
/// <summary>
/// 设置后处理更新帧
/// </summary>
/// <param name="onLateUpdate">更新帧事件</param>
/// <returns>FSM</returns>
public FSM OnLateUpdate(Action onLateUpdate)
{
_onLateUpdate = onLateUpdate;
return this;
}
/// <summary>
/// 唤醒后处理更新帧事件
/// </summary>
public void OnLateUpdate()
{
_onLateUpdate?.Invoke();
}
#endregion
#region 状态退出回调
/// <summary>
/// 状态退出属性
/// </summary>
private Action _onExit;
/// <summary>
/// 设置退出回调
/// </summary>
/// <param name="onExit">退出回调</param>
/// <returns>FSM</returns>
public FSM OnExit(Action onExit)
{
_onExit = onExit;
return this;
}
/// <summary>
/// 唤起退出事件
/// </summary>
public void OnExit()
{
_onExit?.Invoke();
}
#endregion
}
/// <summary>
/// 有限状态机工厂
/// </summary>
/// <typeparam name="T"></typeparam>
/// ReSharper disable once InconsistentNaming
/// ReSharper disable once ClassNeverInstantiated.Global
public class FSMFactory<T>
{
#region 状态调用
/// <summary>
/// 当前状态机标识
/// </summary>
/// ReSharper disable once MemberCanBePrivate.Global
public T CurrentStateId { protected set; get; }
/// <summary>
/// 当前状态机对象
/// </summary>
/// ReSharper disable once MemberCanBePrivate.Global
public IFSM CurrentState { protected set; get; }
/// <summary>
/// 上个状态机对象标识
/// </summary>
/// ReSharper disable once MemberCanBePrivate.Global
public T PreviousStateId { protected set; get; }
/// <summary>
/// 当前状态机逻辑帧, 主要逻辑帧并不是固定时间帧所以需要追加编号
/// </summary>
/// ReSharper disable once MemberCanBePrivate.Global
public long FrameCountOfCurrentState { protected set; get; } = 1;
#endregion
#region 状态属性与操作
/// <summary>
/// 内部集成的状态机列表
/// </summary>
// ReSharper disable once CollectionNeverQueried.Local
// ReSharper disable once FieldCanBeMadeReadOnly.Local
private Dictionary<T, IFSM> _states = new();
/// <summary>
/// 追加状态
/// </summary>
/// <param name="key">状态标识</param>
/// <param name="state">状态对象</param>
public void AddState(T key, IFSM state)
{
_states.Add(key, state);
}
/// <summary>
/// 获取保存的状态机运行时, 如果不存在就追加
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public FSM GetAndSaveState(T key)
{
if (_states.TryGetValue(key, out var s))
{
return s as FSM;
}
var state = new FSM();
_states.Add(key, state);
return state;
}
/// <summary>
/// 获取状态机运行时
/// </summary>
/// <param name="key"></param>
/// <param name="state"></param>
/// <returns></returns>
public bool GetState(T key, out IFSM state)
{
return _states.TryGetValue(key, out state);
}
/// <summary>
/// 修改目前的状态
/// </summary>
/// <param name="key">状态机标识</param>
public void SetCurrentState(T key)
{
if (key.Equals(CurrentStateId)) return;
if (!_states.TryGetValue(key, out var state)) return;
if (CurrentState == null || !state.IsActive()) return;
// 退出目前的状态机
CurrentState.OnExit();
PreviousStateId = CurrentStateId;
// 切换目前状态机
CurrentStateId = key;
CurrentState = state;
FrameCountOfCurrentState = 1;
CurrentState.OnEnter();
}
/// <summary>
/// 启动状态机
/// </summary>
/// <param name="key">状态机标识</param>
public void StartState(T key)
{
if (!_states.TryGetValue(key, out var state)) return;
// 启动当前状态机
PreviousStateId = key;
CurrentState = state;
FrameCountOfCurrentState = 0;
state.OnEnter();
}
/// <summary>
/// 清理所有状态机
/// </summary>
public void Clear()
{
CurrentState = null;
CurrentStateId = default;
_states.Clear();
}
#endregion
#region 回调唤醒
/// <summary>
/// 逻辑帧更新
/// </summary>
public void OnUpdate()
{
CurrentState?.OnUpdate();
FrameCountOfCurrentState++;
}
/// <summary>
/// 物理帧更新
/// </summary>
public void OnFixedUpdate()
{
CurrentState?.OnFixedUpdate();
}
/// <summary>
/// 后处理帧更新
/// </summary>
public void OnLateUpdate()
{
CurrentState?.OnLateUpdate();
}
#endregion
}
/// <summary>
/// 抽象出来的状态机类
/// </summary>
/// <typeparam name="TStateId">状态机标识</typeparam>
/// <typeparam name="TTarget">状态机对象</typeparam>
/// ReSharper disable once InconsistentNaming
public abstract class AbstractFSM<TStateId, TTarget> : IFSM
{
/// <summary>
/// 状态机管理器
/// </summary>
/// ReSharper disable once MemberCanBeProtected.Global
public FSMFactory<TStateId> Factory { protected set; get; }
/// <summary>
/// 状态管理对象
/// </summary>
public TTarget Target { protected set; get; }
/// <summary>
/// 初始化状态机管理器和目标对象
/// </summary>
/// <param name="factory"></param>
/// <param name="target"></param>
/// ReSharper disable once PublicConstructorInAbstractClass
public AbstractFSM(FSMFactory<TStateId> factory, TTarget target)
{
Factory = factory;
Target = target;
}
/// <summary>
/// 继承类实现启动状态
/// </summary>
/// <returns></returns>
protected virtual bool IsActive() => true;
/// <summary>
/// 继承类实现进入状态机状态
/// </summary>
public virtual void OnEnter()
{
}
/// <summary>
/// 继承类实现逻辑帧更新
/// </summary>
public virtual void OnUpdate()
{
}
/// <summary>
/// 继承类实现物理帧更新
/// </summary>
public virtual void OnFixedUpdate()
{
}
/// <summary>
/// 继承类实现后处理帧更新
/// </summary>
public virtual void OnLateUpdate()
{
}
/// <summary>
/// 继承类实现退出状态机
/// </summary>
public virtual void OnExit()
{
}
/// <summary>
/// 状态机是否可用
/// </summary>
/// <returns>是否可用</returns>
bool IFSM.IsActive()
{
return IsActive();
}
/// <summary>
/// 状态机启动
/// </summary>
void IFSM.OnEnter()
{
Debug.Log("#FSM Enter " + GetType().Name);
OnEnter();
}
/// <summary>
/// 逻辑帧更新
/// </summary>
void IFSM.OnUpdate()
{
OnUpdate();
}
/// <summary>
/// 物理帧更新
/// </summary>
void IFSM.OnFixedUpdate()
{
OnFixedUpdate();
}
/// <summary>
/// 后处理帧更新
/// </summary>
void IFSM.OnLateUpdate()
{
OnLateUpdate();
}
/// <summary>
/// 状态机退出
/// </summary>
void IFSM.OnExit()
{
Debug.Log("#FSM Exit " + GetType().Name);
OnExit();
}
}
}
需要用到状态管理直接实例化 FSMFactory 并对衍生的状态做 AbstractFSM 继承即可.
网路消息序列化
using System;
using System.IO;
using System.Text;
// 字节数组序列化
namespace Utility
{
/// <summary>
/// 网络序列化接口
/// </summary>
public interface IMessage
{
/// <summary>
/// 消息协议ID
/// </summary>
/// <returns></returns>
int GetProtocolId();
/// <summary>
/// 设置协议Id
/// </summary>
/// <param name="protocolId"></param>
void SetProtocolId(int protocolId);
/// <summary>
/// 序列化字节流
/// </summary>
void Serialization(Stream stream);
/// <summary>
/// 反序列化
/// </summary>
void Deserialization(Stream stream);
}
/// <summary>
/// 序列化消息抽象实现
/// </summary>
public abstract class AbstractMessage : IMessage
{
/// <summary>
/// 消息序列包ID, 默认为 0
/// </summary>
/// <returns></returns>
public virtual int GetProtocolId() => 0;
/// <summary>
/// 设置协议Id
/// </summary>
/// <param name="protocolId"></param>
public virtual void SetProtocolId(int protocolId)
{
}
/// <summary>
/// 序列化
/// </summary>
/// <param name="stream"></param>
public abstract void Serialization(Stream stream);
/// <summary>
/// 反序列化
/// </summary>
/// <param name="stream"></param>
public abstract void Deserialization(Stream stream);
}
/// <summary>
/// 序列化处理工具集成
/// 注意: Unity采用字节序而非网络序列, 并且不是默认 UTF8 字符串
/// </summary>
public static class SerializeUtility
{
/// <summary>
/// Int16偏移值
/// </summary>
/// ReSharper disable once MemberCanBePrivate.Global
public const int INT16Offset = sizeof(short);
/// <summary>
/// Int32 偏移值
/// </summary>
/// ReSharper disable once MemberCanBePrivate.Global
public const int INT32Offset = sizeof(int);
/// <summary>
/// Int64 偏移值
/// </summary>
/// ReSharper disable once MemberCanBePrivate.Global
public const int INT64Offset = sizeof(long);
#region 编码消息
/// <summary>
/// 序列化 byte 到消息内
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
/// ReSharper disable once MemberCanBePrivate.Global
public static void WriteByte(Stream stream, byte value)
{
stream.WriteByte(value);
}
/// <summary>
/// 序列化 byte 到消息内, 强制转化 int32
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
public static void WriteByte(Stream stream, Int32 value)
{
stream.WriteByte((byte)value);
}
/// <summary>
/// 序列化 bytes 字节流到消息内
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
public static void WriteBytes(Stream stream, byte[] value)
{
WriteInt32(stream, value.Length);
stream.Write(value, 0, value.Length);
}
/// <summary>
/// 序列化 int16 到消息内, 约等于 short
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
/// ReSharper disable once MemberCanBePrivate.Global
public static void WriteInt16(Stream stream, Int16 value)
{
var buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, buffer.Length);
}
/// <summary>
/// 序列化 int32 到消息内
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
/// ReSharper disable once MemberCanBePrivate.Global
public static void WriteInt32(Stream stream, Int32 value)
{
var buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, buffer.Length);
}
/// <summary>
/// 序列化 int64 到消息内, 约等于 long 类型
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
public static void WriteInt64(Stream stream, Int64 value)
{
var buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, buffer.Length);
}
/// <summary>
/// 序列化 string 到消息内, 只允许 utf8 传输
/// </summary>
/// <param name="stream"></param>
/// <param name="value"></param>
/// ReSharper disable once MemberCanBePrivate.Global
public static void WriteString(Stream stream, string value)
{
if (value == null)
{
WriteInt32(stream, 0);
return;
}
var buffer = Encoding.UTF8.GetBytes(value);
var lengthBuffer = BitConverter.GetBytes(buffer.Length);
Array.Reverse(lengthBuffer);
stream.Write(lengthBuffer, 0, lengthBuffer.Length);
stream.Write(buffer, 0, buffer.Length);
}
/// <summary>
/// 写入消息结构体
/// </summary>
/// <param name="stream"></param>
/// <param name="message"></param>
/// ReSharper disable once MemberCanBePrivate.Global
public static void WriteStruct(Stream stream, IMessage message)
{
message.Serialization(stream);
}
#endregion
#region 解码消息
/// <summary>
/// 解码 byte 数据
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
/// ReSharper disable once MemberCanBePrivate.Global
public static byte ReadByte(Stream stream)
{
return (byte)stream.ReadByte();
}
/// <summary>
/// 解码 bytes 字节流数据
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static byte[] ReadBytes(Stream stream)
{
var lengthBuffer = new byte[INT32Offset];
_ = stream.Read(lengthBuffer, 0, INT32Offset);
Array.Reverse(lengthBuffer);
var length = BitConverter.ToInt32(lengthBuffer, INT32Offset);
var buffer = new byte[length];
_ = stream.Read(buffer, 0, length);
return buffer;
}
/// <summary>
/// 解码 int16 数据, 也就是 short
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
/// ReSharper disable once MemberCanBePrivate.Global
public static Int16 ReadInt16(Stream stream)
{
var buffer = new byte[INT16Offset];
_ = stream.Read(buffer, 0, INT16Offset);
Array.Reverse(buffer);
return BitConverter.ToInt16(buffer, 0);
}
/// <summary>
/// 解码 int32 数据
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
/// ReSharper disable once MemberCanBePrivate.Global
public static Int32 ReadInt32(Stream stream)
{
var buffer = new byte[INT32Offset];
_ = stream.Read(buffer, 0, INT32Offset);
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// 通过二进制数据解析读取 int32
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public static Int32 ReadInt32(byte[] buffer)
{
Array.Reverse(buffer);
return BitConverter.ToInt32(buffer, 0);
}
/// <summary>
/// 解码 int64 数据
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static Int64 ReadInt64(Stream stream)
{
var buffer = new byte[INT64Offset];
_ = stream.Read(buffer, 0, INT64Offset);
Array.Reverse(buffer);
return BitConverter.ToInt64(buffer, 0);
}
/// <summary>
/// 解码字符串数据, 只支持 UTF-8
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public static string ReadString(Stream stream)
{
var lengthBuffer = new byte[INT32Offset];
_ = stream.Read(lengthBuffer, 0, INT32Offset);
Array.Reverse(lengthBuffer);
var length = BitConverter.ToInt32(lengthBuffer, 0);
var buffer = new byte[length];
_ = stream.Read(buffer, 0, length);
return Encoding.UTF8.GetString(buffer, 0, length);
}
/// <summary>
/// 解构数据
/// todo: 可以考虑返回 bool 和学习 TryGetValue 之类 out 返回
/// </summary>
/// <param name="stream"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T ReadStruct<T>(Stream stream) where T : IMessage, new()
{
var clazz = new T(); // 动态实例化
clazz.Deserialization(stream);
return clazz;
}
#endregion
#region 快速编码
/// <summary>
/// 将消息在缓冲区当中编码成二进制流
/// </summary>
/// <param name="stream"></param>
/// <param name="message"></param>
/// <returns></returns>
public static byte[] PackBytes(MemoryStream stream,IMessage message)
{
// 重置缓冲区
stream.SetLength(0);
stream.Position = 0;
// 先填充首位Int32, 作为 body 长度占位和协议ID占位
WriteInt32(stream, 0); // 4
WriteInt32(stream, message.GetProtocolId()); // 4
// 结构写入到内存流中
message.Serialization(stream);
// 计算当前写入长度偏移, 只需要 协议id+body 长度
var offset = (int)stream.Position - INT32Offset;
// 设置目前缓冲区指向位置0, 覆盖掉之前占位的消息body长度数据
stream.Position = 0;
WriteInt32(stream, offset);
// 返回消息二进制流
return stream.ToArray();
}
#endregion
}
}