Skip to content

Commit 6b0a428

Browse files
authored
Merge pull request #14 from mygamedevtools/feature/hybrid-transitions
[Feature] Add transitions from external scenes
2 parents f33c852 + eaff22a commit 6b0a428

File tree

6 files changed

+182
-94
lines changed

6 files changed

+182
-94
lines changed

Packages/mygamedevtools-scene-loader/Runtime/Interfaces/ISceneLoader.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* Created on: 7/16/2022 (en-US)
55
*/
66

7+
using UnityEngine.SceneManagement;
8+
79
namespace MyGameDevTools.SceneLoading
810
{
911
/// <summary>
@@ -22,6 +24,8 @@ public interface ISceneLoader
2224
/// It will transition from the current active scene (<see cref="ISceneManager.GetActiveScene()"/>)
2325
/// to the target scene (<paramref name="targetSceneInfo"/>), with an optional intermediate loading scene (<paramref name="intermediateSceneInfo"/>).
2426
/// If the <paramref name="intermediateSceneInfo"/> is not set, the transition will have no intermediate loading scene and will instead simply load the target scene directly.
27+
/// Also, you can provide a scene that wasn't loaded from this scene loader to transition from, as the <paramref name="externalOriginScene"/>,
28+
/// instead of transitioning from the current active scene.
2529
/// The complete transition flow is:
2630
/// <br/><br/>
2731
/// 1. Load the intermediate scene (if provided).<br/>
@@ -36,7 +40,10 @@ public interface ISceneLoader
3640
/// A reference to the scene that's going to be loaded as the transition intermediate (as a loading scene).
3741
/// If null, the transition will not have an intermediate loading scene.
3842
/// </param>
39-
void TransitionToScene(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo = null);
43+
/// <param name="externalOriginScene">
44+
/// A reference to a scene loaded outside of this scene loader, instead of taking the current active scene as the origin scene.
45+
/// </param>
46+
void TransitionToScene(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo = null, Scene externalOriginScene = default);
4047

4148
/// <summary>
4249
/// Unloads the given scene from the current scene stack.
@@ -48,12 +55,12 @@ public interface ISceneLoader
4855

4956
/// <summary>
5057
/// Loads a scene additively on top of the current scene stack, optionally marking it as the active scene
51-
/// (<see cref="ISceneManager.SetActiveScene(UnityEngine.SceneManagement.Scene)"/>).
58+
/// (<see cref="ISceneManager.SetActiveScene(Scene)"/>).
5259
/// </summary>
5360
/// <param name="sceneInfo">
5461
/// Reference to the scene that's going to be loaded.
5562
/// </param>
56-
/// <param name="setActive">Should the loaded scene be marked as active? Equivalent to calling <see cref="ISceneManager.SetActiveScene(UnityEngine.SceneManagement.Scene)"/>.</param>
63+
/// <param name="setActive">Should the loaded scene be marked as active? Equivalent to calling <see cref="ISceneManager.SetActiveScene(Scene)"/>.</param>
5764
void LoadScene(ILoadSceneInfo sceneInfo, bool setActive = false);
5865
}
5966
}

Packages/mygamedevtools-scene-loader/Runtime/Interfaces/ISceneLoaderAsync.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
*/
66

77
using System;
8+
using UnityEngine.SceneManagement;
89

910
namespace MyGameDevTools.SceneLoading
1011
{
1112
/// <summary>
1213
/// Interface to standardize async scene loading operations.
13-
/// <typeparamref name="TAsync"/> can be a <see cref="UnityEngine.Coroutine"/> or an awaitable type that returns <see cref="UnityEngine.SceneManagement.Scene"/>, such as
14+
/// <typeparamref name="TAsync"/> can be a <see cref="UnityEngine.Coroutine"/> or an awaitable type that returns <see cref="Scene"/>, such as
1415
/// <see cref="System.Threading.Tasks.ValueTask{T}"/>.
1516
/// </summary>
1617
public interface ISceneLoaderAsync<TAsync> : ISceneLoader
1718
{
1819
/// <summary>
19-
/// Async version of the <see cref="ISceneLoader.TransitionToScene(ILoadSceneInfo, ILoadSceneInfo)"/>.
20+
/// Async version of the <see cref="ISceneLoader.TransitionToScene(ILoadSceneInfo, ILoadSceneInfo, Scene)"/>.
2021
/// </summary>
2122
/// <param name="targetSceneReference">
2223
/// A reference to the scene that's going to be transitioned to.
@@ -25,10 +26,13 @@ public interface ISceneLoaderAsync<TAsync> : ISceneLoader
2526
/// A reference to the scene that's going to be loaded as the transition intermediate (as a loading scene).
2627
/// If null, the transition will not have an intermediate loading scene.
2728
/// </param>
29+
/// <param name="externalOriginScene">
30+
/// A reference to a scene loaded outside of this scene loader, instead of taking the current active scene as the origin scene.
31+
/// </param>
2832
/// <returns>
2933
/// The loading operation.
3034
/// </returns>
31-
TAsync TransitionToSceneAsync(ILoadSceneInfo targetSceneReference, ILoadSceneInfo intermediateSceneReference = default);
35+
TAsync TransitionToSceneAsync(ILoadSceneInfo targetSceneReference, ILoadSceneInfo intermediateSceneReference = default, Scene externalOriginScene = default);
3236

3337
/// <summary>
3438
/// Async version of the <see cref="ISceneLoader.LoadScene(ILoadSceneInfo, bool)"/>.
@@ -37,7 +41,7 @@ public interface ISceneLoaderAsync<TAsync> : ISceneLoader
3741
/// Reference to the scene that's going to be loaded.
3842
/// </param>
3943
/// <param name="setActive">
40-
/// Should the loaded scene be marked as active? Equivalent to calling <see cref="ISceneManager.SetActiveScene(UnityEngine.SceneManagement.Scene)"/>.
44+
/// Should the loaded scene be marked as active? Equivalent to calling <see cref="ISceneManager.SetActiveScene(Scene)"/>.
4145
/// </param>
4246
/// <param name="progress">
4347
/// Optional <see cref="IProgress{T}"/> reference to report the scene loading progress (ranging from 0 to 1).

Packages/mygamedevtools-scene-loader/Runtime/SceneLoaders/SceneLoaderAsync.cs

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,59 +22,77 @@ public SceneLoaderAsync(ISceneManager manager)
2222
_manager = manager ?? throw new ArgumentNullException("Cannot create a scene loader with a null Scene Manager");
2323
}
2424

25-
public void TransitionToScene(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo) => TransitionToSceneAsync(targetSceneInfo, intermediateSceneInfo);
25+
public void TransitionToScene(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, Scene externalOriginScene) => TransitionToSceneAsync(targetSceneInfo, intermediateSceneInfo, externalOriginScene);
2626

2727
public void UnloadScene(ILoadSceneInfo sceneInfo) => _ = UnloadSceneAsync(sceneInfo);
2828

2929
public void LoadScene(ILoadSceneInfo sceneInfo, bool setActive) => _ = LoadSceneAsync(sceneInfo, setActive);
3030

31-
public ValueTask<Scene> TransitionToSceneAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo) => intermediateSceneInfo == null ? TransitionDirectlyAsync(targetSceneInfo) : TransitionWithIntermediateAsync(targetSceneInfo, intermediateSceneInfo);
31+
public ValueTask<Scene> TransitionToSceneAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, Scene externalOriginScene) => intermediateSceneInfo == null ? TransitionDirectlyAsync(targetSceneInfo, externalOriginScene) : TransitionWithIntermediateAsync(targetSceneInfo, intermediateSceneInfo, externalOriginScene);
3232

3333
public ValueTask<Scene> LoadSceneAsync(ILoadSceneInfo sceneInfo, bool setActive = false, IProgress<float> progress = null) => _manager.LoadSceneAsync(sceneInfo, setActive, progress);
3434

3535
public ValueTask<Scene> UnloadSceneAsync(ILoadSceneInfo sceneInfo) => _manager.UnloadSceneAsync(sceneInfo);
3636

37-
async ValueTask<Scene> TransitionWithIntermediateAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo)
37+
async ValueTask<Scene> TransitionDirectlyAsync(ILoadSceneInfo loadSceneInfo, Scene externalOriginScene)
3838
{
39-
var currentScene = _manager.GetActiveScene();
39+
var externalOrigin = externalOriginScene.IsValid();
40+
var currentScene = externalOrigin ? externalOriginScene : _manager.GetActiveScene();
41+
await UnloadCurrentScene(currentScene, externalOrigin);
42+
return await LoadSceneAsync(loadSceneInfo, true);
43+
}
44+
45+
async ValueTask<Scene> TransitionWithIntermediateAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, Scene externalOriginScene)
46+
{
47+
var externalOrigin = externalOriginScene.IsValid();
48+
var currentScene = externalOrigin ? externalOriginScene : _manager.GetActiveScene();
4049
await _manager.LoadSceneAsync(intermediateSceneInfo);
4150

4251
var loadingBehavior = Object.FindObjectOfType<LoadingBehavior>();
43-
Scene loadedScene;
44-
if (loadingBehavior)
45-
{
46-
var progress = loadingBehavior.Progress;
47-
while (progress.State != LoadingState.Loading)
48-
await Task.Yield();
52+
return loadingBehavior
53+
? await TransitionWithIntermediateLoadingAsync(targetSceneInfo, intermediateSceneInfo, loadingBehavior, currentScene, externalOrigin)
54+
: await TransitionWithIntermediateNoLoadingAsync(targetSceneInfo, intermediateSceneInfo, currentScene, externalOrigin);
55+
}
4956

50-
if (currentScene.IsValid())
51-
await UnloadSceneAsync(new LoadSceneInfoScene(currentScene));
57+
async ValueTask<Scene> TransitionWithIntermediateLoadingAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, LoadingBehavior loadingBehavior, Scene currentScene, bool externalOrigin)
58+
{
59+
var progress = loadingBehavior.Progress;
60+
while (progress.State != LoadingState.Loading)
61+
await Task.Yield();
5262

53-
loadedScene = await _manager.LoadSceneAsync(targetSceneInfo, true, progress);
54-
progress.SetState(LoadingState.TargetSceneLoaded);
63+
await UnloadCurrentScene(currentScene, externalOrigin);
5564

56-
while (progress.State != LoadingState.TransitionComplete)
57-
await Task.Yield();
65+
var loadedScene = await _manager.LoadSceneAsync(targetSceneInfo, true, progress);
66+
progress.SetState(LoadingState.TargetSceneLoaded);
5867

59-
_ = UnloadSceneAsync(intermediateSceneInfo);
60-
}
61-
else
62-
{
63-
if (currentScene.IsValid())
64-
await UnloadSceneAsync(new LoadSceneInfoScene(currentScene));
65-
loadedScene = await LoadSceneAsync(targetSceneInfo, true);
66-
_ = UnloadSceneAsync(intermediateSceneInfo);
67-
}
68+
while (progress.State != LoadingState.TransitionComplete)
69+
await Task.Yield();
6870

71+
_ = UnloadSceneAsync(intermediateSceneInfo);
6972
return loadedScene;
7073
}
7174

72-
async ValueTask<Scene> TransitionDirectlyAsync(ILoadSceneInfo loadSceneInfo)
75+
async ValueTask<Scene> TransitionWithIntermediateNoLoadingAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, Scene currentScene, bool externalOrigin)
7376
{
74-
var currentScene = _manager.GetActiveScene();
75-
if (currentScene.IsValid())
76-
await UnloadSceneAsync(new LoadSceneInfoScene(currentScene));
77-
return await LoadSceneAsync(loadSceneInfo, true);
77+
await UnloadCurrentScene(currentScene, externalOrigin);
78+
var loadedScene = await LoadSceneAsync(targetSceneInfo, true);
79+
_ = UnloadSceneAsync(intermediateSceneInfo);
80+
return loadedScene;
81+
}
82+
83+
async ValueTask UnloadCurrentScene(Scene currentScene, bool externalOrigin)
84+
{
85+
if (!currentScene.IsValid())
86+
return;
87+
88+
if (externalOrigin)
89+
{
90+
var operation = UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(currentScene);
91+
while (!operation.isDone)
92+
await Task.Yield();
93+
}
94+
else
95+
await _manager.UnloadSceneAsync(new LoadSceneInfoScene(currentScene));
7896
}
7997
}
8098
}

Packages/mygamedevtools-scene-loader/Runtime/SceneLoaders/SceneLoaderCoroutine.cs

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
* Created on: 9/4/2022 (en-US)
55
*/
66

7+
using Cysharp.Threading.Tasks;
78
using System;
89
using System.Collections;
910
using UnityEngine;
11+
using UnityEngine.SceneManagement;
1012
using Object = UnityEngine.Object;
1113

1214
namespace MyGameDevTools.SceneLoading
@@ -22,13 +24,13 @@ public SceneLoaderCoroutine(ISceneManager manager)
2224
_manager = manager ?? throw new ArgumentNullException("Cannot create a scene loader with a null Scene Manager");
2325
}
2426

25-
public void TransitionToScene(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo) => TransitionToSceneAsync(targetSceneInfo, intermediateSceneInfo);
27+
public void TransitionToScene(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, Scene externalOriginScene) => TransitionToSceneAsync(targetSceneInfo, intermediateSceneInfo, externalOriginScene);
2628

2729
public void UnloadScene(ILoadSceneInfo sceneInfo) => UnloadSceneAsync(sceneInfo);
2830

2931
public void LoadScene(ILoadSceneInfo sceneInfo, bool setActive) => LoadSceneAsync(sceneInfo, setActive);
3032

31-
public Coroutine TransitionToSceneAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo) => RoutineBehaviour.Instance.StartCoroutine(intermediateSceneInfo == null ? TransitionDirectlyRoutine(targetSceneInfo) : TransitionWithIntermediateRoutine(targetSceneInfo, intermediateSceneInfo));
33+
public Coroutine TransitionToSceneAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, Scene externalOriginScene) => RoutineBehaviour.Instance.StartCoroutine(intermediateSceneInfo == null ? TransitionDirectlyRoutine(targetSceneInfo, externalOriginScene) : TransitionWithIntermediateRoutine(targetSceneInfo, intermediateSceneInfo, externalOriginScene));
3234

3335
public Coroutine UnloadSceneAsync(ILoadSceneInfo sceneInfo) => RoutineBehaviour.Instance.StartCoroutine(UnloadRoutine(sceneInfo));
3436

@@ -44,42 +46,57 @@ IEnumerator UnloadRoutine(ILoadSceneInfo sceneInfo)
4446
yield return new WaitTask(_manager.UnloadSceneAsync(sceneInfo).AsTask());
4547
}
4648

47-
IEnumerator TransitionWithIntermediateRoutine(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo)
49+
IEnumerator TransitionDirectlyRoutine(ILoadSceneInfo targetSceneInfo, Scene externalOriginScene)
4850
{
49-
var currentScene = _manager.GetActiveScene();
51+
var externalOrigin = externalOriginScene.IsValid();
52+
var currentScene = externalOrigin ? externalOriginScene : _manager.GetActiveScene();
53+
yield return UnloadCurrentScene(currentScene, externalOrigin);
54+
yield return LoadRoutine(targetSceneInfo, true, null);
55+
}
56+
57+
IEnumerator TransitionWithIntermediateRoutine(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, Scene externalOriginScene)
58+
{
59+
var externalOrigin = externalOriginScene.IsValid();
60+
var currentScene = externalOrigin ? externalOriginScene : _manager.GetActiveScene();
5061
yield return new WaitTask(_manager.LoadSceneAsync(intermediateSceneInfo).AsTask());
5162

5263
var loadingBehavior = Object.FindObjectOfType<LoadingBehavior>();
53-
if (loadingBehavior)
54-
{
55-
var progress = loadingBehavior.Progress;
56-
yield return new WaitUntil(() => progress.State == LoadingState.Loading);
64+
yield return loadingBehavior
65+
? TransitionWithIntermediateLoadingAsync(targetSceneInfo, intermediateSceneInfo, loadingBehavior, currentScene, externalOrigin)
66+
: TransitionWithIntermediateNoLoadingAsync(targetSceneInfo, intermediateSceneInfo, currentScene, externalOrigin);
67+
}
68+
69+
IEnumerator TransitionWithIntermediateLoadingAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, LoadingBehavior loadingBehavior, Scene currentScene, bool externalOrigin)
70+
{
71+
var progress = loadingBehavior.Progress;
72+
yield return new WaitUntil(() => progress.State == LoadingState.Loading);
5773

58-
if (currentScene.IsValid())
59-
yield return UnloadRoutine(new LoadSceneInfoScene(currentScene));
74+
yield return UnloadCurrentScene(currentScene, externalOrigin);
6075

61-
yield return new WaitTask(_manager.LoadSceneAsync(targetSceneInfo, true, progress).AsTask());
62-
progress.SetState(LoadingState.TargetSceneLoaded);
76+
yield return new WaitTask(_manager.LoadSceneAsync(targetSceneInfo, true, progress).AsTask());
77+
progress.SetState(LoadingState.TargetSceneLoaded);
6378

64-
yield return new WaitUntil(() => progress.State == LoadingState.TransitionComplete);
79+
yield return new WaitUntil(() => progress.State == LoadingState.TransitionComplete);
6580

66-
UnloadSceneAsync(intermediateSceneInfo);
67-
}
68-
else
69-
{
70-
if (currentScene.IsValid())
71-
yield return UnloadRoutine(new LoadSceneInfoScene(currentScene));
72-
yield return LoadRoutine(targetSceneInfo, true, null);
73-
UnloadSceneAsync(intermediateSceneInfo);
74-
}
81+
UnloadSceneAsync(intermediateSceneInfo);
7582
}
7683

77-
IEnumerator TransitionDirectlyRoutine(ILoadSceneInfo targetSceneInfo)
84+
IEnumerator TransitionWithIntermediateNoLoadingAsync(ILoadSceneInfo targetSceneInfo, ILoadSceneInfo intermediateSceneInfo, Scene currentScene, bool externalOrigin)
7885
{
79-
var currentScene = _manager.GetActiveScene();
80-
if (currentScene.IsValid())
81-
yield return UnloadRoutine(new LoadSceneInfoScene(currentScene));
86+
yield return UnloadCurrentScene(currentScene, externalOrigin);
8287
yield return LoadRoutine(targetSceneInfo, true, null);
88+
UnloadSceneAsync(intermediateSceneInfo);
89+
}
90+
91+
IEnumerator UnloadCurrentScene(Scene currentScene, bool externalOrigin)
92+
{
93+
if (!currentScene.IsValid())
94+
yield break;
95+
96+
if (externalOrigin)
97+
yield return UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(currentScene);
98+
else
99+
yield return UnloadRoutine(new LoadSceneInfoScene(currentScene));
83100
}
84101
}
85102
}

0 commit comments

Comments
 (0)