-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSceneLoader.cs
More file actions
361 lines (307 loc) · 13.2 KB
/
SceneLoader.cs
File metadata and controls
361 lines (307 loc) · 13.2 KB
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
using System;
using System.Collections.Generic;
using System.Linq;
using CCC.Runtime.Utils;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.SceneManagement;
using VContainer;
using VContainer.Unity;
namespace CCC.Runtime.SceneLoading
{
/// <summary>
/// A class that manages scene loading and unloading with support for different scene types,
/// transition effects, and additive scene loading. Provides centralized scene management
/// with automatic scene type handling and transition animations.
/// </summary>
public class SceneLoader : IInitializable
{
#region Private Fields
private readonly SceneConfigurations _config;
private readonly LifetimeScope _scope;
private readonly UIGroup _transitionScreen;
private readonly ActiveScenes _activeScenes = new();
private readonly SceneEntry[] _singleScene = new SceneEntry[1];
#endregion
#region Properties
/// <summary>
/// Gets or sets the action to perform during entry transitions (fade in).
/// </summary>
/// <value>Action that takes a float parameter (0-1) for transition progress.</value>
public Action<float> EntryTransition { get; set; }
/// <summary>
/// Gets or sets the action to perform during exit transitions (fade out).
/// </summary>
/// <value>Action that takes a float parameter (0-1) for transition progress.</value>
public Action<float> ExitTransition { get; set; }
#endregion
#region Constructor & Initialization
[Inject]
public SceneLoader(SceneConfigurations config, LifetimeScope scope, UIGroup transitionScreen)
{
_config = config;
_scope = scope;
_transitionScreen = transitionScreen;
}
/// <summary>
/// If the SceneLoader scene is the only loaded scene, loads all loadOnStart scenes.
/// Otherwise, only adds the loaded scenes into the active scenes data structure.
/// </summary>
public void Initialize()
{
EntryTransition ??= t => _transitionScreen.Opacity = t;
ExitTransition ??= t => _transitionScreen.Opacity = 1 - t;
bool blankSlate = true;
foreach (SceneEntry scene in _config.Scenes)
{
if (!IsSceneLoaded(scene))
{
continue;
}
_activeScenes.Add(scene);
if (scene.type != SceneType.SceneLoader)
{
blankSlate = false;
}
}
if (!blankSlate)
{
return;
}
List<SceneEntry> loadOnStartScenes = _config.Scenes
.Where(s => s.loadOnStart && s.type != SceneType.SceneLoader)
.ToList();
if (loadOnStartScenes.Count > 0)
{
LoadScenesAsync(loadOnStartScenes)
.ContinueWith(() =>
{
Scene firstLoaded = SceneManager.GetSceneByName(loadOnStartScenes[0].sceneName);
if (firstLoaded.isLoaded)
{
SceneManager.SetActiveScene(firstLoaded);
}
})
.Forget();
}
}
#endregion
#region Public Methods
/// <summary>
/// Unloads all but the SceneLoader scene and reloads all scenes marked to load on start.
/// </summary>
/// <returns>A UniTask representing the asynchronous reset operation.</returns>
public UniTask ResetScenes()
{
List<SceneEntry> scenesToUnload = _activeScenes[SceneMask.InverseMask(SceneType.SceneLoader)].ToList();
List<SceneEntry> scenesToLoad =
_config.Scenes.Where(scene => scene.loadOnStart && !IsSceneLoaded(scene)).ToList();
return SwitchScene(scenesToLoad, scenesToUnload);
}
/// <summary>
/// Gets all active scenes of requested type.
/// </summary>
/// <param name="type">The scene type to filter by.</param>
/// <returns>An enumerable of active scenes of the specified type.</returns>
public IEnumerable<SceneEntry> GetActiveScenes(SceneType type)
{
return _activeScenes[type];
}
/// <summary>
/// Unloads all dynamic scenes, reloads ConstantReload scenes and loads the given scene.
/// </summary>
/// <param name="sceneEntryIndex">The index of the SceneEntry in the SceneLoader.</param>
/// <param name="specificScenesToUnload">Optional: specific scenes to unload. This is instead of the dynamic scenes unload</param>
/// <returns>A UniTask representing the asynchronous scene switch operation.</returns>
public UniTask SwitchScene(int sceneEntryIndex,
IReadOnlyCollection<SceneEntry> specificScenesToUnload = null) =>
SwitchScene(_config.Scenes[sceneEntryIndex], specificScenesToUnload);
/// <summary>
/// Unloads all dynamic scenes, reloads ConstantReload scenes and loads the given scene.
/// </summary>
/// <param name="scene">Scene to load</param>
/// <param name="specificScenesToUnload">Optional: specific scenes to unload. This is instead of the dynamic scenes unload</param>
/// <returns>A UniTask representing the asynchronous scene switch operation.</returns>
public UniTask SwitchScene(SceneEntry scene, IReadOnlyCollection<SceneEntry> specificScenesToUnload = null)
{
_singleScene[0] = scene;
return SwitchScene(_singleScene, specificScenesToUnload);
}
/// <summary>
/// Unloads all dynamic scenes, reloads ConstantReload scenes and loads the given scene.
/// </summary>
/// <param name="newScenes">Scenes to load</param>
/// <param name="specificScenesToUnload">Optional: specific scenes to unload. This is instead of the dynamic scenes unload</param>
/// <returns>A UniTask representing the asynchronous scene switch operation.</returns>
public async UniTask SwitchScene(IReadOnlyCollection<SceneEntry> newScenes,
IReadOnlyCollection<SceneEntry> specificScenesToUnload = null)
{
await UniTaskUtils.Interpolate(EntryTransition, _config.SceneSwitchFadeDuration, ignoreTimeScale: true);
UniTask minLoadTimer = UniTaskUtils.Delay(_config.MinLoadTime);
if (specificScenesToUnload != null)
{
await UnloadScenesAsync(specificScenesToUnload);
}
else
{
await UnloadScenesAsync(SceneType.Dynamic);
}
if (_activeScenes[SceneType.ConstantReload].Any())
{
await ReloadScenesAsync();
}
await UniTask.WhenAll(LoadScenesAsync(newScenes), minLoadTimer);
if (newScenes.Count > 0)
{
Scene firstLoaded = SceneManager.GetSceneByName(newScenes.First().sceneName);
if (firstLoaded.isLoaded)
{
SceneManager.SetActiveScene(firstLoaded);
}
}
await UniTaskUtils.Interpolate(ExitTransition, _config.SceneSwitchFadeDuration, ignoreTimeScale: true);
}
/// <summary>
/// Loads the given scene additively.
/// </summary>
/// <param name="sceneEntryIndex">The index of the SceneEntry in the SceneLoader.</param>
public UniTask LoadScene(int sceneEntryIndex) => LoadScene(_config.Scenes[sceneEntryIndex]);
/// <summary>
/// Unloads the given scene by index.
/// </summary>
/// <param name="sceneEntryIndex">The index of the SceneEntry in the SceneLoader.</param>
public UniTask UnloadScene(int sceneEntryIndex) => UnloadScene(_config.Scenes[sceneEntryIndex]);
/// <summary>
/// Unloads the given scene.
/// </summary>
/// <param name="scene">The scene entry to unload.</param>
public UniTask UnloadScene(SceneEntry scene)
{
_activeScenes.Remove(scene);
return SceneManager.UnloadSceneAsync(scene.sceneName).ToUniTask();
}
/// <summary>
/// Unloads the given scenes.
/// </summary>
/// <param name="scenes">The collection of scene entries to unload.</param>
public UniTask UnloadScenes(IEnumerable<SceneEntry> scenes)
{
UniTask[] sceneUnloadTasks = new UniTask[scenes.Count()];
int i = 0;
foreach (SceneEntry scene in scenes)
{
sceneUnloadTasks[i++] = SceneManager.UnloadSceneAsync(scene.sceneName).ToUniTask();
_activeScenes.Remove(scene);
}
return UniTask.WhenAll(sceneUnloadTasks);
}
/// <summary>
/// Unloads all active dynamic scenes asynchronously.
/// </summary>
public UniTask UnloadDynamicScenes()
{
return UnloadScenesAsync(SceneType.Dynamic);
}
/// <summary>
/// Unloads the given scene by SceneEntry.
/// </summary>
public async UniTask LoadScene(SceneEntry scene)
{
try
{
using (LifetimeScope.EnqueueParent(_scope))
{
await SceneManager.LoadSceneAsync(scene.sceneName, LoadSceneMode.Additive);
}
}
catch (Exception e)
{
Debug.LogError("Failed to inject scene. Resolving to load scene without injection: " + e.Message);
await SceneManager.LoadSceneAsync(scene.sceneName, LoadSceneMode.Additive);
}
_activeScenes.Add(scene);
}
#endregion
#region Private Methods
/// <summary>
/// Checks if a scene is currently loaded in the SceneManager.
/// </summary>
/// <param name="scene">The scene entry to check.</param>
/// <returns>True if the scene is loaded, false otherwise.</returns>
private bool IsSceneLoaded(SceneEntry scene)
{
int numOfLoadedScenes = SceneManager.sceneCount;
for (int i = 0; i < numOfLoadedScenes; ++i)
{
if (SceneManager.GetSceneAt(i).name == scene.sceneName)
{
return true;
}
}
return false;
}
/// <summary>
/// Reloads all constant reload scenes asynchronously.
/// </summary>
/// <returns>A UniTask representing the asynchronous reload operation.</returns>
private async UniTask ReloadScenesAsync()
{
HashSet<SceneEntry> constantReloadScenes = _activeScenes[SceneType.ConstantReload];
int constantReloadCount = constantReloadScenes.Count;
var tasks = new UniTask[constantReloadCount];
int i = 0;
foreach (SceneEntry reloadScene in constantReloadScenes)
{
tasks[i++] = SceneManager.UnloadSceneAsync(reloadScene.sceneName).ToUniTask();
}
await UniTask.WhenAll(tasks);
i = 0;
foreach (SceneEntry reloadScene in constantReloadScenes)
{
tasks[i++] = LoadScene(reloadScene);
}
await UniTask.WhenAll(tasks);
}
/// <summary>
/// Unloads the specified scenes asynchronously.
/// </summary>
/// <param name="scenes">The collection of scenes to unload.</param>
/// <returns>A UniTask representing the asynchronous unload operation.</returns>
private async UniTask UnloadScenesAsync(IReadOnlyCollection<SceneEntry> scenes)
{
int sceneCount = scenes.Count;
var tasks = new UniTask[sceneCount];
int i = 0;
foreach (SceneEntry scene in scenes)
{
tasks[i++] = SceneManager.UnloadSceneAsync(scene.sceneName)
.ToUniTask()
.ContinueWith(() => _activeScenes.Remove(scene));
}
await UniTask.WhenAll(tasks);
}
/// <summary>
/// Unloads all scenes of the specified type asynchronously.
/// </summary>
/// <param name="type">The scene type to unload.</param>
/// <returns>A UniTask representing the asynchronous unload operation.</returns>
private UniTask UnloadScenesAsync(SceneType type) => UnloadScenesAsync(_activeScenes[type]);
/// <summary>
/// Loads the specified scenes asynchronously.
/// </summary>
/// <param name="scenes">The collection of scenes to load.</param>
/// <returns>A UniTask representing the asynchronous load operation.</returns>
private async UniTask LoadScenesAsync(IReadOnlyCollection<SceneEntry> scenes)
{
int sceneCount = scenes.Count;
var tasks = new UniTask[sceneCount];
int i = 0;
foreach (SceneEntry scene in scenes)
{
tasks[i++] = LoadScene(scene);
}
await UniTask.WhenAll(tasks);
}
#endregion
}
}