基于ET的回合制战棋-场景切换部分
前言
先吐槽一下ET自带的场景切换组件实在是太简陋了。。因此自己根据项目需要对整个流程进行了重构,不得不说ETTask单线程异步任务是真的好用,免去了命令模式的编写。
场景切换流程
通过SceneChangeData实现数据流在场景转换流程中的传递
具体函数
InitLoadingUI:
实现LoadingUI淡入效果1
2
3
4
5
6
7
8
9
10
11
12
13
14private static async ETTask InitLoadingUI(this SceneChangeComponent self, SceneChangeData data)
{
//LoadingUI淡入
UIComponent.Instance.ShowWindow(WindowID.WindowID_Loading);
DlgLoading dlgLoading=UIComponent.Instance.GetDlgLogic<DlgLoading>();
if (dlgLoading != null)
{
data.dlgLoading = dlgLoading; //存入数据流中
dlgLoading.SetLoadingProgress(0f); //使得进度条为0
CanvasGroup canvasGroup = dlgLoading.View.uiTransform.GetComponent<CanvasGroup>();
canvasGroup.alpha = 0f;
await UIEffectHelper.FadeIn(canvasGroup, 1f, 1f);
}
}HandleExitSceneFunc:
根据退出的场景类型实现函数分发:1
2
3
4
5
6
7
8
9
10
11
12
13
14public static async ETTask HandleExitSceneFunc(this SceneChangeComponent self, SceneChangeData data,Scene zoneScene, float progress)
{
string lastSceneName = data.lastSceneName;
if (SceneConst.sceneName2Type.ContainsKey(lastSceneName))
{
GameSceneType sceneType = SceneConst.sceneName2Type[lastSceneName];
if (self.allEvents.TryGetValue(sceneType, out ASceneChangeEvent sceneEvent))
{
await sceneEvent.OnExitScene(zoneScene);
}
}
await self.HandleProgress(data, progress);
}不同场景的具体类型在Sceneconst中进行定义:
ASceneChangeEvent抽象类:
+HandleUnloadFunc:
处理场景资源卸载,采用计数方式,因为ETTask的本质为单线程,所以无需考虑多线程加锁。当所有资源卸载完毕后进入下一阶段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
32public static async ETTask HandleUnloadFunc(this SceneChangeComponent self, SceneChangeData data, float progress)
{
string lastSceneName = data.lastSceneName;
if (SceneConst.sceneName2Type.ContainsKey(lastSceneName))
{
GameSceneType sceneType = SceneConst.sceneName2Type[lastSceneName];
if (self.allEvents.TryGetValue(sceneType, out ASceneChangeEvent sceneEvent))
{
List<string> unloadNames = sceneEvent.GetUnloadResource();
int count = unloadNames?.Count??0;
int unloadedCount = 0;
for (int i = 0; i < count; i++)
{
string assetBundleName = $"{unloadNames[i]}.unity3d";
self.UnloadResources(assetBundleName, () =>
{
++unloadedCount;
}).Coroutine();
}
while (unloadedCount < count)
{
await TimerComponent.Instance.WaitFrameAsync();
}
}
}
//广播场景卸载消息
EventMessageComponent.Instance.SendMessage(Message.Create(MessageEventNames.UNLOAD_SCENE_MSG, self, data.lastSceneName));
await self.HandleProgress(data, progress);
}HandlePreloadFunc:
场景资源预加载,同样采用计数方式,获取对应场景类型中需要预加载的AB资源列表,分别进行异步加载,全部加载后进入下一阶段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
30public static async ETTask HandlePreloadFunc(this SceneChangeComponent self, SceneChangeData data, float progress)
{
string nextSceneName = data.nextSceneName;
if (SceneConst.sceneName2Type.ContainsKey(nextSceneName))
{
GameSceneType sceneType = SceneConst.sceneName2Type[nextSceneName];
if (self.allEvents.TryGetValue(sceneType, out ASceneChangeEvent sceneEvent))
{
List<string> preloadNames = sceneEvent.GetPreloadResource();
int count = preloadNames?.Count ?? 0;
int loadedCount = 0;
for (int i = 0; i < count; i++)
{
string assetbundleName = $"{preloadNames[i]}.unity3d";
self.PreloadResources(assetbundleName, () =>
{
++loadedCount; //本质还是单线程协程,不需要加锁
}).Coroutine();
}
while (loadedCount < count)
{
await TimerComponent.Instance.WaitFrameAsync();
}
}
}
EventMessageComponent.Instance.SendMessage(Message.Create(MessageEventNames.LOAD_SCENE_MSG,self,data.nextSceneName));
await self.HandleProgress(data, progress);
}LoadingMap:
这一阶段加载对应场景AB包后通过SceneManager.LoadSceneAsync进行场景的异步加载,加载完成后会发布ChangeSceneFinish事件,可以通过分发执行不同具体场景名的具体函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14private static async ETTask LoadingMap(this SceneChangeComponent self, SceneChangeData data,float progress,Scene zoneScene)
{
self.tcs = ETTask.Create(true);
// 加载对应的场景AB包
await ResourcesComponent.Instance.LoadBundleAsync($"{data.nextSceneName}.unity3d"); //加载对应场景AB包
// 加载map
self.loadMapOperation = SceneManager.LoadSceneAsync(data.nextSceneName);
await self.tcs; //在Update中进行SetResult
await self.HandleProgress(data, progress);
await ResourcesComponent.Instance.UnloadBundleAsync($"{data.nextSceneName}.unity3d",false); //卸载对应场景AB包
//广播场景加载消息
await Game.EventSystem.PublishAsync(new EventType.ChangeSceneFinish(){sceneName = data.nextSceneName,zoneScene = zoneScene});
}根据具体函数名的函数分发:
HandleEnterSceneFunc
与HandleExitSceneFunc同理,处理对应场景类型的Enter函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public static async ETTask HandleEnterSceneFunc(this SceneChangeComponent self, SceneChangeData data,Scene zoneScene,float progress)
{
string nextSceneName = data.nextSceneName;
if (SceneConst.sceneName2Type.ContainsKey(nextSceneName))
{
GameSceneType sceneType = SceneConst.sceneName2Type[nextSceneName];
//找到对应分发函数
if (self.allEvents.TryGetValue(sceneType, out ASceneChangeEvent sceneEvent))
{
await sceneEvent.OnEnterScene(zoneScene);
}
}
await self.HandleProgress(data, progress);
}DisposeLoadingUI
销毁对应的loadingUI界面,场景转换流程结束1
2
3
4
5
6
7
8
9
10
11
12
13
14private static async ETTask DisposeLoadingUI(this SceneChangeComponent self, SceneChangeData data)
{
if (data.dlgLoading != null)
{
//LoadingUI淡出
data.dlgLoading.SetLoadingProgress(1f); //使得进度条充满
CanvasGroup canvasGroup = data.dlgLoading.View.uiTransform.GetComponent<CanvasGroup>();
canvasGroup.alpha = 1f;
await UIEffectHelper.FadeOut(canvasGroup, 0f, 1f, null, () =>
{
UIComponent.Instance.CloseWindow<DlgLoading>();
});
}
}
场景Additive加载流程
- 采取AB包方式进行加载,先进行场景AB包的异步加载,随后执行SceneManager.LoadSceneAsync的场景异步加载
- 在场景加载完毕后异步卸载场景AB包
具体函数
1 | //MARKER:场景Addtive异步加载 |
碰到问题以及解决办法
碰到的问题主要在于用additive加载的场景并没有保留其对应的全局光照信息以及光照贴图,因此我对Unity的Editor进行了拓展,用LightMapPrefab的ScriptableObject存储全局光照信息,并在Scene/Save LightMapData中进行了选项拓展,在Additive加载完成后进行全局光照等信息的恢复。
LightMapPrefab类:
主要是对光照信息、天空盒、全局光照颜色/模式等进行存储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
77public class LightMapPrefab:ScriptableObject
{
[SerializeField]
public LightmapsMode mode;
[SerializeField]
public Texture2D[] lightmapColor, lightmapDir;
[SerializeField]
public Material skyBoxMat;
[SerializeField]
public Color ambientColor;
[SerializeField]
public AmbientMode ambientMode;
public LightMapPrefab()
{
}
public void SaveLightMap()
{
this.mode = LightmapSettings.lightmapsMode;
this.lightmapColor = null;
this.lightmapDir = null;
if (LightmapSettings.lightmaps != null && LightmapSettings.lightmaps.Length > 0)
{
int l = LightmapSettings.lightmaps.Length;
this.lightmapColor = new Texture2D[l];
this.lightmapDir = new Texture2D[l];
for (int i = 0; i < l; i++)
{
this.lightmapColor[i] = LightmapSettings.lightmaps[i].lightmapColor;
this.lightmapDir[i] = LightmapSettings.lightmaps[i].lightmapDir;
}
}
this.skyBoxMat = RenderSettings.skybox;
this.ambientColor = RenderSettings.ambientLight;
this.ambientMode = RenderSettings.ambientMode;
}
public void LoadLightMap()
{
LightmapSettings.lightmapsMode = this.mode;
int l1 = (this.lightmapDir == null)? 0 : this.lightmapDir.Length;
int l2 = (this.lightmapColor == null)? 0 : this.lightmapColor.Length;
int l = Mathf.Max(l1, l2);
LightmapData[] lightmaps = new LightmapData[l];
for (int i = 0; i < l; i++)
{
lightmaps[i] = new LightmapData();
if (i < l1)
{
lightmaps[i].lightmapDir = this.lightmapDir[i];
}
if (i < l2)
{
lightmaps[i].lightmapColor = this.lightmapColor[i];
}
}
LightmapSettings.lightmaps = lightmaps;
RenderSettings.skybox = this.skyBoxMat;
RenderSettings.ambientMode = this.ambientMode;
RenderSettings.ambientLight = this.ambientColor;
}
}Scene/Save LightMapData 拓展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public static class SceneEditorExpand
{
public static string mapDataSavePath = "Assets/Bundles/SceneData";
public static string assetBundleName = "scenedata.unity3d";
[MenuItem("Scene/Save LightMapData")]
static public void SaveSceneMapData()
{
LightMapPrefab lightMapPrefab = ScriptableObject.CreateInstance<LightMapPrefab>();
lightMapPrefab.SaveLightMap();
string path=mapDataSavePath+$"/{SceneManager.GetActiveScene().name}_Data.asset";
AssetDatabase.CreateAsset(lightMapPrefab,path);
AssetDatabase.SaveAssets();
AssetImporter importer = AssetImporter.GetAtPath(path);
importer.assetBundleName = assetBundleName;
AssetDatabase.Refresh();
}
}
具体调用方式
动态在zoneScene中加入SceneChangeComponent组件异步执行场景转换流程,结束后销毁组件
拓展思路
若想将思路用在非ETTask的OOP模式中,可以通过类似于状态机的CommandList将整个场景的切换流程贯穿起来,通过Create(),Execute(),Finish(),Cancel()等生命周期去走CommandList中的所有Command,数据流存放在CommandList中即可。