QFramework 除了提供了一套架构之外,QFramework 还提供了可以脱离架构使用的工具 TypeEventSystem、EasyEvent、BindableProperty、IOCContainer。
这些工具并不是有意提供,而是 QFramework 的架构在设计之初是通过这几个工具组合使用而成的。
内置工具
TypeEventSystem
基本用法:
using UnityEngine;
namespace QFramework.Example
{
public class TypeEventSystemBasicExample : MonoBehaviour
{
public struct TestEventA
{
public int Age;
}
private void Start()
{
TypeEventSystem.Global.Register<TestEventA>(e =>
{
Debug.Log(e.Age);
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
private void Update()
{
// 鼠标左键点击
if (Input.GetMouseButtonDown(0))
{
TypeEventSystem.Global.Send(new TestEventA()
{
Age = 18
});
}
// 鼠标右键点击
if (Input.GetMouseButtonDown(1))
{
TypeEventSystem.Global.Send<TestEventA>();
}
}
}
}
// 输出结果
// 点击鼠标左键,则输出:
// 18
// 点击鼠标右键,则输出:
// 0
事件继承支持
除了基本用法,TypeEventSystem 的事件还支持继承关系。
using UnityEngine;
namespace QFramework.Example
{
public class TypeEventSystemInheritEventExample : MonoBehaviour
{
public interface IEventA
{
}
public struct EventB : IEventA
{
}
private void Start()
{
TypeEventSystem.Global.Register<IEventA>(e =>
{
Debug.Log(e.GetType().Name);
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
TypeEventSystem.Global.Send<IEventA>(new EventB());
// 无效
TypeEventSystem.Global.Send<EventB>();
}
}
}
}
// 输出结果:
// 当按下鼠标左键时,输出:
// EventB
手动注销
using UnityEngine;
namespace QFramework.Example
{
public class TypeEventSystemUnRegisterExample : MonoBehaviour
{
public struct EventA
{
}
private void Start()
{
TypeEventSystem.Global.Register<EventA>(OnEventA);
}
void OnEventA(EventA e)
{
}
private void OnDestroy()
{
TypeEventSystem.Global.UnRegister<EventA>(OnEventA);
}
}
}
接口事件
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace QFramework.Example
{
public struct InterfaceEventA
{
}
public struct InterfaceEventB
{
}
public class InterfaceEventModeExample : MonoBehaviour
, IOnEvent<InterfaceEventA>
, IOnEvent<InterfaceEventB>
{
public void OnEvent(InterfaceEventA e)
{
Debug.Log(e.GetType().Name);
}
public void OnEvent(InterfaceEventB e)
{
Debug.Log(e.GetType().Name);
}
private void Start()
{
this.RegisterEvent<InterfaceEventA>()
.UnRegisterWhenGameObjectDestroyed(gameObject);
this.RegisterEvent<InterfaceEventB>();
}
private void OnDestroy()
{
this.UnRegisterEvent<InterfaceEventB>();
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
TypeEventSystem.Global.Send<InterfaceEventA>();
TypeEventSystem.Global.Send<InterfaceEventB>();
}
}
}
}
// 输出结果
// 当按下鼠标左键时,输出:
// InterfaceEventA
// InterfaceEventB
同样接口事件也支持事件之间的继承。
非 MonoBehavior 脚本如何自动销毁
public class NoneMonoScript : IUnRegisterList
{
public List<IUnRegister> UnregisterList { get; } = new List<IUnRegister>();
void Start()
{
TypeEventSystem.Global.Register<EasyEventExample.EventA>(a =>
{
}).AddToUnregisterList(this);
}
void OnDestroy()
{
this.UnRegisterAll();
}
}
如果想手动注销,必须要创建一个用于接收事件的方法。
而用自动注销则直接用委托即可。
这两个各有优劣,按需使用。
另外,事件的定义最好使用 struct,因为 struct 的 gc 更少,可以获得更好的性能。
EasyEvent
TypeEventSystem 是基于 EasyEvent 实现的。
EasyEvent 也是一个可以脱离架构使用的工具。
基本用法:
using UnityEngine;
namespace QFramework.Example
{
public class EasyEventExample : MonoBehaviour
{
private EasyEvent mOnMouseLeftClickEvent = new EasyEvent();
private EasyEvent<int> mOnValueChanged = new EasyEvent<int>();
public class EventA : EasyEvent<int,int> { }
private EventA mEventA = new EventA();
private void Start()
{
mOnMouseLeftClickEvent.Register(() =>
{
Debug.Log("鼠标左键点击");
}).UnRegisterWhenGameObjectDestroyed(gameObject);
mOnValueChanged.Register(value =>
{
Debug.Log($"值变更:{value}");
}).UnRegisterWhenGameObjectDestroyed(gameObject);
mEventA.Register((a, b) =>
{
Debug.Log($"自定义事件:{a} {b}");
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
mOnMouseLeftClickEvent.Trigger();
}
if (Input.GetMouseButtonDown(1))
{
mOnValueChanged.Trigger(10);
}
// 鼠标中键
if (Input.GetMouseButtonDown(2))
{
mEventA.Trigger(1,2);
}
}
}
}
// 输出结果:
// 按鼠标左键时,输出:
// 鼠标左键点击
// 按鼠标右键时,输出:
// 值变更:10
// 按鼠标中键时,输出:
// 自定义事件:1 2
EasyEvent 最多支持三个泛型。
EasyEvent 的优势
EasyEvent 是 C# 委托和事件的替代。
EasyEvent 相比 C# 委托和事件,优势是可以自动注销。
相比 TypeEventSystem,优势是更轻量,大多数情况下不用声明事件类,而且性能更好(接近 C# 委托)。
缺点则是其携带的参数没有名字,需要自己定义名字。
在设计一些通用系统的时候,EasyEvent 会派上用场,比如背包系统、对话系统,TypeEventSystem 是一个非常好的例子。
BindableProperty
BindableProperty 提供 数据 + 数据变更事件 的一个对象。
基本用法:
var age = new BindableProperty<int>(10);
age.Register(newAge=>{
Debug.Log(newAge)
}).UnRegisterWhenGameObjectDestoryed(gameObject);
age++;
age--;
// 输出结果
// 11
// 10
就是当调用 age++ 和 age-- 的时候,就会触发数据变更事件。
BindableProperty 除了提供 Register 这个 API 之外,还提供了 RegisterWithInitValue API,意思是 注册时 先把当前值返回过来。
具体用法如下:
var age = new BindableProperty<int>(5);
age.RegisterWithInitValue(newAge => {
Debug.Log(newAge);
});
// 输出结果
// 5
这个 API 就是,没有任何变化的情况下,age 先返回一个当前的值,比较方便用于显示初始界面。
使用 BindableProperty 优化 CounterApp 的代码
using UnityEngine;
using UnityEngine.UI;
namespace QFramework.Example
{
// 1. 定义一个 Model 对象
public class CounterAppModel : AbstractModel
{
public BindableProperty<int> Count { get; } = new BindableProperty<int>();
protected override void OnInit()
{
var storage = this.GetUtility<Storage>();
// 设置初始值(不触发事件)
Count.SetValueWithoutEvent(storage.LoadInt(nameof(Count)));
// 当数据变更时 存储数据
Count.Register(newCount =>
{
storage.SaveInt(nameof(Count),newCount);
});
}
}
public class AchievementSystem : AbstractSystem
{
protected override void OnInit()
{
this.GetModel<CounterAppModel>() // -+
.Count
.Register(newCount =>
{
if (newCount == 10)
{
Debug.Log("触发 点击达人 成就");
}
else if (newCount == 20)
{
Debug.Log("触发 点击专家 成就");
}
else if (newCount == -10)
{
Debug.Log("触发 点击菜鸟 成就");
}
});
}
}
// 定义 utility 层
public class Storage : IUtility
{
public void SaveInt(string key, int value)
{
PlayerPrefs.SetInt(key,value);
}
public int LoadInt(string key, int defaultValue = 0)
{
return PlayerPrefs.GetInt(key, defaultValue);
}
}
// 2.定义一个架构(提供 MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册 System
this.RegisterSystem(new AchievementSystem()); // +
// 注册 Model
this.RegisterModel(new CounterAppModel());
// 注册存储工具的对象
this.RegisterUtility(new Storage());
}
}
// 引入 Command
public class IncreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
var model = this.GetModel<CounterAppModel>();
model.Count.Value++; // -+
}
}
public class DecreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count.Value--; // -+
}
}
// Controller
public class CounterAppController : MonoBehaviour , IController /* 3.实现 IController 接口 */
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;
// 4. Model
private CounterAppModel mModel;
void Start()
{
// 5. 获取模型
mModel = this.GetModel<CounterAppModel>();
// View 组件获取
mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
mCountText = transform.Find("CountText").GetComponent<Text>();
// 监听输入
mBtnAdd.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<IncreaseCountCommand>();
});
mBtnSub.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand(new DecreaseCountCommand(/* 这里可以传参(如果有) */));
});
// 表现逻辑
mModel.Count.RegisterWithInitValue(newCount => // -+
{
UpdateView();
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
void UpdateView()
{
mCountText.text = mModel.Count.ToString();
}
// 3.
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}
private void OnDestroy()
{
// 8. 将 Model 设置为空
mModel = null;
}
}
}
- Model 中的 Count 和 mCount 改成了一个叫做 Count 的 BindableProperty
- 删掉了 CountChangeEvent 改用监听 BindableProperty
- Controller 在初始化中去掉一次 UpdateView 的主动调用
由于 Count 数据是单个数据 + 事件变更的形式,所以用 BindableProperty 非常合适,可以少写很多代码。
一般情况下,像主角的金币、分数等数据非常适合用 BindableProperty 的方式实现。
IOCContainer
QFramework 架构的模块注册与获取是通过 IOCContainer 实现的。
IOC 的意思是控制反转,即控制反转容器。
其技术的本质很简单,本质就是一个字典,Key 是 Type,Value 是 Object,即:Dictionary<Type,object>。
QFramework 架构中的 IOCContainer 是一个非常简易版本的控制翻转容器,仅支持了注册对象为单例的模式。
基本使用:
using System;
using UnityEngine;
namespace QFramework.Example
{
public class IOCContainerExample : MonoBehaviour
{
public class SomeService
{
public void Say()
{
Debug.Log("SomeService Say Hi");
}
}
public interface INetworkService
{
void Connect();
}
public class NetworkService : INetworkService
{
public void Connect()
{
Debug.Log("NetworkService Connect Succeed");
}
}
private void Start()
{
var container = new IOCContainer();
container.Register(new SomeService());
container.Register<INetworkService>(new NetworkService());
container.Get<SomeService>().Say();
container.Get<INetworkService>().Connect();
}
}
}
// 输出结果:
// SomeService Say Hi
// NetworkService Connect Succeed
使用 IOCContainer 更容易设计出符合依赖倒置原则的模块。
而 QFramework 架构的用接口设计模块的支持就是通过 IOCContainer 支持的,同样使用 IOCContainer 也更容易设计出分层的架构。