【Unity框架】QFramework示例
聪头 游戏开发萌新

QFramework示例

QFramework源码示例

0.计数器

image

完整分析

以下顺序是按照源码顺序分析,实际开发时编写顺序可根据自身需要调整

1.定义Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1.定义计数模型 (Model)
public interface ICounterAppModel : IModel
{
BindableProperty<int> Count { get; }
}
public class CounterAppModel : AbstractModel,ICounterAppModel
{
public BindableProperty<int> Count { get; } = new BindableProperty<int>();

protected override void OnInit()
{
var storage = this.GetUtility<IStorage>();

// 设置初始值(不触发事件)
Count.SetValueWithoutEvent(storage.LoadInt(nameof(Count)));

// 当数据变更时 存储数据
Count.Register(newCount =>
{
storage.SaveInt(nameof(Count),newCount);
});
}
}

2.定义System

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 2.定义成就系统 (System)
public interface IAchievementSystem : ISystem
{

}

public class AchievementSystem : AbstractSystem ,IAchievementSystem
{
protected override void OnInit()
{
this.GetModel<ICounterAppModel>() // -+
.Count
.Register(newCount =>
{
if (newCount == 10)
{
Debug.Log("触发 点击达人 成就");
}
else if (newCount == 20)
{
Debug.Log("触发 点击专家 成就");
}
else if (newCount == -10)
{
Debug.Log("触发 点击菜鸟 成就");
}
});
}
}

3.定义Utility

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 3.定义存储工具 (Utility)
public interface IStorage : IUtility
{
void SaveInt(string key, int value);
int LoadInt(string key, int defaultValue = 0);
}

public class Storage : IStorage
{
public void SaveInt(string key, int value)
{
PlayerPrefs.SetInt(key,value);
}

public int LoadInt(string key, int defaultValue = 0)
{
return PlayerPrefs.GetInt(key, defaultValue);
}
}

4.定义Architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 4.定义一个架构(提供 MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册 System
this.RegisterSystem<IAchievementSystem>(new AchievementSystem());

// 注册 Model
this.RegisterModel<ICounterAppModel>(new CounterAppModel());

// 注册存储工具的对象
this.RegisterUtility<IStorage>(new Storage());
}


protected override void ExecuteCommand(ICommand command)
{
Debug.Log("Before " + command.GetType().Name + "Execute");
base.ExecuteCommand(command);
Debug.Log("After " + command.GetType().Name + "Execute");
}

protected override TResult ExecuteCommand<TResult>(ICommand<TResult> command)
{
Debug.Log("Before " + command.GetType().Name + "Execute");
var result = base.ExecuteCommand(command);
Debug.Log("After " + command.GetType().Name + "Execute");
return result;
}
}

5.引入Command

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 5.引入 Command
public class IncreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
var model = this.GetModel<ICounterAppModel>();

model.Count.Value++;
}
}

public class DecreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
this.GetModel<ICounterAppModel>().Count.Value--; // -+
}
}

6.编写Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 6.Controller
public class CounterAppController : MonoBehaviour , IController /* 3.实现 IController 接口 */
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;

// Model
private ICounterAppModel mModel;

void Start()
{
// 获取模型
mModel = this.GetModel<ICounterAppModel>();

// View 组件获取 (使用GenCodeKit可以避免手动查找)
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();
}

// 实现接口
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}

private void OnDestroy()
{
// 将 Model 设置为空
mModel = null;
}
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
using UnityEngine;
using UnityEngine.UI;

namespace QFramework.Example
{

// 1.定义计数模型 (Model)
public interface ICounterAppModel : IModel
{
BindableProperty<int> Count { get; }
}
public class CounterAppModel : AbstractModel,ICounterAppModel
{
public BindableProperty<int> Count { get; } = new BindableProperty<int>();

protected override void OnInit()
{
var storage = this.GetUtility<IStorage>();

// 设置初始值(不触发事件)
Count.SetValueWithoutEvent(storage.LoadInt(nameof(Count)));

// 当数据变更时 存储数据
Count.Register(newCount =>
{
storage.SaveInt(nameof(Count),newCount);
});
}
}

// 2.定义成就系统 (System)
public interface IAchievementSystem : ISystem
{

}

public class AchievementSystem : AbstractSystem ,IAchievementSystem
{
protected override void OnInit()
{
this.GetModel<ICounterAppModel>() // -+
.Count
.Register(newCount =>
{
if (newCount == 10)
{
Debug.Log("触发 点击达人 成就");
}
else if (newCount == 20)
{
Debug.Log("触发 点击专家 成就");
}
else if (newCount == -10)
{
Debug.Log("触发 点击菜鸟 成就");
}
});
}
}

// 3.定义存储工具 (Utility)
public interface IStorage : IUtility
{
void SaveInt(string key, int value);
int LoadInt(string key, int defaultValue = 0);
}

public class Storage : IStorage
{
public void SaveInt(string key, int value)
{
PlayerPrefs.SetInt(key,value);
}

public int LoadInt(string key, int defaultValue = 0)
{
return PlayerPrefs.GetInt(key, defaultValue);
}
}


// 4.定义一个架构(提供 MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册 System
this.RegisterSystem<IAchievementSystem>(new AchievementSystem());

// 注册 Model
this.RegisterModel<ICounterAppModel>(new CounterAppModel());

// 注册存储工具的对象
this.RegisterUtility<IStorage>(new Storage());
}


protected override void ExecuteCommand(ICommand command)
{
Debug.Log("Before " + command.GetType().Name + "Execute");
base.ExecuteCommand(command);
Debug.Log("After " + command.GetType().Name + "Execute");
}

protected override TResult ExecuteCommand<TResult>(ICommand<TResult> command)
{
Debug.Log("Before " + command.GetType().Name + "Execute");
var result = base.ExecuteCommand(command);
Debug.Log("After " + command.GetType().Name + "Execute");
return result;
}
}

// 5.引入 Command
public class IncreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
var model = this.GetModel<ICounterAppModel>();

model.Count.Value++;
}
}

public class DecreaseCountCommand : AbstractCommand
{
protected override void OnExecute()
{
this.GetModel<ICounterAppModel>().Count.Value--; // -+
}
}

// 6.Controller
public class CounterAppController : MonoBehaviour , IController /* 3.实现 IController 接口 */
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;

// Model
private ICounterAppModel mModel;

void Start()
{
// 获取模型
mModel = this.GetModel<ICounterAppModel>();

// View 组件获取 (使用GenCodeKit可以避免手动查找)
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();
}

// 实现接口
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}

private void OnDestroy()
{
// 将 Model 设置为空
mModel = null;
}
}
}

1.查询示例

image

重点分析

定义两个Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class StudentModel : AbstractModel
{

public List<string> StudentNames = new List<string>()
{
"张三",
"李四"
};

protected override void OnInit()
{

}
}

public class TeacherModel : AbstractModel
{
public List<string> TeacherNames = new List<string>()
{
"王五",
"赵六"
};

protected override void OnInit()
{

}
}

Query对象

Query对象查询两个Model,返回结果

1
2
3
4
5
6
7
8
9
10
11
/// <summary>
/// 获取学校的全部人数
/// </summary>
public class SchoolAllPersonCountQuery : AbstractQuery<int>
{
protected override int OnDo()
{
return this.GetModel<StudentModel>().StudentNames.Count +
this.GetModel<TeacherModel>().TeacherNames.Count;
}
}

调用Query

1
mAllPersonCount = this.SendQuery(new SchoolAllPersonCountQuery());

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using System.Collections.Generic;
using UnityEngine;

namespace QFramework.Example
{
public class QueryExampleController : MonoBehaviour,IController
{
public class StudentModel : AbstractModel
{

public List<string> StudentNames = new List<string>()
{
"张三",
"李四"
};

protected override void OnInit()
{

}
}

public class TeacherModel : AbstractModel
{
public List<string> TeacherNames = new List<string>()
{
"王五",
"赵六"
};

protected override void OnInit()
{

}
}

// Architecture
public class QueryExampleApp : Architecture<QueryExampleApp>
{
protected override void Init()
{
this.RegisterModel(new StudentModel());
this.RegisterModel(new TeacherModel());
}
}


/// <summary>
/// 获取学校的全部人数
/// </summary>
public class SchoolAllPersonCountQuery : AbstractQuery<int>
{
protected override int OnDo()
{
return this.GetModel<StudentModel>().StudentNames.Count +
this.GetModel<TeacherModel>().TeacherNames.Count;
}
}

private int mAllPersonCount = 0;

private void OnGUI()
{
GUILayout.Label(mAllPersonCount.ToString());

if (GUILayout.Button("查询学校总人数"))
{
mAllPersonCount = this.SendQuery(new SchoolAllPersonCountQuery());
}
}

public IArchitecture GetArchitecture()
{
return QueryExampleApp.Interface;
}
}
}

2.类型事件系统

1) 事件绑定与自动解绑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using UnityEngine;

namespace QFramework.Example
{
public class TypeEventSystemBasicExample : MonoBehaviour
{
public struct TestEventA
{
public int Age;
}

private void Start()
{
// 注册事件:底层会封装成EasyEvent<TestEventA>类型(装饰器模式)
TypeEventSystem.Global.Register<TestEventA>(e =>
{
Debug.Log(e.Age);
}).UnRegisterWhenGameObjectDestroyed(gameObject); //Gameobject销毁时解绑事件,本质是添加一个Mono
}

private void Update()
{
// 鼠标左键点击
if (Input.GetMouseButtonDown(0))
{
// 发送事件(含参)
TypeEventSystem.Global.Send(new TestEventA()
{
Age = 18
});
}

// 鼠标右键点击
if (Input.GetMouseButtonDown(1))
{
// 发送事件(不含参):底层自动会调用无参构造
TypeEventSystem.Global.Send<TestEventA>();
}
}
}
}

2) 继承事件绑定

注意:如果注册时的泛型使用接口/父类,在发送时也需要使用接口/父类,只是传入的参数可以是 实现类/子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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>();
}
}
}
}

3) 事件手动解绑

1.调用UnRegister可以手动解绑

2.将注册的事件绑定到UnregisterList,调用UnRegisterAll()即可实现一键手动解绑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System.Collections.Generic;
using UnityEngine;

namespace QFramework.Example
{
public class TypeEventSystemUnRegisterExample : MonoBehaviour
{

public struct EventA
{

}

public struct EventB
{

}

private void Start()
{
TypeEventSystem.Global.Register<EventA>(OnEventA);
TypeEventSystem.Global.Register<EventB>(b => { }).UnRegisterWhenGameObjectDestroyed(this);
}

void OnEventA(EventA e)
{

}

private void OnDestroy()
{
TypeEventSystem.Global.UnRegister<EventA>(OnEventA);
}


public class NoneMonoScript : IUnRegisterList
{
public List<IUnRegister> UnregisterList { get; } = new List<IUnRegister>();


void Start()
{
TypeEventSystem.Global.Register<EventA>(a =>
{

}).AddToUnregisterList(this);


}

void OnDestroy()
{
this.UnRegisterAll();
}
}
}
}

4) 实现事件接口

通过实现接口的方式实现事件绑定。概念冗余,不推荐。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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>();
}
}
}
}

3.简单事件

EasyEvent是类型事件系统的底层实现,本质就是对C# Action委托的扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using UnityEngine;

namespace QFramework.Example
{
public class EasyEventExample : MonoBehaviour
{
// 无参事件
private EasyEvent mOnMouseLeftClickEvent = new EasyEvent();

// int参数事件
private EasyEvent<int> mOnValueChanged = new EasyEvent<int>();

// int, 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);
}
}
}
}

4.BindableProperty

image

绑定属性,实现值修改触发事件的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using UnityEngine;

namespace QFramework.Example
{
public class BindablePropertyExample : MonoBehaviour
{
private BindableProperty<int> mSomeValue = new BindableProperty<int>(0);

private BindableProperty<string> mName = new BindableProperty<string>("QFramework");

void Start()
{
// 绑定事件,不触发事件
mSomeValue.Register(newValue =>
{
Debug.Log(newValue);
}).UnRegisterWhenGameObjectDestroyed(gameObject);


// 绑定事件,触发事件
mName.RegisterWithInitValue(newName =>
{
Debug.Log(mName);
}).UnRegisterWhenGameObjectDestroyed(gameObject);

// 修改值但不触发事件
mSomeValue.SetValueWithoutEvent(10);
}

void Update()
{
if (Input.GetMouseButtonDown(0))
{
mSomeValue.Value++; //第一次输出11
}
}
}
}

5.IOC容器

提供对象注册到IOC容器的操作

作者认为注册到容器的对象不可被销毁,这样在调用时确保安全性。本人在这做了一些修改,为容器引入Release方法,能够在需要时销毁容器内对象(大型项目可能需要)。此操作风险与收益并存,需要慎用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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();

}
}
}

6.不使用架构

如果熟练到一定程度,即使不使用架构约束也能写出架构程度的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
using System;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework.Example
{
public class ArchitectureInHeartExample : MonoBehaviour
{

#region Framework

public interface ICommand
{
void Execute();
}

public class BindableProperty<T>
{
private T mValue = default;

public T Value
{
get => mValue;
set
{
if (mValue != null && mValue.Equals(value)) return;
mValue = value;
OnValueChanged?.Invoke(mValue);
}
}

public event Action<T> OnValueChanged = _ => { };
}

#endregion


#region 定义 Model

public static class CounterModel
{
public static BindableProperty<int> Counter = new BindableProperty<int>()
{
Value = 0
};
}

#endregion

#region 定义 Command
public struct IncreaseCountCommand : ICommand
{
public void Execute()
{
CounterModel.Counter.Value++;
}
}

public struct DecreaseCountCommand : ICommand
{
public void Execute()
{
CounterModel.Counter.Value--;
}
}
#endregion


private void OnGUI()
{
if (GUILayout.Button("+"))
{
new IncreaseCountCommand().Execute();
}

GUILayout.Label(CounterModel.Counter.Value.ToString());

if (GUILayout.Button("-"))
{
new DecreaseCountCommand().Execute();
}
}
}
}

7-11 实战项目

篇幅有限,详见源码

12.协程&异步&带返回值的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using System;
using System.Collections;
using System.Threading.Tasks;
using UnityEngine;

namespace QFramework.Example
{
public class CommandWithResultExample : MonoBehaviour
{
public class ExampleArchitecture : Architecture<ExampleArchitecture>
{
protected override void Init()
{

}
}

// 同步命令
public class SimpleResultCommand : AbstractCommand<string>
{
protected override string OnExecute()
{
return "Hello Command With Result";
}
}

// 协程命令
public class ACoroutineCommand : AbstractCommand<IEnumerator>
{
protected override IEnumerator OnExecute()
{
Debug.Log("ACoroutineCommandStart:" + Time.time);
yield return new WaitForSeconds(1.0f);
Debug.Log("ACoroutineCommandFinish:" + Time.time);
}
}

// 异步命令
public class TaskACommand : AbstractCommand<Task<bool>>
{
protected override async Task<bool> OnExecute()
{
await Task.Delay(TimeSpan.FromSeconds(2.0f));
return true;
}
}

void Start()
{
Debug.Log(ExampleArchitecture.Interface.SendCommand(new SimpleResultCommand()));
StartCoroutine(ExampleArchitecture.Interface.SendCommand(new ACoroutineCommand()));
SendTaskACommand();
}

async void SendTaskACommand()
{

var result = await ExampleArchitecture.Interface.SendCommand(new TaskACommand());
Debug.Log("ATaskCommandResult:" + result + " time:" + Time.time);
}
}
}

13.Or事件

将一个事件同时注册到多个BIndableProperty或EasyEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework.Example
{
public class OrEventExample : MonoBehaviour
{
private BindableProperty<int> mPropertyA = new BindableProperty<int>(10);
private BindableProperty<int> mPropertyB = new BindableProperty<int>(5);

private EasyEvent EventA = new EasyEvent();

// Start is called before the first frame update
void Start()
{
mPropertyA
.Or(EventA)
.Or(mPropertyB)
.Register(() =>
{
Debug.Log("Event Received");
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}

private void Update()
{
if (Input.GetMouseButtonDown(0))
{
mPropertyA.Value++;
}

if (Input.GetMouseButtonDown(1))
{
mPropertyB.Value++;
}

if (Input.GetKeyDown(KeyCode.Space))
{
EventA.Trigger();
}
}
}
}

14.释放资源 [自定义]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework
{
public class ModelA : AbstractModel
{
public int data = 100;
protected override void OnInit()
{
Debug.Log("ModelA: OnInit()");
}

protected override void OnRelease()
{
base.OnRelease();
Debug.Log("ModelA: OnRelease()");
}
}

public class ArchitectureA : Architecture<ArchitectureA>
{
protected override void Init()
{
Debug.Log("ArchitectureA: Init!");
RegisterModel(new ModelA());
}
}


public class ReleaseExample : MonoBehaviour
{
private void Start()
{
var modelA = ArchitectureA.Interface.GetModel<ModelA>();
if (modelA != null)
{
Debug.Log("Init data: " + modelA.data); //100
modelA.data = 888;
Debug.Log("Change data: " + modelA.data); //888
}

ArchitectureA.Interface.Release(); //后续再调用ArchitectureA.Interface又会重新初始化

modelA = ArchitectureA.Interface.GetModel<ModelA>();

if (modelA != null)
{
Debug.Log("data: " + modelA.data); //100
}
}
}
}

QFramework 工具集示例

1.ActionKit

Start执行时不会阻塞当前代码

源码分析

ActionKit大致工作过程(以Delay举例):

  • 调用Action.Delay函数,传入延迟时间和时间结束的回调函数。底层会从==缓存池内申请==一个Delay对象,做一些初始化工作并绑定上DelayTimeOnDelayFinish。最后返回Delay Action。
  • 调用Start函数,传入Monobehaviour对象(以下简称Mono)和整个Action执行结束的回调函数onFinish。函数会在Mono上添加或获取MonoUpdateActionExecutor脚本
  • 紧接着调用MonoUpdateActionExecutorExecute函数,以Action作为KeyonFinish作为Value生成Key-Value Pair添加到mPrepareExecutionActions列表中(这里不直接使用字典,说明重复添加同一事件是会累加的)。
  • MonoUpdateActionExecutor脚本的Update里,每帧会判断mPrepareExecutionActions列表是否有元素,如果有就取出放到mExecutingActions字典中。(源码中出现重复Key会直接用Value覆盖,但是Key不大可能重复)接着遍历mExecutingActions字典,执行每个动作的Execute函数
  • Execute函数本质是一个if-else状态机,分为NotStart、Started、Finished。返回true代表动作执行结束,返回false代表动作未完成。其中Started->Finished的过程通常根据需求决定。下面是状态机大致工作顺序:
    • 调用OnStart,从NotStart->Started,返回false
    • 调用OnExecute,返回false。
    • 直到调用Finish函数,从Started->Finished,立刻调用OnFinish,返回true
  • 调用onFinish回调函数,通知动作结束。调用动作的Deinit函数,将动作先托管给ActionQueue(Monobehaviour单例),由ActionQueue将动作==放回缓存池==。(猜测作者是确保执行过程互不交叉,每个阶段做完所有该阶段的任务后再进入下一个阶段)
    • 放回缓存池前还可以调用mResetMethod事件来初始化对象(本例不需要,因为Delay对象每次取出会做一次完整的初始化)

0) Delay

延迟1s后触发回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;

namespace QFramework.Example
{
public class DelayExample : MonoBehaviour
{
void Start()
{
Debug.Log("Start Time:" + Time.time);

ActionKit.Delay(1.0f, () =>
{
Debug.Log("End Time:" + Time.time);

}).Start(this);
}
}
}

Actionkit底层分析(Delay)

QFramework.Actionkit底层分析-Delay

详见:Mindmaster

1) Sequence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;

namespace QFramework.Example
{
public class SequenceAndCallback : MonoBehaviour
{
void Start()
{
Debug.Log("Sequence Start:" + Time.time);

ActionKit.Sequence()
.Callback(() => Debug.Log("Delay Start:" + Time.time))
.Delay(1.0f)
.Callback(() => Debug.Log("Delay Finish:" + Time.time))
.Start(this, _ => { Debug.Log("Sequence Finish:" + Time.time); });
}
}
}

2) DelayFrame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using UnityEngine;

namespace QFramework.Example
{
public class DelayFrameExample : MonoBehaviour
{
void Start()
{
Debug.Log("Delay Frame Start FrameCount:" + Time.frameCount);

ActionKit.DelayFrame(1, () => { Debug.Log("Delay Frame Finish FrameCount:" + Time.frameCount); })
.Start(this);


ActionKit.Sequence()
.DelayFrame(10)
.Callback(() => Debug.Log("Sequence Delay FrameCount:" + Time.frameCount))
.Start(this);

// ActionKit.Sequence()
// .NextFrame()
// .Start(this);

ActionKit.NextFrame(() => { }).Start(this);
}
}
}

3) Condition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using UnityEngine;

namespace QFramework.Example
{
public class ConditionExample : MonoBehaviour
{
private void Start()
{
ActionKit.Sequence()
.Callback(() => Debug.Log("Before Condition"))
.Condition(() => Input.GetMouseButtonDown(0))
.Callback(() => Debug.Log("Mouse Clicked"))
.Start(this);
}
}
}

4) Repeat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using UnityEngine;

namespace QFramework.Example
{
public class RepeatExample : MonoBehaviour
{
private void Start()
{
ActionKit.Repeat()
.Condition(() => Input.GetMouseButtonDown(0))
.Callback(() => Debug.Log("Mouse Clicked"))
.Start(this);


ActionKit.Repeat(5)
.Condition(() => Input.GetMouseButtonDown(1))
.Callback(() => Debug.Log("Mouse right clicked"))
.Start(this, () =>
{
Debug.Log("Right click finished");

ActionKit.Repeat(5)
.Condition(() => Input.GetMouseButtonDown(1))
.Callback(() => Debug.Log("Mouse right clicked"))
.Start(this, () =>
{
Debug.Log("Right click finished");
});
});
}
}
}

5) Parallel

只有全部并行任务完成后才会继续执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEngine;

namespace QFramework.Example
{
public class ParallelExample : MonoBehaviour
{
void Start()
{
Debug.Log("Parallel Start:" + Time.time);

ActionKit.Parallel()
.Delay(1.0f, () => { Debug.Log(Time.time); })
.Delay(2.0f, () => { Debug.Log(Time.time); })
.Delay(3.0f, () => { Debug.Log(Time.time); })
.Start(this, () =>
{
Debug.Log("Parallel Finish:" + Time.time);
});
}
}
}

6) 综合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using UnityEngine;

namespace QFramework.Example
{
public class ComplexExample : MonoBehaviour
{
private void Start()
{
ActionKit.Sequence()
.Callback(() => Debug.Log("Sequence Start"))
.Callback(() => Debug.Log("Parallel Start"))
.Parallel(p =>
{
p.Delay(1.0f, () => Debug.Log("Delay 1s Finished"))
.Delay(2.0f, () => Debug.Log("Delay 2s Finished"));
})
.Callback(() => Debug.Log("Parallel Finished"))
.Callback(() => Debug.Log("Check Mouse Clicked"))
.Sequence(s =>
{
s.Condition(() => Input.GetMouseButton(0))
.Callback(() => Debug.Log("Mouse Clicked"));
})
.Start(this, () =>
{
Debug.Log("Finish");

});
}
}
}

7) Custom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using UnityEngine;

namespace QFramework.Example
{
public class CustomExample : MonoBehaviour
{
class SomeData
{
public int ExecuteCount = 0;
}

private void Start()
{
ActionKit.Custom(a =>
{
a
.OnStart(() => { Debug.Log("OnStart"); })
.OnExecute(dt =>
{
Debug.Log("OnExecute");

a.Finish();
})
.OnFinish(() => { Debug.Log("OnFinish"); });
}).Start(this);

// OnStart
// OnExecute
// OnFinish

ActionKit.Custom<SomeData>(a =>
{
a
.OnStart(() =>
{
a.Data = new SomeData()
{
ExecuteCount = 0
};
})
.OnExecute(dt =>
{
Debug.Log(a.Data.ExecuteCount);
a.Data.ExecuteCount++;

if (a.Data.ExecuteCount >= 5)
{
a.Finish();
}
}).OnFinish(() => { Debug.Log("Finished"); });
})
.Start(this);

// 0
// 1
// 2
// 3
// 4
// Finished

// 还支持 Sequence、Repeat、Spawn 等
// Also support sequence repeat spawn
// ActionKit.Sequence()
// .Custom(c =>
// {
// c.OnStart(() => c.Finish());
// }).Start(this);
}
}
}

Actionkit底层分析(Custom)

QFramework.Actionkit底层分析-Custom

详见:Mindmaster

8) Coroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System.Collections;
using UnityEngine;

namespace QFramework.Example
{
public class CoroutineExample : MonoBehaviour
{
private void Start()
{
ActionKit.Coroutine(SomeCoroutine).Start(this);

SomeCoroutine().ToAction().Start(this);

ActionKit.Sequence()
.Coroutine(SomeCoroutine)
.Start(this);
}

IEnumerator SomeCoroutine()
{
yield return new WaitForSeconds(1.0f);
Debug.Log("Hello:" + Time.time);
}
}
}

9) Unity生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using UnityEngine;

namespace QFramework.Example
{
public class GlobalMonoEventsExample : MonoBehaviour
{
void Start()
{
ActionKit.OnUpdate.Register(() =>
{
if (Time.frameCount % 30 == 0)
{
Debug.Log("Update");
}
}).UnRegisterWhenGameObjectDestroyed(gameObject);

ActionKit.OnFixedUpdate.Register(() =>
{
// fixed update code here
// 这里写 fixed update 相关代码
}).UnRegisterWhenGameObjectDestroyed(gameObject);

ActionKit.OnLateUpdate.Register(() =>
{
// late update code here
// 这里写 late update 相关代码
}).UnRegisterWhenGameObjectDestroyed(gameObject);

ActionKit.OnGUI.Register(() =>
{
GUILayout.Label("See Example Code");
GUILayout.Label("请查看示例代码");
}).UnRegisterWhenGameObjectDestroyed(gameObject);

ActionKit.OnApplicationFocus.Register(focus =>
{
Debug.Log("focus:" + focus);
}).UnRegisterWhenGameObjectDestroyed(gameObject);

ActionKit.OnApplicationPause.Register(pause =>
{
Debug.Log("pause:" + pause);
}).UnRegisterWhenGameObjectDestroyed(gameObject);

ActionKit.OnApplicationQuit.Register(() =>
{
Debug.Log("quit");
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
}
}

10) DOTween

直接将DOTween写在Callback里也一样

11) UniRx

12) RandomDelay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace QFramework.Example
{
public class RandomDelayExample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
ActionKit.Repeat()
.Delay(() => Random.Range(0.5f, 2.5f))
.Callback(() => { Debug.Log("Time:" + Time.time); })
.Start(this);
}

// Update is called once per frame
void Update()
{
}
}
}

13) StartGlobal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework.Example
{
public class StartGlobalExample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

var action = ActionKit.Repeat()
.Delay(1.0f)
.Callback(() => Debug.Log("wait 1.0f"))
.StartGlobal();

// action.Pause();
// action.Resume();
// action.Deinit(); // Stop
}

// Update is called once per frame
void Update()
{

}
}
}

14) PauseResume

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


namespace QFramework.Example
{
public class PauseResumeExample : MonoBehaviour
{
// Start is called before the first frame update
IEnumerator Start()
{
// NOT Support Yet
var action = ActionKit.Repeat()
.Delay(0.5f)
.Callback(() => Debug.Log(Time.time))
.Start(this);

yield return new WaitForSeconds(3.0f);
Debug.Log("Pause");
action.Pause();

yield return new WaitForSeconds(2.0f);
Debug.Log("Restart");
action.Resume();

}
}
}

15) Lerp (用DOTween)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework.Example
{
public class CubeLerpExample : MonoBehaviour
{
void Start()
{
ActionKit.Lerp(0, 360, 5.0f, (v) =>
{
this.Rotation(Quaternion.Euler(0, 0, v));
}).Start(this);

}

void Update()
{

}
}
}

16) StopManagement

17) LerpWithEase (用DOTween)

2.BinablePropertyKit

1) EditorPrefsBoolProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/****************************************************************************
* Copyright (c) 2016 - 2022 liangxiegame UNDER MIT License
*
* https://qframework.cn
* https://github.com/liangxiegame/QFramework
* https://gitee.com/liangxiegame/QFramework
****************************************************************************/

#if UNITY_EDITOR
using UnityEditor;

namespace QFramework
{

public class EditorPrefsBoolProperty : BindableProperty<bool>
{

public EditorPrefsBoolProperty(string key, bool initValue = false)
{
// 初始化
mValue = EditorPrefs.GetBool(key, initValue);

Register(value => { EditorPrefs.SetBool(key, value); });
}
}
}
#endif

2) PlayerPrefsBoolProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/****************************************************************************
* Copyright (c) 2016 - 2022 liangxiegame UNDER MIT License
*
* https://qframework.cn
* https://github.com/liangxiegame/QFramework
* https://gitee.com/liangxiegame/QFramework
****************************************************************************/

using UnityEngine;

namespace QFramework
{
public class PlayerPrefsBooleanProperty : BindableProperty<bool>
{
public PlayerPrefsBooleanProperty(string saveKey, bool defaultValue = false)
{
mValue = PlayerPrefs.GetInt(saveKey, defaultValue ? 1 : 0) == 1;

this.Register(value => PlayerPrefs.SetInt(saveKey, value ? 1 : 0));
}
}
}

3) PlayerPrefsFloatProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/****************************************************************************
* Copyright (c) 2016 - 2022 liangxiegame UNDER MIT License
*
* https://qframework.cn
* https://github.com/liangxiegame/QFramework
* https://gitee.com/liangxiegame/QFramework
****************************************************************************/

using UnityEngine;

namespace QFramework
{
public class PlayerPrefsFloatProperty : BindableProperty<float>
{
public PlayerPrefsFloatProperty(string saveKey, float defaultValue = 0.0f)
{
mValue = PlayerPrefs.GetFloat(saveKey, defaultValue);

this.Register(value => PlayerPrefs.SetFloat(saveKey, value));
}
}
}

3.CodeGenKit

步骤:

  1. 使用Alt+V或手动添加ViewController脚本
  2. 为需要绑定的对象添加Bind脚本(添加Bind脚本的对象会在生成代码时,自动赋值到最近的ViewController生成的脚本中)
  3. 也可以为ViewController脚本所在对象添加OtherBinds脚本,手动赋值

4.EventKit

1) EasyEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using UnityEngine;

namespace QFramework.Example
{
public class EasyEventExample : MonoBehaviour
{
private EasyEvent<string> mSomeStringEvent = new EasyEvent<string>();

private void Awake()
{
mSomeStringEvent.Register(str =>
{
Debug.Log(str);
}).UnRegisterWhenGameObjectDestroyed(gameObject);

EasyEvents.Register<EasyEvent<int>>();
EasyEvents.Register<MyEvent>();

EasyEvents.Get<EasyEvent<int>>().Register(number =>
{
Debug.Log(number);
}).UnRegisterWhenGameObjectDestroyed(gameObject);

EasyEvents.Get<MyEvent>().Register((str1,str2)=>
{
Debug.Log(str1 +":" + str2);
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}

private void Update()
{
if (Input.GetMouseButtonDown(0))
{
mSomeStringEvent.Trigger("Hello World");
}

if (Input.GetMouseButtonDown(1))
{
EasyEvents.Get<EasyEvent<int>>().Trigger(123);
}

if (Input.GetKeyDown(KeyCode.Space))
{
EasyEvents.Get<MyEvent>().Trigger("你好","EasyEvent");
}
}

public class MyEvent : EasyEvent<string,string>
{

}
}
}

2) EnumEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/****************************************************************************
* Copyright (c) 2018.3 布鞋 827922094@qq.com
* Copyright (c) 2015 - 2022 liangxiegame UNDER MIT License
*
* https://qframework.cn
* https://github.com/liangxiegame/QFramework
* https://gitee.com/liangxiegame/QFramework
****************************************************************************/

using UnityEngine;

namespace QFramework
{
public class EnumEventExample : MonoBehaviour
{
#region 事件定义

public enum TestEvent
{
Start,
TestOne,
End,
}

public enum TestEventB
{
Start = TestEvent.End + 1, // 为了保证每个消息 Id 唯一,需要头尾相接
TestB,
End,
}

#endregion 事件定义

void Start()
{
Debug.Log("End: " + (int) TestEvent.End + " Start: " + (int) TestEventB.Start);
EnumEventSystem.Global.Register(TestEvent.TestOne, OnEvent);
}

void OnEvent(int key, params object[] obj)
{
switch (key)
{
case (int) TestEvent.TestOne:
Debug.Log(obj[0]);
break;
}
}

private void Update()
{
if (Input.GetMouseButtonDown(0))
{
EnumEventSystem.Global.Send(TestEvent.TestOne, "Hello World!");
}
}

private void OnDestroy()
{
EnumEventSystem.Global.UnRegister(TestEvent.TestOne, OnEvent);
}
}
}

3) UnityEventTrigger

本质是扩展了GameObject,使用Onxxx即可注册事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using UnityEngine;

namespace QFramework.Example
{
public class EventTriggerExample : MonoBehaviour
{
void Start()
{
GameObject.Find("Ground").OnTriggerEnter2DEvent(collider2D1 =>
{
Debug.Log(collider2D1.name + ": entered");

}).UnRegisterWhenGameObjectDestroyed(gameObject);

var uiImage = GameObject.Find("Canvas/Image");

uiImage.OnPointerDownEvent(data =>
{
Debug.Log("Click");

}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
}
}

4) StringEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
using UnityEngine;

namespace QFramework.Example
{
public class StringEventSystemExample : MonoBehaviour
{
void Start()
{
StringEventSystem.Global.Register("TEST_ONE", () =>
{
Debug.Log("TEST_ONE");
}).UnRegisterWhenGameObjectDestroyed(gameObject);

// 事件 + 参数
StringEventSystem.Global.Register<int>("TEST_TWO", (count) =>
{
Debug.Log("TEST_TWO:" + count);

}).UnRegisterWhenGameObjectDestroyed(gameObject);
}

private void Update()
{
if (Input.GetMouseButtonDown(0))
{
StringEventSystem.Global.Send("TEST_ONE");
StringEventSystem.Global.Send("TEST_TWO",10);

}
}
}
}

// 输出结果
// 点击鼠标左键
// TEST_ONE
// TEST_TWO:10

对比

TypeEventSystem:

  • 事件体定义简洁
  • 比较适合用于设计框架
  • 支持 struct 获得较好内存性能
  • 使用反射,CPU 性能相对比较差

EasyEvent

  • 方便、易用、开发效率高
  • CPU 性能、内存性能较好,接近委托
  • 功能有限
  • 比较适合设计通用解决工具,比如通用背包、全局生命周期触发等
  • StringEventSystem、TypeEventSystem 的底层由 EasyEvent 实现

EnumEventSystem

  • 使用枚举作为事件 id,比较适合和服务端的 protobuf 或带有消息 id 的长链接通信
  • 性能较好
  • 枚举用于定义消息体有维护成本

StringEventSystem

  • 使用字符串作为事件 id,比较适合和其他脚本层通信,比如 Lua、ILRuntime、PlayMaker 等。
  • 性能一般

目前官方推荐使用 TypeEventSystem 和 EasyEvent 这两个工具。

如果要和网络通信则选择用 EnumEventSystem。

如果要和其他脚本层通信选择用 StringEventSystem。

5.FluentAPI

详见QFramework API文档

image

6.FSMKit

1) Chain

相当于用委托实现状态事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using UnityEngine;

namespace QFramework.Example
{
public class IStateBasicUsageExample : MonoBehaviour
{
public enum States
{
A,
B
}

public FSM<States> FSM = new FSM<States>();

void Start()
{
Application.targetFrameRate = 60;

FSM.OnStateChanged((previousState, nextState) =>
{
Debug.Log($"{previousState}=>{nextState}");
});

FSM.State(States.A)
.OnCondition(()=>FSM.CurrentStateId == States.B) //当前状态是B则能转换到A
.OnEnter(() =>
{
Debug.Log("Enter A");
})
.OnUpdate(() =>
{
if (FSM.FrameCountOfCurrentState % 60 == 0)
{
Debug.Log("Heart beat");
}
})
.OnFixedUpdate(() =>
{

})
.OnGUI(() =>
{
GUILayout.Label("State A");
if (GUILayout.Button("To State B"))
{
FSM.ChangeState(States.B);
}
})
.OnExit(() =>
{
Debug.Log("Exit A");

});

FSM.State(States.B)
.OnCondition(() => FSM.CurrentStateId == States.A)
.OnGUI(() =>
{
GUILayout.Label("State B");
if (GUILayout.Button("To State A"))
{
FSM.ChangeState(States.A);
}
});

FSM.StartState(States.A);
}

private void Update()
{
FSM.Update();
}

private void FixedUpdate()
{
FSM.FixedUpdate();
}

private void OnGUI()
{
FSM.OnGUI();
}

private void OnDestroy()
{
FSM.Clear();
}
}
}

2) Class

相当于用类继承实现状态事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
using UnityEngine;

namespace QFramework.Example
{
public class IStateClassExample : MonoBehaviour
{

public enum States
{
A,
B,
C
}

public FSM<States> FSM = new FSM<States>();

public class StateA : AbstractState<States,IStateClassExample>
{
public StateA(FSM<States> fsm, IStateClassExample target) : base(fsm, target)
{
}

protected override bool OnCondition()
{
return mFSM.CurrentStateId == States.B;
}

public override void OnGUI()
{
GUILayout.Label("State A");

if (GUILayout.Button("To State B"))
{
mFSM.ChangeState(States.B);
}
}
}


public class StateB: AbstractState<States,IStateClassExample>
{
public StateB(FSM<States> fsm, IStateClassExample target) : base(fsm, target)
{
}

protected override bool OnCondition()
{
return mFSM.CurrentStateId == States.A;
}

public override void OnGUI()
{
GUILayout.Label("State B");

if (GUILayout.Button("To State A"))
{
mFSM.ChangeState(States.A);
}
}
}

private void Start()
{
FSM.AddState(States.A, new StateA(FSM, this));
FSM.AddState(States.B, new StateB(FSM, this));

// 支持和链式模式混用
// FSM.State(States.C)
// .OnEnter(() =>
// {
//
// });

FSM.StartState(States.A);
}

private void OnGUI()
{
FSM.OnGUI();
}

private void OnDestroy()
{
FSM.Clear();
}
}
}

3) 综合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
using UnityEngine;
using UnityEngine.UI;

namespace QFramework.Example
{
public class FSMFrameCountExample : MonoBehaviour
{
enum States
{
FadeAlphaIn,
FadeAlphaOut,
FadeColorBlue,
FadeColorRed,
Delay,
RotateTo
}

private FSM<States> mFSM = new FSM<States>();

public Image Image;

void Start()
{
Application.targetFrameRate = 60;

mFSM.State(States.FadeAlphaIn)
.OnEnter(() => Image.ColorAlpha(0))
.OnUpdate(() =>
{
if (mFSM.FrameCountOfCurrentState <= 60)
{
Image.ColorAlpha(Mathf.Lerp(0, 1, mFSM.FrameCountOfCurrentState / 60.0f));
}
else
{
mFSM.ChangeState(States.FadeAlphaOut);
}
});

mFSM.State(States.FadeAlphaOut)
.OnUpdate(() =>
{
if (mFSM.FrameCountOfCurrentState <= 60)
{
Image.ColorAlpha(Mathf.Lerp(1, 0, mFSM.FrameCountOfCurrentState / 60.0f));
}
else
{
mFSM.ChangeState(States.FadeColorBlue);
}
});

mFSM.State(States.FadeColorBlue)
.OnUpdate(() =>
{
if (mFSM.FrameCountOfCurrentState <= 60)
{
Image.color = Color.Lerp(Color.white, Color.blue,
mFSM.FrameCountOfCurrentState / 60.0f);
}
else
{
mFSM.ChangeState(States.FadeColorRed);
}
});

mFSM.State(States.FadeColorRed)
.OnUpdate(() =>
{
if (mFSM.FrameCountOfCurrentState <= 60)
{
Image.color = Color.Lerp(Color.blue, Color.red,
mFSM.FrameCountOfCurrentState / 60.0f);
}
else
{
mFSM.ChangeState(States.Delay);
}
});

mFSM.State(States.Delay)
.OnUpdate(() =>
{
if (mFSM.FrameCountOfCurrentState > 60)
{
mFSM.ChangeState(States.RotateTo);
}
});

mFSM.State(States.RotateTo)
.OnUpdate(() =>
{
if (mFSM.FrameCountOfCurrentState <= 60)
{
Image.Rotation(Quaternion.Lerp(Quaternion.identity, Quaternion.Euler(45, 45, 45),
mFSM.FrameCountOfCurrentState / 60.0f));
}
});

mFSM.StartState(States.FadeAlphaIn);
}

// Update is called once per frame
void Update()
{
mFSM.Update();
}

private void OnDestroy()
{
mFSM.Clear();
mFSM = null;
}
}
}

7.GridKit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEngine;

namespace QFramework.Example
{
public class GridKitExample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
var grid = new EasyGrid<string>(4, 4);

grid.Fill("Empty");

grid[2, 3] = "@@@ Hello @@@";

grid.ForEach((x, y, content) => Debug.Log($"({x},{y}):{content}"));

grid.Resize(5, 5, (x, y) => "123");

grid.ForEach((x, y, content) => Debug.Log($"({x},{y}):{content}"));

grid.Clear();
}

}
}

8.LocalKit

使用步骤:

  1. 在QFrameworkData/LocaleKit里可以配置语言类型
  2. 为Text组件绑定LocaleText脚本,可以为不同语言设置文本
  3. 为任意GameObject绑定LocaleUnityEvent脚本,在切换到该语言时可以触发事件
  4. 为交互组件绑定LocaleKitChangeLanguageAction脚本,触发交互时可以切换到任意语言

9.LogKit

image image image image image

10.PoolKit

1) SimpleObjectPool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework
{
public class SimpleObjectPoolExample : MonoBehaviour
{
private SimpleObjectPool<GameObject> mObjectPool;

private List<GameObject> cacheList = new List<GameObject>();

void Start()
{
mObjectPool = new SimpleObjectPool<GameObject>(() =>
{
var gameObj = new GameObject();
gameObj.SetActive(false);
return gameObj;
}, gameObj => { gameObj.SetActive(false); }, 5);
}

// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
var obj = mObjectPool.Allocate();
obj.SetActive(true);
// cacheList.Add(obj);
}


if (Input.GetMouseButtonDown(1))
{
mObjectPool.Clear(o => { Destroy(o); });
// if (cacheList.Count > 0)
// {
// mObjectPool.Recycle(cacheList[^1]);
// cacheList.RemoveAt(cacheList.Count - 1);
// }
}
}
}
}

2) SafeObjectPool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/****************************************************************************
* Copyright (c) 2018 ~ 2022 UNDER MIT Lisence 布鞋 827922094@qq.com
*
* https://qframework.cn
* https://github.com/liangxiegame/QFramework
****************************************************************************/

using UnityEngine;

namespace QFramework
{
public class CallPool : MonoBehaviour
{
class Fish
{

}

private void Start()
{
#region SimpleObjectPool
var pool = new SimpleObjectPool<Fish>(() => new Fish(),initCount:50);

Debug.Log(pool.CurCount);

var fish = pool.Allocate();

Debug.Log(pool.CurCount);

pool.Recycle(fish);

Debug.Log(pool.CurCount);


var gameObjPool = new SimpleObjectPool<GameObject>(() =>
{
var gameObj = new GameObject("AGameObject");
// init gameObj code

// gameObjPrefab = Resources.Load<GameObject>("somePath/someGameObj");

return gameObj;
}, (gameObj) =>
{
// reset code here
});
#endregion


#region SafeObjectPool

//分配方式1,手动设置new规则
SafeObjectPool<Bullet>.Instance.SetFactoryMethod(() =>
{
// bullet can be mono behaviour
return new Bullet();
});

//分配方式2,自动调用无参构造new
SafeObjectPool<Bullet>.Instance.SetObjectFactory(new DefaultObjectFactory<Bullet>());


SafeObjectPool<Bullet>.Instance.Init(50,25);

var bullet = Bullet.Allocate();

Debug.Log(SafeObjectPool<Bullet>.Instance.CurCount);

bullet.Recycle2Cache();

Debug.Log(SafeObjectPool<Bullet>.Instance.CurCount);

#endregion
}


class Bullet :IPoolable,IPoolType
{
public void OnRecycled()
{
Debug.Log("回收了");
}

public bool IsRecycled { get; set; }

public static Bullet Allocate()
{
return SafeObjectPool<Bullet>.Instance.Allocate();
}

public void Recycle2Cache()
{
SafeObjectPool<Bullet>.Instance.Recycle(this);
}
}

}
}
// 50
// 49
// 50
// 回收了 x 25
// 24
// 回收了 24
// 回收了
// 回收了 25

对比

1.实现方式不同:SimpleObjectPool通过委托定义Allocate和Recycle函数;SafeObjectPool通过实现IPoolable,IPoolType接口来定义

2.容量不同:SimpleObjectPool支持无限扩容,SafeObjctPool有最大值

作者建议:

  • ①适合用于项目开发SimpleObjectPool
  • ②适合用于库级开发,更多限制,要求开发者一开始就想好,更安全的SafeObjectPool

11.SingletonKit

1) Singleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
namespace QFramework.Example
{
using UnityEngine;

internal class Class2Singleton : Singleton<Class2Singleton>
{
private static int mIndex = 0;

private Class2Singleton() {}

public override void OnSingletonInit()
{
mIndex++;
}

public void Log(string content)
{
Debug.Log("Class2Singleton" + mIndex + ":" + content);
}
}

public class SingletonExample : MonoBehaviour
{
private void Start()
{
Class2Singleton.Instance.Log("Hello World!");

// delete instance
Class2Singleton.Instance.Dispose();

// a differente instance
Class2Singleton.Instance.Log("Hello World!");
}
}
}

2) MonoSingleton

适合做场景内单例,如关卡蓝图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
namespace QFramework.Example
{
using System.Collections;
using UnityEngine;

internal class Class2MonoSingleton : MonoSingleton<Class2MonoSingleton>
{
public override void OnSingletonInit()
{
Debug.Log(name + ":" + "OnSingletonInit");
}

private void Awake()
{
Debug.Log(name + ":" + "Awake");
}

private void Start()
{
Debug.Log(name + ":" + "Start");
}

protected override void OnDestroy()
{
base.OnDestroy();

Debug.Log(name + ":" + "OnDestroy");
}
}

public class MonoSingletonExample : MonoBehaviour
{
private IEnumerator Start()
{
var instance = Class2MonoSingleton.Instance;

yield return new WaitForSeconds(3.0f);

instance.Dispose();
}
}
}

3) SingletonProperty

感觉和1) Singleton没区别啊,就是多了一层封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
namespace QFramework.Example
{
using UnityEngine;

internal class Class2SingletonProperty : ISingleton
{
public static Class2SingletonProperty Instance
{
get { return SingletonProperty<Class2SingletonProperty>.Instance; }
}

private Class2SingletonProperty() {}

private static int mIndex = 0;

public void OnSingletonInit()
{
mIndex++;
}

public void Dispose()
{
SingletonProperty<Class2SingletonProperty>.Dispose();
}

public void Log(string content)
{
Debug.Log("Class2SingletonProperty" + mIndex + ":" + content);
}
}

public class SingletonPropertyExample : MonoBehaviour
{
// Use this for initialization
void Start ()
{
Class2SingletonProperty.Instance.Log("Hello World!");

// delete current instance
Class2SingletonProperty.Instance.Dispose();

// new instance
Class2SingletonProperty.Instance.Log("Hello World!");
}
}
}

4) MonoSingletonProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
namespace QFramework.Example
{
using System.Collections;
using UnityEngine;

internal class Class2MonoSingletonProperty : MonoBehaviour,ISingleton
{
public static Class2MonoSingletonProperty Instance
{
get { return MonoSingletonProperty<Class2MonoSingletonProperty>.Instance; }
}

public void Dispose()
{
MonoSingletonProperty<Class2MonoSingletonProperty>.Dispose();
}

public void OnSingletonInit()
{
Debug.Log(name + ":" + "OnSingletonInit");
}

private void Awake()
{
Debug.Log(name + ":" + "Awake");
}

private void Start()
{
Debug.Log(name + ":" + "Start");
}

protected void OnDestroy()
{
Debug.Log(name + ":" + "OnDestroy");
}
}

public class MonoSingletonPropertyExample : MonoBehaviour
{
private IEnumerator Start()
{
var instance = Class2MonoSingletonProperty.Instance;

yield return new WaitForSeconds(3.0f);

instance.Dispose();
}
}
}

5) MonoSingletonPath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
namespace QFramework.Example
{
using UnityEngine;

[MonoSingletonPath("[Example]/MonoSingletonPath")]
internal class ClassUseMonoSingletonPath : MonoSingleton<ClassUseMonoSingletonPath>
{

}

public class MonoSingletonPathExample : MonoBehaviour
{
private void Start()
{
var intance = ClassUseMonoSingletonPath.Instance;
}
}
}

6) PersistentMonoSingleton

当场景里包含两个PersistentMonoSingleton,保留先创建的

  • 分析:由于单例生成代码中多了一行FindObjectOfType,因此如果场景存在单例对象就不会重复创建

适用场景:建议切换场景不释放的Mono设为该单例,例如背景音乐等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace QFramework.Example
{
public class PersistentMonoSingletonExample : MonoBehaviour
{
// Use this for initialization
IEnumerator Start()
{
// 创建一个单例
var instance = GameManager.Instance;
instance.PrintID();

// 强制创建一个实例
new GameObject().AddComponent<GameManager>();
instance.PrintID();

// 等一帧,等待第二个 GameManager 把自己删除
yield return new WaitForEndOfFrame();

// 结果为 1
Debug.Log(FindObjectsOfType<GameManager>().Length);

// 保留最先创建的实例
Debug.Log(instance == FindObjectOfType<GameManager>());
instance.PrintID();

if (SceneManager.GetActiveScene().name == "SceneA")
SceneManager.LoadScene("SceneB");
}


public class GameManager : PersistentMonoSingleton<GameManager>
{
public static int gm_id;
public int id;

protected override void Awake()
{
base.Awake();
GameManager.gm_id++;
this.id = gm_id;
}

public void PrintID()
{
Debug.Log("instance id: " + this.id);
}
}
}
}

7) ReplaceableMonoSingleton

和Persistent相反,保留后创建的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System.Collections;
using UnityEngine;

namespace QFramework.Example
{
public class ReplaceableMonoSingletonExample : MonoBehaviour
{
// Use this for initialization
IEnumerator Start()
{
// 创建一个单例
var instance = GameManager.Instance;

// 强制创建一个实例
new GameObject().AddComponent<GameManager>();

// 等一帧,等待第二个 GameManager 把自己删除
yield return new WaitForEndOfFrame();

// 结果为 1
Debug.Log(FindObjectsOfType<GameManager>().Length);

// 最先创建的实例已经被删除
Debug.Log(instance != FindObjectOfType<GameManager>());
}


public class GameManager : ReplaceableMonoSingleton<GameManager>
{

}
}
}

8) PrefabSingletonProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;
using UnityEngine;

namespace QFramework.Example
{
public class PrefabSingletonPropertyExample : MonoBehaviour
{
private void Start()
{
var prefabA = MyPrefabA.Instance;
}

private void OnDestroy()
{
PrefabSingletonProperty<MyPrefabA>.Dispose();
}
}
}
1
2
3
4
5
6
7
8
9
10
using UnityEngine;

namespace QFramework.Example
{
public class MyPrefabA : MonoBehaviour
{
public static MyPrefabA Instance => PrefabSingletonProperty<MyPrefabA>
.InstanceWithLoader(prefabName => Resources.Load<GameObject>("Prefabs/" + prefabName));
}
}

9) ScriptableSingletonProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework.Example
{
public class ScriptableSingletonPropertyExample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
MyScriptableA.Instance.ScriptableKey.LogInfo();

}


private void OnDestroy()
{
ScriptableSingletonProperty<MyScriptableA>.Dispose();
}
}
}

12.TableKit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace QFramework
{
public class TableKitExample : MonoBehaviour
{
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public int Level { get; set; }
}
public class School : Table<Student>
{
public TableIndex<int, Student> AgeIndex = new TableIndex<int, Student>((student) => student.Age);
public TableIndex<int, Student> LevelIndex = new TableIndex<int, Student>((student) => student.Level);

protected override void OnAdd(Student item)
{
AgeIndex.Add(item);
LevelIndex.Add(item);
}

protected override void OnRemove(Student item)
{
AgeIndex.Remove(item);
LevelIndex.Remove(item);
}

protected override void OnClear()
{
AgeIndex.Clear();
LevelIndex.Clear();
}

public override IEnumerator<Student> GetEnumerator()
{
return AgeIndex.Dictionary.Values.SelectMany(s=>s).GetEnumerator();
}

protected override void OnDispose()
{
AgeIndex.Dispose();
LevelIndex.Dispose();
}
}


private void Start()
{
var school = new School();
school.Add(new Student(){Age = 1,Level = 2,Name = "liangxie"});
school.Add(new Student(){Age = 2,Level = 2,Name = "ava"});
school.Add(new Student(){Age = 3,Level = 2,Name = "abc"});
school.Add(new Student(){Age = 3,Level = 3,Name = "efg"});

foreach (var student in school.LevelIndex.Get(2).Where(s=>s.Age < 3))
{
Debug.Log(student.Age + ":" + student.Level + ":" + student.Name);
}
}
}
}
// 1:2:liangxie
// 2:2:ava

UIKit

1) Basic

如何生成面板的操作见QFramework文档

2) InitAndCallAPIS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using UnityEngine;

namespace QFramework.Example
{
public class InitAndCallApisExample : MonoBehaviour
{
private void Start()
{
// UIKit 的资源管理默认使用的是 ResKit
ResKit.Init();

// UIKit 的分辨率设置
UIKit.Root.SetResolution(1920, 1080, 1);

// 打开 UIBasicPanel
UIKit.OpenPanel<UIBasicPanel>();


ActionKit.Sequence()
.Delay(1f)
.Callback(() => UIKit.HidePanel<UIBasicPanel>())
.Delay(1)
.Callback(() => UIKit.OpenPanel<UIBasicPanel>())
.Delay(1)
.Callback(() => UIKit.HidePanel<UIBasicPanel>())
.Delay(1)
.Callback(() => UIKit.ShowPanel<UIBasicPanel>())
.Start(this);
}
}
}

3) OpenPanelAsync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System.Collections;
using UnityEngine;

namespace QFramework.Example
{
public class OpenPanelAsyncExample : MonoBehaviour
{
IEnumerator Start()
{
yield return ResKit.InitAsync();

yield return UIKit.OpenPanelAsync<UIBasicPanel>();

// or
// UIKit.OpenPanelAsync<UIBasicPanel>().ToAction().Start(this);
}
}
}

4) Stack

  • Push相当于缓存打开的页面到Stack,并取消显示
  • Pop相当于从缓存中取出页面并显示(底层没有非空判断,注意下)

作者版本(个人感觉不易理解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using UnityEngine;

namespace QFramework.Example
{
public class UIKitStackExample : MonoBehaviour
{
private void Start()
{
ResKit.Init();

UIKit.Stack.Push(UIKit.OpenPanel<UIStackPanel1>());
UIKit.Stack.Push(UIKit.OpenPanel<UIStackPanel2>());

UIKit.Stack.Pop();


ActionKit.Delay(3, () =>
{
UIKit.Stack.Pop();

}).Start(this);
}
}
}

修改后版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using UnityEngine;

namespace QFramework.Example
{
public class UIKitStackExample : MonoBehaviour
{
private UIStackPanel1 ret;
private void Start()
{
ResKit.Init();

ret = UIKit.OpenPanel<UIStackPanel1>();

// UIKit.Stack.Push(UIKit.OpenPanel<UIStackPanel2>());

// UIKit.Stack.Pop();


// ActionKit.Delay(3, () =>
// {
// UIKit.Stack.Pop();
//
// }).Start(this);
}

private void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Debug.Log("Q Pressed");
UIKit.Stack.Push(ret);
}
if (Input.GetKeyDown(KeyCode.E))
{
Debug.Log("E Pressed");
UIKit.Stack.Pop();
}
}
}
}

5) MultiPanel

OpenType分为Single和Multiple

  • Single:对同一面板,无论调用多少次Open,都只会打开一个
  • Multiple:对同一面板,每次调用会生成一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using UnityEngine;

namespace QFramework.Example
{
public class MultiPanelExample : MonoBehaviour
{
void Start()
{
ResKit.Init();
}

private UIMultiPanel mUIMultiPanel;

private int mPageIndex = 0;
private void OnGUI()
{
if (GUILayout.Button("打开"))
{
mUIMultiPanel = UIKit.OpenPanel<UIMultiPanel>(new UIMultiPanelData()
{
PageIndex = mPageIndex
}, PanelOpenType.Multiple);

mPageIndex++;
}

if (GUILayout.Button("关闭"))
{
UIKit.ClosePanel<UIMultiPanel>();
}

if (mUIMultiPanel && GUILayout.Button("关闭当前"))
{
UIKit.ClosePanel(mUIMultiPanel);
mUIMultiPanel = null;
}
}
}
}

6) ResourcesPanelLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using UnityEngine;

namespace QFramework.Example
{
public class CustomPanelLoaderExample : MonoBehaviour
{
public class ResourcesPanelLoaderPool : AbstractPanelLoaderPool
{
/// <summary>
/// Load Panel from Resources
/// </summary>
public class ResourcesPanelLoader : IPanelLoader
{
private GameObject mPanelPrefab;

public GameObject LoadPanelPrefab(PanelSearchKeys panelSearchKeys)
{
mPanelPrefab = Resources.Load<GameObject>(panelSearchKeys.GameObjName);
return mPanelPrefab;
}

public void LoadPanelPrefabAsync(PanelSearchKeys panelSearchKeys, Action<GameObject> onPanelLoad)
{
var request = Resources.LoadAsync<GameObject>(panelSearchKeys.GameObjName);

request.completed += operation => { onPanelLoad(request.asset as GameObject); };
}

public void Unload()
{
mPanelPrefab = null;
}
}

protected override IPanelLoader CreatePanelLoader()
{
return new ResourcesPanelLoader();
}
}

void Start()
{
// 游戏启动时,设置一次
UIKit.Config.PanelLoaderPool = new ResourcesPanelLoaderPool();
}
}
}

ResKit

1) Basic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using UnityEngine;

namespace QFramework.Example
{
public class ResKitExample : MonoBehaviour
{
// 每个脚本都需要
private ResLoader mResLoader = ResLoader.Allocate();

private void Start()
{
// 项目启动只调用一次即可
ResKit.Init();

// 通过资源名 + 类型搜索并加载资源(更方便)
var prefab = mResLoader.LoadSync<GameObject>("AssetObj");
var gameObj = Instantiate(prefab);
gameObj.name = "这是使用通过 AssetName 加载的对象";

// 通过 AssetBundleName 和 资源名搜索并加载资源(更精确)
prefab = mResLoader.LoadSync<GameObject>("assetobj_prefab", "AssetObj");
gameObj = Instantiate(prefab);
gameObj.name = "这是使用通过 AssetName 和 AssetBundle 加载的对象";
}

private void OnDestroy()
{
// 释放所有本脚本加载过的资源
// 释放只是释放资源的引用
// 当资源的引用数量为 0 时,会进行真正的资源卸载操作
mResLoader.Recycle2Cache();
mResLoader = null;
}
}
}

2) AsyncLoad

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using System.Collections;
using UnityEngine;

namespace QFramework.Example
{
public class AsyncLoadExample : MonoBehaviour
{
IEnumerator Start()
{
yield return ResKit.InitAsync();

var resLoader = ResLoader.Allocate();

resLoader.Add2Load<GameObject>("AssetObj 1",(b, res) =>
{
if (b)
{
res.Asset.As<GameObject>().Instantiate();
}
});

// AssetBundleName + AssetName
resLoader.Add2Load<GameObject>("assetobj 2_prefab","AssetObj 2",(b, res) =>
{
if (b)
{
res.Asset.As<GameObject>().Instantiate();
}
});

resLoader.Add2Load<GameObject>("AssetObj 3",(b, res) =>
{
if (b)
{
res.Asset.As<GameObject>().Instantiate();
}
});

resLoader.LoadAsync(() =>
{
// 加载成功 5 秒后回收
ActionKit.Delay(5.0f, () =>
{
resLoader.Recycle2Cache();

}).Start(this);
});
}
}
}

3) LoadScene

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using UnityEngine;

namespace QFramework.Example
{
public class LoadSceneExample : MonoBehaviour
{
private ResLoader mResLoader = null;

void Start()
{
ResKit.Init();

mResLoader = ResLoader.Allocate();

// 同步加载
// mResLoader.LoadSceneSync("SceneRes");

// 异步加载
// mResLoader.LoadSceneAsync("SceneRes");

// 异步加载
mResLoader.LoadSceneAsync("SceneRes", onStartLoading: operation =>
{
Debug.Log(operation.progress); //0
// 做一些加载操作
operation.completed += asyncOperation =>
{
Debug.Log(operation.progress); //1
Debug.Log("SceneRes loaded");
};
});
}

private void OnDestroy()
{
mResLoader.Recycle2Cache();
mResLoader = null;
}
}
}

4) ResoucesLoad

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEngine;
using UnityEngine.UI;

namespace QFramework.Example
{
public class LoadResourcesResExample : MonoBehaviour
{
public RawImage RawImage;

private ResLoader mResLoader = ResLoader.Allocate();

private void Start()
{
// 加载 Resources 目录里的资源不用调用 ResKit.Init

RawImage.texture = mResLoader.LoadSync<Texture2D>("resources://TestTexture");
}

private void OnDestroy()
{
Debug.Log("On Destroy ");
mResLoader.Recycle2Cache();
mResLoader = null;
}
}
}

5) SpriteAtlas(有卸载问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.U2D;
using UnityEngine.UI;

namespace QFramework
{
/// <inheritdoc />
/// <summary>
/// 参考:http://www.cnblogs.com/TheChenLin/p/9763710.html
/// </summary>
public class TestSpriteAtlas : MonoBehaviour
{
[SerializeField] private Image mImage;

private ResLoader loader;

// Use this for initialization
private IEnumerator Start()
{
loader = ResLoader.Allocate();

ResKit.Init();

var spriteAtlas = loader.LoadSync<SpriteAtlas>("spriteatlas");
var square = spriteAtlas.GetSprite("shop");

loader.AddObjectForDestroyWhenRecycle2Cache(square);

mImage.sprite = square;

yield return new WaitForSeconds(3.0f);

Debug.Log("Recycle2Cache");
loader.Recycle2Cache();
loader = null;
}
}
}

6) NetImage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using UnityEngine;
using UnityEngine.UI;

namespace QFramework.Example
{
public class NetImageExample : MonoBehaviour
{
ResLoader mResLoader = ResLoader.Allocate();

// Use this for initialization
void Start()
{
var image = transform.Find("Image").GetComponent<Image>();

mResLoader.Add2Load<Texture2D>(
"http://pic.616pic.com/ys_b_img/00/44/76/IUJ3YQSjx1.jpg".ToNetImageResName(),
(b, res) =>
{
if (b)
{
var texture = res.Asset as Texture2D;

var sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height),
Vector2.one * 0.5f);
image.sprite = sprite;
mResLoader.AddObjectForDestroyWhenRecycle2Cache(sprite);
}
});

mResLoader.LoadAsync();
}

private void OnDestroy()
{
mResLoader.Recycle2Cache();
mResLoader = null;
}
}
}

7) LocalImage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
namespace QFramework.Example
{
using System.Collections;
using UnityEngine.UI;
using UnityEngine;

public class ImageLoaderExample : MonoBehaviour
{
private ResLoader mResLoader = null;

private IEnumerator Start()
{
ResMgr.Init();

mResLoader = ResLoader.Allocate();

// local image
var localImageUrl = "file://" + Application.persistentDataPath + "/Workspaces/lM1wmsLQtfzRQc6fsdEU.jpg";

mResLoader.Add2Load(localImageUrl.ToLocalImageResName(),
delegate(bool b, IRes res)
{
Debug.LogError(b);
if (b)
{
var texture2D = res.Asset as Texture2D;
transform.Find("Image").GetComponent<Image>().sprite = Sprite.Create(texture2D,
new Rect(0, 0, texture2D.width, texture2D.height), Vector2.one * 0.5f);
}
});

mResLoader.LoadAsync();


yield return new WaitForSeconds(5.0f);
mResLoader.Recycle2Cache();
mResLoader = null;
}
}
}

8) Custom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using UnityEngine;

namespace QFramework
{
public class CustomResExample : MonoBehaviour
{
// 自定义的 Res
public class MyRes : Res
{
public MyRes(string name)
{
mAssetName = name;
}

// 同步加载(自己实现)
public override bool LoadSync()
{
// Asset = 加载的结果给 Asset 赋值
State = ResState.Ready;
return true;
}

// 异步加载(自己实现)
public override void LoadAsync()
{
// Asset = 加载的结果给 Asset 赋值
State = ResState.Ready;
}


// 释放资源(自己实现)
protected override void OnReleaseRes()
{
// 卸载操作
// Asset = null
State = ResState.Waiting;
}
}

// 自定义的 Res 创建器(包含识别功能)
public class MyResCreator : IResCreator
{
// 识别
public bool Match(ResSearchKeys resSearchKeys)
{
return resSearchKeys.AssetName.StartsWith("myres://");
}

// 创建
public IRes Create(ResSearchKeys resSearchKeys)
{
return new MyRes(resSearchKeys.AssetName);
}
}

void Start()
{
// 添加创建器
ResFactory.AddResCreator<MyResCreator>();

var resLoader = ResLoader.Allocate();

var resSearchKeys = ResSearchKeys.Allocate("myres://hello_world");

var myRes = resLoader.LoadResSync(resSearchKeys);

resSearchKeys.Recycle2Cache();

Debug.Log(myRes.AssetName);
Debug.Log(myRes.State);
}
}
}

9) LoadPrefabByMonoType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace QFramework.Example
{

public class LoadPrefabByMonoType : MonoBehaviour
{
private ResLoader mResLoader;
private void Awake()
{
ResKit.Init();
mResLoader = ResLoader.Allocate();
}

private void Start()
{
mResLoader.LoadSync<SquareA>("SquareA")
.InstantiateWithParent(transform)
.LocalIdentity();

mResLoader.Add2Load<SquareA>("SquareA",(b, res) =>
{
if (b)
{
(res.Asset as GameObject).GetComponent<SquareA>()
.InstantiateWithParent(transform)
.LocalRotation(Quaternion.Euler(new Vector3(0, 0, 45)))
.GetComponent<SpriteRenderer>()
.color = Color.blue;
}
});

mResLoader.LoadAsync();
}

private void OnDestroy()
{
mResLoader.Recycle2Cache();
mResLoader = null;
}
}
}

AudioKit

1) Basic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using UnityEngine;
using UnityEngine.UI;

namespace QFramework.Example
{
public class AudioExample : MonoBehaviour
{
private void Awake()
{
var btnPlayHome = transform.Find("BtnPlayHome").GetComponent<Button>();
var btnPlayGame = transform.Find("BtnPlayGame").GetComponent<Button>();
var btnPlaySound = transform.Find("BtnPlaySoundClick").GetComponent<Button>();
var btnPlayVoiceA = transform.Find("BtnPlayVoice").GetComponent<Button>();
var btnSoundOn = transform.Find("BtnSoundOn").GetComponent<Button>();
var btnSoundOff = transform.Find("BtnSoundOff").GetComponent<Button>();
var btnMusicOn = transform.Find("BtnMusicOn").GetComponent<Button>();
var btnMusicOff = transform.Find("BtnMusicOff").GetComponent<Button>();
var btnVoiceOn = transform.Find("BtnVoiceOn").GetComponent<Button>();
var btnVoiceOff = transform.Find("BtnVoiceOff").GetComponent<Button>();

var btnStopAllSound = transform.Find("BtnStopAllSounds").GetComponent<Button>();

var musicVolumeSlider = transform.Find("MusicVolume").GetComponent<Slider>();
var voiceVolumeSlider = transform.Find("VoiceVolume").GetComponent<Slider>();
var soundVolumeSlider = transform.Find("SoundVolume").GetComponent<Slider>();


btnPlayHome.onClick.AddListener(() => { AudioKit.PlayMusic("resources://home_bg"); });

btnPlayGame.onClick.AddListener(() => { AudioKit.PlayMusic("resources://game_bg"); });

btnPlaySound.onClick.AddListener(() => { AudioKit.PlaySound("resources://game_bg"); });

btnPlayVoiceA.onClick.AddListener(() => { AudioKit.PlayVoice("resources://game_bg"); });

btnSoundOn.onClick.AddListener(() => { AudioKit.Settings.IsSoundOn.Value = true; });

btnSoundOff.onClick.AddListener(() => { AudioKit.Settings.IsSoundOn.Value = false; });

btnMusicOn.onClick.AddListener(() => { AudioKit.Settings.IsMusicOn.Value = true; });

btnMusicOff.onClick.AddListener(() => { AudioKit.Settings.IsMusicOn.Value = false; });

btnVoiceOn.onClick.AddListener(() => { AudioKit.Settings.IsVoiceOn.Value = true; });

btnVoiceOff.onClick.AddListener(() => { AudioKit.Settings.IsVoiceOn.Value = false; });

btnStopAllSound.onClick.AddListener(() =>
{
AudioKit.StopAllSound();
});


AudioKit.Settings.MusicVolume.RegisterWithInitValue(v => musicVolumeSlider.value = v);
AudioKit.Settings.VoiceVolume.RegisterWithInitValue(v => voiceVolumeSlider.value = v);
AudioKit.Settings.SoundVolume.RegisterWithInitValue(v => soundVolumeSlider.value = v);

musicVolumeSlider.onValueChanged.AddListener(v => { AudioKit.Settings.MusicVolume.Value = v; });
voiceVolumeSlider.onValueChanged.AddListener(v => { AudioKit.Settings.VoiceVolume.Value = v; });
soundVolumeSlider.onValueChanged.AddListener(v => { AudioKit.Settings.SoundVolume.Value = v; });
}
}
}

2) CustomAudioLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System;
using UnityEngine;

namespace QFramework.Example
{
public class CustomAudioLoaderExample : MonoBehaviour
{
/// <summary>
/// 定义从 Resources 加载音频
/// </summary>
class ResourcesAudioLoaderPool : AbstractAudioLoaderPool
{
protected override IAudioLoader CreateLoader()
{
return new ResourcesAudioLoader();
}
}

class ResourcesAudioLoader : IAudioLoader
{
private AudioClip mClip;

public AudioClip Clip => mClip;

public AudioClip LoadClip(AudioSearchKeys panelSearchKeys)
{
mClip = Resources.Load<AudioClip>(panelSearchKeys.AssetName);
return mClip;
}

public void LoadClipAsync(AudioSearchKeys audioSearchKeys, Action<bool,AudioClip> onLoad)
{
var resourceRequest = Resources.LoadAsync<AudioClip>(audioSearchKeys.AssetName);
resourceRequest.completed += operation =>
{
var clip = resourceRequest.asset as AudioClip;
onLoad(clip, clip);
};
}

public void Unload()
{
Resources.UnloadAsset(mClip);
}
}


void Start()
{
// 启动时需要调用一次
AudioKit.Config.AudioLoaderPool = new ResourcesAudioLoaderPool();
}
}
}
 评论