MeteorCat / QFramework自定义扩展

Created Sat, 09 Nov 2024 21:35:56 +0800 Modified Wed, 29 Oct 2025 23:25:05 +0800
3997 Words

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
    }

}