From 4ca9346914cb8c9ae516c96e1fa7a58a2a873896 Mon Sep 17 00:00:00 2001 From: Edvinas01 Date: Thu, 19 May 2022 11:32:49 +0300 Subject: [PATCH] Initial commit --- CHANGELOG.md | 31 ++ CHANGELOG.md.meta | 7 + Documentation~/README.md | 2 + Editor.meta | 8 + Editor/CHARK.ScriptableScenes.Editor.asmdef | 18 + .../CHARK.ScriptableScenes.Editor.asmdef.meta | 7 + Editor/PropertyDrawers.meta | 3 + .../PropertyDrawers/ReadOnlyPropertyDrawer.cs | 23 ++ .../ReadOnlyPropertyDrawer.cs.meta | 3 + Editor/SceneReferenceCollectionEditor.cs | 87 +++++ Editor/SceneReferenceCollectionEditor.cs.meta | 3 + Editor/ScriptableSceneManagerWindow.cs | 331 ++++++++++++++++++ Editor/ScriptableSceneManagerWindow.cs.meta | 3 + ...riptableSceneManagerWindowPostprocessor.cs | 27 ++ ...bleSceneManagerWindowPostprocessor.cs.meta | 3 + Editor/Utilities.meta | 3 + .../ScriptableSceneEditorUtilities.cs | 238 +++++++++++++ .../ScriptableSceneEditorUtilities.cs.meta | 3 + Editor/Utilities/ScriptableSceneGUI.cs | 60 ++++ Editor/Utilities/ScriptableSceneGUI.cs.meta | 3 + LICENSE.md | 21 ++ LICENSE.md.meta | 7 + Runtime.meta | 8 + Runtime/AssemblyInfo.cs | 4 + Runtime/AssemblyInfo.cs.meta | 3 + Runtime/BaseScriptableScene.cs | 66 ++++ Runtime/BaseScriptableScene.cs.meta | 3 + Runtime/BaseScriptableSceneCollection.cs | 72 ++++ Runtime/BaseScriptableSceneCollection.cs.meta | 3 + Runtime/CHARK.ScriptableScenes.asmdef | 4 + Runtime/CHARK.ScriptableScenes.asmdef.meta | 7 + Runtime/Events.meta | 8 + Runtime/Events/CollectionEventArgs.cs | 97 +++++ Runtime/Events/CollectionEventArgs.cs.meta | 3 + Runtime/Events/CollectionEventHandler.cs | 161 +++++++++ Runtime/Events/CollectionEventHandler.cs.meta | 3 + .../Events/CollectionEventHandlerDelegates.cs | 17 + .../CollectionEventHandlerDelegates.cs.meta | 3 + Runtime/Events/ICollectionEventHandler.cs | 34 ++ .../Events/ICollectionEventHandler.cs.meta | 3 + Runtime/Events/ISceneEventHandler.cs | 44 +++ Runtime/Events/ISceneEventHandler.cs.meta | 3 + Runtime/Events/SceneEventArgs.cs | 104 ++++++ Runtime/Events/SceneEventArgs.cs.meta | 3 + Runtime/Events/SceneEventHandler.cs | 201 +++++++++++ Runtime/Events/SceneEventHandler.cs.meta | 3 + Runtime/Events/SceneEventHandlerDelegates.cs | 22 ++ .../Events/SceneEventHandlerDelegates.cs.meta | 3 + Runtime/PropertyAttributes.meta | 3 + .../PropertyAttributes/ReadOnlyAttribute.cs | 12 + .../ReadOnlyAttribute.cs.meta | 3 + Runtime/ScriptableScene.cs | 167 +++++++++ Runtime/ScriptableScene.cs.meta | 3 + Runtime/ScriptableSceneCollection.cs | 198 +++++++++++ Runtime/ScriptableSceneCollection.cs.meta | 3 + Runtime/ScriptableSceneController.cs | 229 ++++++++++++ Runtime/ScriptableSceneController.cs.meta | 3 + Runtime/ScriptableSceneControllerDebugger.cs | 218 ++++++++++++ .../ScriptableSceneControllerDebugger.cs.meta | 3 + Runtime/Transitions.meta | 3 + .../BaseScriptableSceneTransition.cs | 28 ++ .../BaseScriptableSceneTransition.cs.meta | 3 + Runtime/Transitions/FadeCanvas.cs | 90 +++++ Runtime/Transitions/FadeCanvas.cs.meta | 12 + .../FadeScriptableSceneTransition.cs | 140 ++++++++ .../FadeScriptableSceneTransition.cs.meta | 3 + Runtime/Utilities.meta | 3 + .../Utilities/AddComponentMenuConstants.cs | 20 ++ .../AddComponentMenuConstants.cs.meta | 3 + Runtime/Utilities/CreateAssetMenuConstants.cs | 30 ++ .../CreateAssetMenuConstants.cs.meta | 3 + Runtime/Utilities/MenuItemConstants.cs | 20 ++ Runtime/Utilities/MenuItemConstants.cs.meta | 3 + Runtime/Utilities/ScriptableSceneUtilities.cs | 259 ++++++++++++++ .../ScriptableSceneUtilities.cs.meta | 3 + .../CHARK.ScriptableScenes.Samples.asmdef | 16 + ...CHARK.ScriptableScenes.Samples.asmdef.meta | 7 + Tests.meta | 3 + Tests/Runtime.meta | 3 + .../CHARK.ScriptableScenes.Tests.asmdef | 22 ++ .../CHARK.ScriptableScenes.Tests.asmdef.meta | 7 + Tests/Runtime/ReflectionUtilities.cs | 90 +++++ Tests/Runtime/ReflectionUtilities.cs.meta | 3 + .../ScriptableSceneControllerEventTest.cs | 234 +++++++++++++ ...ScriptableSceneControllerEventTest.cs.meta | 3 + .../Runtime/ScriptableSceneControllerTest.cs | 73 ++++ .../ScriptableSceneControllerTest.cs.meta | 3 + Tests/Runtime/ScriptableSceneTestUtilities.cs | 117 +++++++ .../ScriptableSceneTestUtilities.cs.meta | 3 + package.json | 19 + package.json.meta | 7 + 91 files changed, 3848 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CHANGELOG.md.meta create mode 100644 Documentation~/README.md create mode 100644 Editor.meta create mode 100644 Editor/CHARK.ScriptableScenes.Editor.asmdef create mode 100644 Editor/CHARK.ScriptableScenes.Editor.asmdef.meta create mode 100644 Editor/PropertyDrawers.meta create mode 100644 Editor/PropertyDrawers/ReadOnlyPropertyDrawer.cs create mode 100644 Editor/PropertyDrawers/ReadOnlyPropertyDrawer.cs.meta create mode 100644 Editor/SceneReferenceCollectionEditor.cs create mode 100644 Editor/SceneReferenceCollectionEditor.cs.meta create mode 100644 Editor/ScriptableSceneManagerWindow.cs create mode 100644 Editor/ScriptableSceneManagerWindow.cs.meta create mode 100644 Editor/ScriptableSceneManagerWindowPostprocessor.cs create mode 100644 Editor/ScriptableSceneManagerWindowPostprocessor.cs.meta create mode 100644 Editor/Utilities.meta create mode 100644 Editor/Utilities/ScriptableSceneEditorUtilities.cs create mode 100644 Editor/Utilities/ScriptableSceneEditorUtilities.cs.meta create mode 100644 Editor/Utilities/ScriptableSceneGUI.cs create mode 100644 Editor/Utilities/ScriptableSceneGUI.cs.meta create mode 100644 LICENSE.md create mode 100644 LICENSE.md.meta create mode 100644 Runtime.meta create mode 100644 Runtime/AssemblyInfo.cs create mode 100644 Runtime/AssemblyInfo.cs.meta create mode 100644 Runtime/BaseScriptableScene.cs create mode 100644 Runtime/BaseScriptableScene.cs.meta create mode 100644 Runtime/BaseScriptableSceneCollection.cs create mode 100644 Runtime/BaseScriptableSceneCollection.cs.meta create mode 100644 Runtime/CHARK.ScriptableScenes.asmdef create mode 100644 Runtime/CHARK.ScriptableScenes.asmdef.meta create mode 100644 Runtime/Events.meta create mode 100644 Runtime/Events/CollectionEventArgs.cs create mode 100644 Runtime/Events/CollectionEventArgs.cs.meta create mode 100644 Runtime/Events/CollectionEventHandler.cs create mode 100644 Runtime/Events/CollectionEventHandler.cs.meta create mode 100644 Runtime/Events/CollectionEventHandlerDelegates.cs create mode 100644 Runtime/Events/CollectionEventHandlerDelegates.cs.meta create mode 100644 Runtime/Events/ICollectionEventHandler.cs create mode 100644 Runtime/Events/ICollectionEventHandler.cs.meta create mode 100644 Runtime/Events/ISceneEventHandler.cs create mode 100644 Runtime/Events/ISceneEventHandler.cs.meta create mode 100644 Runtime/Events/SceneEventArgs.cs create mode 100644 Runtime/Events/SceneEventArgs.cs.meta create mode 100644 Runtime/Events/SceneEventHandler.cs create mode 100644 Runtime/Events/SceneEventHandler.cs.meta create mode 100644 Runtime/Events/SceneEventHandlerDelegates.cs create mode 100644 Runtime/Events/SceneEventHandlerDelegates.cs.meta create mode 100644 Runtime/PropertyAttributes.meta create mode 100644 Runtime/PropertyAttributes/ReadOnlyAttribute.cs create mode 100644 Runtime/PropertyAttributes/ReadOnlyAttribute.cs.meta create mode 100644 Runtime/ScriptableScene.cs create mode 100644 Runtime/ScriptableScene.cs.meta create mode 100644 Runtime/ScriptableSceneCollection.cs create mode 100644 Runtime/ScriptableSceneCollection.cs.meta create mode 100644 Runtime/ScriptableSceneController.cs create mode 100644 Runtime/ScriptableSceneController.cs.meta create mode 100644 Runtime/ScriptableSceneControllerDebugger.cs create mode 100644 Runtime/ScriptableSceneControllerDebugger.cs.meta create mode 100644 Runtime/Transitions.meta create mode 100644 Runtime/Transitions/BaseScriptableSceneTransition.cs create mode 100644 Runtime/Transitions/BaseScriptableSceneTransition.cs.meta create mode 100644 Runtime/Transitions/FadeCanvas.cs create mode 100644 Runtime/Transitions/FadeCanvas.cs.meta create mode 100644 Runtime/Transitions/FadeScriptableSceneTransition.cs create mode 100644 Runtime/Transitions/FadeScriptableSceneTransition.cs.meta create mode 100644 Runtime/Utilities.meta create mode 100644 Runtime/Utilities/AddComponentMenuConstants.cs create mode 100644 Runtime/Utilities/AddComponentMenuConstants.cs.meta create mode 100644 Runtime/Utilities/CreateAssetMenuConstants.cs create mode 100644 Runtime/Utilities/CreateAssetMenuConstants.cs.meta create mode 100644 Runtime/Utilities/MenuItemConstants.cs create mode 100644 Runtime/Utilities/MenuItemConstants.cs.meta create mode 100644 Runtime/Utilities/ScriptableSceneUtilities.cs create mode 100644 Runtime/Utilities/ScriptableSceneUtilities.cs.meta create mode 100644 Samples~/CHARK.ScriptableScenes.Samples.asmdef create mode 100644 Samples~/CHARK.ScriptableScenes.Samples.asmdef.meta create mode 100644 Tests.meta create mode 100644 Tests/Runtime.meta create mode 100644 Tests/Runtime/CHARK.ScriptableScenes.Tests.asmdef create mode 100644 Tests/Runtime/CHARK.ScriptableScenes.Tests.asmdef.meta create mode 100644 Tests/Runtime/ReflectionUtilities.cs create mode 100644 Tests/Runtime/ReflectionUtilities.cs.meta create mode 100644 Tests/Runtime/ScriptableSceneControllerEventTest.cs create mode 100644 Tests/Runtime/ScriptableSceneControllerEventTest.cs.meta create mode 100644 Tests/Runtime/ScriptableSceneControllerTest.cs create mode 100644 Tests/Runtime/ScriptableSceneControllerTest.cs.meta create mode 100644 Tests/Runtime/ScriptableSceneTestUtilities.cs create mode 100644 Tests/Runtime/ScriptableSceneTestUtilities.cs.meta create mode 100644 package.json create mode 100644 package.json.meta diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..40f01db --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.0.2] - 2022-05-23 +Minor UX updates and bug fixes. + +### Added +- More logging which shows that a scene is being currently loaded. +- Methods to access in `loadingCollection` and `loadedCollection` in `ScriptableSceneController`. +- Exposed `IsLoading` property in `ScriptableSceneController`. + +### Changed +- Updated Scene Manager Window to support reordering and to provide more info. +- Improved component UX via `AddComponentMenu`. + +### Fixed +- Incorrect collection progress being reported when a collection is loading. + +## [0.0.1] - 2022-05-21 +Initial preview version. + +### Added +- `ScriptableScene` - wrapper `ScriptableObject` for `SceneAsset`, which allows referencing scenes without needing to hard-code scene name, path or build index. Click on _Assets > Create > CHARK > Scriptable Scenes > Scriptable Scene_ to create. +- `ScriptableSceneCollection` - container for `ScriptableScene` and is useful to load a set of scenes at once (`SetupScene`, `UIScene`, `GameplayScene`, etc). Click on _Assets > Create > CHARK > Scriptable Scenes > Scriptable Scene Collection_ to create. +- `ScriptableSceneTransition` - `ScriptableObject` that can be used to inject scene transitions. +- `FadeScriptableSceneTransition` - built-in transition which simply fades a canvas in and out (via `FadeCanvas`) during scene loading. +- `FadeCanvas` - built-in component which takes care of actually fading the canvas and subscribing to a `ScriptableSceneTransition`. +- `ScriptableSceneManagerWindow` - Editor Window which can be used to quickly open a set of scene in Edit and also Play mode. Click on _Window > CHARK > Scriptable Scenes > Scriptable Scene Manager_ to open. diff --git a/CHANGELOG.md.meta b/CHANGELOG.md.meta new file mode 100644 index 0000000..4df9818 --- /dev/null +++ b/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c28747f8216ad3c4eb99c42b3d9e6cf4 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/README.md b/Documentation~/README.md new file mode 100644 index 0000000..d68db58 --- /dev/null +++ b/Documentation~/README.md @@ -0,0 +1,2 @@ +# Documentation +Work in progress :( diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..20235be --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a6d15350560b901419434fb44caacbfb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/CHARK.ScriptableScenes.Editor.asmdef b/Editor/CHARK.ScriptableScenes.Editor.asmdef new file mode 100644 index 0000000..27f1b23 --- /dev/null +++ b/Editor/CHARK.ScriptableScenes.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "CHARK.ScriptableScenes.Editor", + "rootNamespace": "CHARK.ScriptableScenes", + "references": [ + "GUID:7cfa357a9d1c66644b9ddbad9d55c0a3" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/CHARK.ScriptableScenes.Editor.asmdef.meta b/Editor/CHARK.ScriptableScenes.Editor.asmdef.meta new file mode 100644 index 0000000..5361019 --- /dev/null +++ b/Editor/CHARK.ScriptableScenes.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1dea93aae78908240a0ecdfb84770568 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/PropertyDrawers.meta b/Editor/PropertyDrawers.meta new file mode 100644 index 0000000..b574e56 --- /dev/null +++ b/Editor/PropertyDrawers.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1cdfdb6bdfb544b49c468723d3bc1589 +timeCreated: 1653303285 \ No newline at end of file diff --git a/Editor/PropertyDrawers/ReadOnlyPropertyDrawer.cs b/Editor/PropertyDrawers/ReadOnlyPropertyDrawer.cs new file mode 100644 index 0000000..f87051d --- /dev/null +++ b/Editor/PropertyDrawers/ReadOnlyPropertyDrawer.cs @@ -0,0 +1,23 @@ +using CHARK.ScriptableScenes.PropertyAttributes; +using UnityEditor; +using UnityEngine; + +namespace CHARK.ScriptableScenes.Editor.PropertyDrawers +{ + [CustomPropertyDrawer(typeof(ReadOnlyAttribute))] + internal class ReadOnlyPropertyDrawer : PropertyDrawer + { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var isEnabled = GUI.enabled; + GUI.enabled = false; + EditorGUI.PropertyField(position, property, label, true); + GUI.enabled = isEnabled; + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + return EditorGUI.GetPropertyHeight(property, label, true); + } + } +} diff --git a/Editor/PropertyDrawers/ReadOnlyPropertyDrawer.cs.meta b/Editor/PropertyDrawers/ReadOnlyPropertyDrawer.cs.meta new file mode 100644 index 0000000..516a228 --- /dev/null +++ b/Editor/PropertyDrawers/ReadOnlyPropertyDrawer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9307c4d6fbc24e1994dbb94e9899f1ad +timeCreated: 1653303366 \ No newline at end of file diff --git a/Editor/SceneReferenceCollectionEditor.cs b/Editor/SceneReferenceCollectionEditor.cs new file mode 100644 index 0000000..57b176a --- /dev/null +++ b/Editor/SceneReferenceCollectionEditor.cs @@ -0,0 +1,87 @@ +using System.Linq; +using CHARK.ScriptableScenes.Editor.Utilities; +using UnityEditor; +using UnityEngine; + +namespace CHARK.ScriptableScenes.Editor +{ + /// + /// Custom inspector for , used to draw debug buttons. + /// + [CanEditMultipleObjects] + [CustomEditor(typeof(BaseScriptableSceneCollection), true)] + internal class SceneReferenceCollectionEditor : UnityEditor.Editor + { + #region Private Fields + + private BaseScriptableSceneCollection sceneCollection; + + #endregion + + #region Unity Lifecycle + + private void OnEnable() + { + sceneCollection = (BaseScriptableSceneCollection) target; + } + + public override void OnInspectorGUI() + { + base.OnInspectorGUI(); + + EditorGUILayout.Space(); + + ScriptableSceneGUI.LabelField("Controls", EditorStyles.boldLabel); + DrawControls(sceneCollection); + } + + #endregion + + #region Private Methods + + private static void DrawControls(BaseScriptableSceneCollection collection) + { + var isAddedScenes = collection.Scenes.Any(); + var isEnabled = GUI.enabled; + + EditorGUILayout.BeginHorizontal(); + + GUI.enabled = isEnabled && isAddedScenes && Application.isPlaying == false; + DrawOpenButton(collection); + DrawPlayButton(collection); + + GUI.enabled = isEnabled && isAddedScenes && Application.isPlaying; + DrawLoadButton(collection); + + EditorGUILayout.EndHorizontal(); + + GUI.enabled = isEnabled; + } + + private static void DrawOpenButton(BaseScriptableSceneCollection collection) + { + if (ScriptableSceneGUI.Button("Open")) + { + collection.Open(); + } + } + + private static void DrawPlayButton(BaseScriptableSceneCollection collection) + { + if (ScriptableSceneGUI.Button("Play")) + { + collection.Play(); + } + } + + private static void DrawLoadButton(BaseScriptableSceneCollection collection) + { + if (ScriptableSceneGUI.Button("Load")) + { + collection.Load(); + } + } + + #endregion + } +} diff --git a/Editor/SceneReferenceCollectionEditor.cs.meta b/Editor/SceneReferenceCollectionEditor.cs.meta new file mode 100644 index 0000000..99d2310 --- /dev/null +++ b/Editor/SceneReferenceCollectionEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 279e91def1c74332b8ecdf5b91d5ae26 +timeCreated: 1652880033 \ No newline at end of file diff --git a/Editor/ScriptableSceneManagerWindow.cs b/Editor/ScriptableSceneManagerWindow.cs new file mode 100644 index 0000000..4bc1cdd --- /dev/null +++ b/Editor/ScriptableSceneManagerWindow.cs @@ -0,0 +1,331 @@ +using System.Collections.Generic; +using System.Linq; +using CHARK.ScriptableScenes.Editor.Utilities; +using CHARK.ScriptableScenes.Utilities; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace CHARK.ScriptableScenes.Editor +{ + /// + /// Window used to manage all available assets. + /// + internal sealed class ScriptableSceneManagerWindow : EditorWindow + { + #region Editor Fields + + [SerializeField] + private Vector2 scrollPosition; + + #endregion + + #region Private Fields + + private const float CollectionListFieldMargin = 2f; + private const float CollectionListMargin = 8f; + private const float CollectionListControlsExtraMargin = 2f; + private const float CollectionListControlButtonMargin = 9f; + + private const int CollectionFieldCount = 4; + + private List sceneCollections; + private ReorderableList sceneCollectionsList; + + #endregion + + #region Unity Lifecycle + + [MenuItem( + MenuItemConstants.BaseWindowItemName + "/Scriptable Scene Manager", + priority = MenuItemConstants.BaseWindowPriority + )] + private static void ShowWindow() + { + var sceneManagerWindow = GetWindow(); + sceneManagerWindow.titleContent = new GUIContent("Scriptable Scene Manager"); + + var minSize = sceneManagerWindow.minSize; + minSize.x = 200f; + minSize.y = 200f; + sceneManagerWindow.minSize = minSize; + + sceneManagerWindow.Show(); + } + + private void OnEnable() + { + SetupWindow(); + } + + private void OnGUI() + { + EditorGUILayout.Space(); + DrawPlayModeControls(); + DrawSceneCollections(); + } + + #endregion + + #region Internal Methods + + /// + /// Setup (reload) scene collections assigned to this window. + /// + internal void SetupWindow() + { + sceneCollections = ScriptableSceneEditorUtilities.GetScriptableSceneCollections(); + sceneCollectionsList = CreateSceneCollectionList(sceneCollections); + } + + #endregion + + #region Private Play Mode Control Methods + + private static void DrawPlayModeControls() + { + var isEnabled = GUI.enabled; + GUI.enabled = Application.isPlaying && isEnabled; + + EditorGUILayout.BeginHorizontal(); + + DrawStopGameButton(); + DrawPauseGameButton(); + DrawStepGameButton(); + + EditorGUILayout.EndHorizontal(); + + GUI.enabled = isEnabled; + } + + private static void DrawStopGameButton() + { + if (ScriptableSceneGUI.Button("Stop")) + { + ScriptableSceneEditorUtilities.StopGame(); + } + } + + private static void DrawPauseGameButton() + { + var isPausedOld = EditorApplication.isPaused; + var isPausedNew = ScriptableSceneGUI.Toggle(isPausedOld, "Pause", "Button"); + if (isPausedOld != isPausedNew) + { + ScriptableSceneEditorUtilities.SetPausedGame(isPausedNew); + } + } + + private static void DrawStepGameButton() + { + if (ScriptableSceneGUI.Button("Step")) + { + ScriptableSceneEditorUtilities.StepGame(); + } + } + + #endregion + + #region Private Scene Collection Methods + + private void DrawSceneCollections() + { + var margins = GetMarginStyle(); + EditorGUILayout.BeginHorizontal(margins); + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + sceneCollectionsList.DoLayoutList(); + EditorGUILayout.EndScrollView(); + + EditorGUILayout.EndHorizontal(); + } + + private static GUIStyle GetMarginStyle() + { + var style = new GUIStyle(EditorStyles.inspectorDefaultMargins); + var padding = style.padding; + padding.left = 4; + return style; + } + + private static ReorderableList CreateSceneCollectionList( + List collections + ) + { + var list = new ReorderableList( + collections, + typeof(BaseScriptableSceneCollection), + true, + false, + false, + false + ) + { + elementHeightCallback = OnGetElementHeight, + onReorderCallback = OnReorder, + drawElementCallback = OnDrawElement + }; + + float OnGetElementHeight(int index) + { + var collection = collections[index]; + return GetElementHeight(collection); + } + + void OnReorder(ReorderableList reorderableList) + { + for (var index = 0; index < collections.Count; index++) + { + var collection = collections[index]; + UpdateDisplayOrder(collection, index); + } + } + + void OnDrawElement(Rect rect, int index, bool isActive, bool isFocused) + { + var collection = collections[index]; + DrawSceneCollection(rect, collection); + } + + return list; + } + + private static float GetElementHeight(BaseScriptableSceneCollection collection) + { + var isExpanded = collection.IsExpanded(); + if (isExpanded == false) + { + return EditorGUIUtility.singleLineHeight + CollectionListFieldMargin; + } + + return (EditorGUIUtility.singleLineHeight + CollectionListFieldMargin) + * CollectionFieldCount + + CollectionListControlsExtraMargin + + CollectionListMargin; + } + + private static void UpdateDisplayOrder(BaseScriptableSceneCollection collection, int index) + { + if (collection.GetDisplayOrder() == index) + { + return; + } + + collection.SetDisplayOrder(index); + } + + private static void DrawSceneCollection(Rect rect, BaseScriptableSceneCollection collection) + { + var fieldYOffset = EditorGUIUtility.singleLineHeight + CollectionListFieldMargin; + var fieldRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight); + + var isExpanded = DrawTitle(fieldRect, collection); + if (isExpanded == false) + { + return; + } + + EditorGUI.indentLevel++; + + fieldRect.y += fieldYOffset; + DrawAssetField(fieldRect, collection); + + fieldRect.y += fieldYOffset; + DrawSceneCountField(fieldRect, collection); + + fieldRect.y += fieldYOffset + CollectionListControlsExtraMargin; + DrawControls(EditorGUI.IndentedRect(fieldRect), collection); + + EditorGUI.indentLevel--; + } + + private static void DrawControls(Rect rect, BaseScriptableSceneCollection collection) + { + var isAddedScenes = collection.Scenes.Any(); + var isEnabled = GUI.enabled; + + GUI.enabled = isEnabled && isAddedScenes && Application.isPlaying == false; + + rect.width = rect.width / 3f - CollectionListControlButtonMargin / 3f; + DrawOpenButton(rect, collection); + + rect.x += rect.width + CollectionListControlButtonMargin / 2f; + DrawPlayButton(rect, collection); + + GUI.enabled = isEnabled && isAddedScenes && Application.isPlaying; + rect.x += rect.width + CollectionListControlButtonMargin / 2f; + DrawLoadButton(rect, collection); + + GUI.enabled = isEnabled; + } + + private static bool DrawTitle(Rect rect, BaseScriptableSceneCollection collection) + { + var name = collection.Name; + var prettyName = ObjectNames.NicifyVariableName(name); + + var isExpanded = collection.IsExpanded(); + + EditorGUI.BeginChangeCheck(); + + var style = GetFoldoutTitleStyle(); + isExpanded = EditorGUI.Foldout(rect, isExpanded, prettyName, true, style); + + if (EditorGUI.EndChangeCheck()) + { + collection.SetExpanded(isExpanded); + } + + return isExpanded; + } + + private static void DrawAssetField(Rect rect, BaseScriptableSceneCollection collection) + { + ScriptableSceneGUI.ObjectField(rect, "Scene Collection", collection, false); + } + + private static void DrawSceneCountField(Rect rect, BaseScriptableSceneCollection collection) + { + var title = new GUIContent( + "Scene Count", + "Number of scenes added to this collection" + ); + + ScriptableSceneGUI.IntField(rect, title, collection.SceneCount); + } + + private static void DrawOpenButton(Rect rect, BaseScriptableSceneCollection collection) + { + if (ScriptableSceneGUI.Button(rect, "Open")) + { + collection.Open(); + } + } + + private static void DrawPlayButton(Rect rect, BaseScriptableSceneCollection collection) + { + if (ScriptableSceneGUI.Button(rect, "Play")) + { + collection.Play(); + } + } + + private static void DrawLoadButton(Rect rect, BaseScriptableSceneCollection collection) + { + if (ScriptableSceneGUI.Button(rect, "Load")) + { + collection.Load(); + } + } + + private static GUIStyle GetFoldoutTitleStyle() + { + return new GUIStyle(EditorStyles.foldout) + { + fontStyle = FontStyle.Bold + }; + } + + #endregion + } +} diff --git a/Editor/ScriptableSceneManagerWindow.cs.meta b/Editor/ScriptableSceneManagerWindow.cs.meta new file mode 100644 index 0000000..88aab6c --- /dev/null +++ b/Editor/ScriptableSceneManagerWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 08ab94ea858841bdaadff21b301bd3b0 +timeCreated: 1652879981 \ No newline at end of file diff --git a/Editor/ScriptableSceneManagerWindowPostprocessor.cs b/Editor/ScriptableSceneManagerWindowPostprocessor.cs new file mode 100644 index 0000000..3a7cb63 --- /dev/null +++ b/Editor/ScriptableSceneManagerWindowPostprocessor.cs @@ -0,0 +1,27 @@ +using UnityEditor; + +namespace CHARK.ScriptableScenes.Editor +{ + /// + /// Reloads assigned to + /// . + /// + internal sealed class ScriptableSceneManagerWindowPostprocessor : AssetPostprocessor + { + private static void OnPostprocessAllAssets( + string[] importedAssets, + string[] deletedAssets, + string[] movedAssets, + string[] movedFromAssetPaths + ) + { + if (EditorWindow.HasOpenInstances() == false) + { + return; + } + + var window = EditorWindow.GetWindow(); + window.SetupWindow(); + } + } +} diff --git a/Editor/ScriptableSceneManagerWindowPostprocessor.cs.meta b/Editor/ScriptableSceneManagerWindowPostprocessor.cs.meta new file mode 100644 index 0000000..61c749e --- /dev/null +++ b/Editor/ScriptableSceneManagerWindowPostprocessor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8dfaf7f979d347d490b191efa48cf3c6 +timeCreated: 1652880013 \ No newline at end of file diff --git a/Editor/Utilities.meta b/Editor/Utilities.meta new file mode 100644 index 0000000..aba607a --- /dev/null +++ b/Editor/Utilities.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 75f28c19b7df406b91a422498762bba8 +timeCreated: 1653049244 \ No newline at end of file diff --git a/Editor/Utilities/ScriptableSceneEditorUtilities.cs b/Editor/Utilities/ScriptableSceneEditorUtilities.cs new file mode 100644 index 0000000..b332461 --- /dev/null +++ b/Editor/Utilities/ScriptableSceneEditorUtilities.cs @@ -0,0 +1,238 @@ +using System.Collections.Generic; +using System.Linq; +using CHARK.ScriptableScenes.Utilities; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace CHARK.ScriptableScenes.Editor.Utilities +{ + /// + /// General utilities for interacting with assets in + /// Editor scripts. + /// + internal static class ScriptableSceneEditorUtilities + { + #region Unity Lifecycle + + [InitializeOnLoadMethod] + private static void Initialize() + { + EditorApplication.playModeStateChanged -= HandlePlayModeStateChanged; + EditorApplication.playModeStateChanged += HandlePlayModeStateChanged; + } + + #endregion + + #region Internal Methods + + /// + /// Collection of all assets in the project. + /// + internal static List GetScriptableSceneCollections() + { + return AssetDatabase + .FindAssets($"t:{typeof(BaseScriptableSceneCollection)}") + .Select(AssetDatabase.GUIDToAssetPath) + .Select(AssetDatabase.LoadAssetAtPath) + .OrderBy(collection => collection.GetDisplayOrder()) + .ThenBy(collection => collection.Name) + .ToList(); + } + + /// + /// Start playing the game using the given . + /// + /// + internal static void Play(this BaseScriptableSceneCollection collection) + { + if (Application.isPlaying) + { + Debug.LogWarning($"Must be in edit mode to play {collection.Name}"); + return; + } + + var scriptableScenes = collection.Scenes.ToList(); + var scriptableScene = scriptableScenes.FirstOrDefault(); + + if (scriptableScene == default) + { + return; + } + + EditorSceneManager.playModeStartScene = + AssetDatabase.LoadAssetAtPath(scriptableScene.ScenePath); + + ScriptableSceneUtilities.SetSelectedCollection(collection); + EditorApplication.isPlaying = true; + } + + /// + /// Load the given during play mode. + /// + internal static void Load(this BaseScriptableSceneCollection collection) + { + if (Application.isPlaying == false) + { + Debug.LogWarning($"Must be in play mode to load {collection.Name}"); + return; + } + + var sceneController = Object.FindObjectOfType(); + if (sceneController) + { + sceneController.LoadSceneCollection(collection); + } + else + { + Debug.LogWarning($"{nameof(ScriptableSceneController)} is missing"); + } + } + + /// + /// Open the given during edit mode. + /// + internal static void Open(this BaseScriptableSceneCollection collection) + { + if (Application.isPlaying) + { + Debug.LogWarning($"Must be in edit mode to open {collection.Name}"); + return; + } + + var scriptableScenes = collection.Scenes.ToList(); + if (scriptableScenes.Count == 0) + { + return; + } + + for (var index = 0; index < scriptableScenes.Count; index++) + { + var scriptableScene = scriptableScenes[index]; + var scenePath = scriptableScene.ScenePath; + + Scene scene; + if (index == 0) + { + scene = EditorSceneManager.OpenScene(scenePath); + } + else + { + scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); + } + + if (scriptableScene.IsActivate) + { + SceneManager.SetActiveScene(scene); + } + } + } + + /// + /// Stop the game inside the Editor. + /// + internal static void StopGame() + { + EditorApplication.isPlaying = false; + } + + /// + /// Pause or unpause the game inside the Editor. + /// + internal static void SetPausedGame(bool isPaused) + { + EditorApplication.isPaused = isPaused; + } + + /// + /// Progress the game by one step inside the Editor. Note that this will pause the game if + /// its unpaused! + /// + internal static void StepGame() + { + EditorApplication.Step(); + GUIUtility.ExitGUI(); + } + + /// + /// The sort order at which to display the given in Editor + /// lists. + /// + internal static int GetDisplayOrder(this BaseScriptableSceneCollection collection) + { + var key = GeDisplayOrderKey(collection); + return EditorPrefs.GetInt(key, 0); + } + + /// + /// Set the sort order at which to display the given in Editor + /// lists. + /// + internal static void SetDisplayOrder( + this BaseScriptableSceneCollection collection, + int order + ) + { + var key = GeDisplayOrderKey(collection); + EditorPrefs.SetInt(key, order); + } + + /// + /// true if the collection should be expanded in Editor foldouts or false + /// otherwise. + /// + internal static bool IsExpanded(this BaseScriptableSceneCollection collection) + { + var key = GetIsExpandedKey(collection); + return EditorPrefs.GetBool(key, false); + } + + /// + /// Set if the given should be expanded in Editor foldouts. + /// + internal static void SetExpanded( + this BaseScriptableSceneCollection collection, + bool isExpanded + ) + { + var key = GetIsExpandedKey(collection); + EditorPrefs.SetBool(key, isExpanded); + } + + #endregion + + #region Private Methods + + private static void HandlePlayModeStateChanged(PlayModeStateChange change) + { + if (change != PlayModeStateChange.EnteredEditMode) + { + return; + } + + EditorSceneManager.playModeStartScene = null; + ScriptableSceneUtilities.ClearSelectedCollection(); + } + + private static string GeDisplayOrderKey(BaseScriptableSceneCollection collection) + { + var prefix = typeof(ScriptableSceneUtilities).FullName; + const string function = nameof(GeDisplayOrderKey); + var target = collection.Guid; + + return $"{prefix}_{function}_{target}"; + } + + private static string GetIsExpandedKey(BaseScriptableSceneCollection collection) + { + var prefix = typeof(ScriptableSceneUtilities).FullName; + const string function = nameof(GetIsExpandedKey); + var target = collection.Guid; + + return $"{prefix}_{function}_{target}"; + } + + #endregion + } +} diff --git a/Editor/Utilities/ScriptableSceneEditorUtilities.cs.meta b/Editor/Utilities/ScriptableSceneEditorUtilities.cs.meta new file mode 100644 index 0000000..643e6f2 --- /dev/null +++ b/Editor/Utilities/ScriptableSceneEditorUtilities.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b8f155ed018443889a6370564b9a1d02 +timeCreated: 1652880079 \ No newline at end of file diff --git a/Editor/Utilities/ScriptableSceneGUI.cs b/Editor/Utilities/ScriptableSceneGUI.cs new file mode 100644 index 0000000..f219e1c --- /dev/null +++ b/Editor/Utilities/ScriptableSceneGUI.cs @@ -0,0 +1,60 @@ +using UnityEditor; +using UnityEngine; + +namespace CHARK.ScriptableScenes.Editor.Utilities +{ + /// + /// Wrappers for Unity GUI field and property drawing methods. + /// + // TODO: add Odin support + // TODO: comment methods in this class + internal static class ScriptableSceneGUI + { + #region Internal Methods + + internal static bool Toggle(bool isToggled, string label, GUIStyle style) + { + return GUILayout.Toggle(isToggled, label, style); + } + + internal static T ObjectField( + Rect rect, + string label, + T @object, + bool isAllowSceneObjects + ) where T : Object + { + var result = EditorGUI.ObjectField( + rect, + label, + @object, + typeof(T), + isAllowSceneObjects + ); + + return (T) result; + } + + internal static void LabelField(string label, GUIStyle style) + { + EditorGUILayout.LabelField(label, style); + } + + internal static int IntField(Rect rect, GUIContent label, int value) + { + return EditorGUI.IntField(rect, label, value); + } + + internal static bool Button(Rect rect, string text) + { + return GUI.Button(rect, text); + } + + internal static bool Button(string text) + { + return GUILayout.Button(text); + } + + #endregion + } +} diff --git a/Editor/Utilities/ScriptableSceneGUI.cs.meta b/Editor/Utilities/ScriptableSceneGUI.cs.meta new file mode 100644 index 0000000..fd0d89d --- /dev/null +++ b/Editor/Utilities/ScriptableSceneGUI.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c3299e7bebaa4a0684b08e37b242f21e +timeCreated: 1652880058 \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..94523a5 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Edvinas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.md.meta b/LICENSE.md.meta new file mode 100644 index 0000000..83b8ba6 --- /dev/null +++ b/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c27888781e2003949933a10defdbacf2 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..4d83121 --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 04d0af51b4997014a8aa7eb2c1ba8844 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/AssemblyInfo.cs b/Runtime/AssemblyInfo.cs new file mode 100644 index 0000000..f2d9a70 --- /dev/null +++ b/Runtime/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("CHARK.ScriptableScenes.Editor")] +[assembly: InternalsVisibleTo("CHARK.ScriptableScenes.Tests")] diff --git a/Runtime/AssemblyInfo.cs.meta b/Runtime/AssemblyInfo.cs.meta new file mode 100644 index 0000000..9e048b0 --- /dev/null +++ b/Runtime/AssemblyInfo.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1aae8a310a7c4388a1ede3c37d2d007a +timeCreated: 1653050143 \ No newline at end of file diff --git a/Runtime/BaseScriptableScene.cs b/Runtime/BaseScriptableScene.cs new file mode 100644 index 0000000..1e17ec3 --- /dev/null +++ b/Runtime/BaseScriptableScene.cs @@ -0,0 +1,66 @@ +using System.Collections; +using CHARK.ScriptableScenes.Events; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace CHARK.ScriptableScenes +{ + /// + /// Wrapper for . + /// + public abstract class BaseScriptableScene : ScriptableObject + { + #region Public Properties + + /// + /// Name of this scene. + /// + public abstract string Name { get; } + + /// + /// Path to the scene. + /// + public abstract string ScenePath { get; } + + /// + /// Scene build index. + /// + public abstract int SceneBuildIndex { get; } + + /// + /// Should this scene be activated after loading. + /// + public abstract bool IsActivate { get; } + + /// + /// Should this scene persist between scene loads (never unloaded, useful for setup scenes). + /// + public abstract bool IsPersist { get; } + + /// + /// Event handler assigned to this scene. + /// + public abstract ISceneEventHandler SceneEvents { get; } + + #endregion + + #region Public Methods + + /// + /// Routine which loads this scene. + /// + public abstract IEnumerator LoadRoutine(); + + /// + /// Routine which unloads this scene. + /// + public abstract IEnumerator UnloadRoutine(); + + /// + /// Activate this scene via . + /// + public abstract void SetActive(); + + #endregion + } +} diff --git a/Runtime/BaseScriptableScene.cs.meta b/Runtime/BaseScriptableScene.cs.meta new file mode 100644 index 0000000..d4a0ab8 --- /dev/null +++ b/Runtime/BaseScriptableScene.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4c1550b203104acd9d193de846d6d961 +timeCreated: 1653074690 \ No newline at end of file diff --git a/Runtime/BaseScriptableSceneCollection.cs b/Runtime/BaseScriptableSceneCollection.cs new file mode 100644 index 0000000..5dd6996 --- /dev/null +++ b/Runtime/BaseScriptableSceneCollection.cs @@ -0,0 +1,72 @@ +using System.Collections; +using System.Collections.Generic; +using CHARK.ScriptableScenes.Events; +using UnityEngine; + +namespace CHARK.ScriptableScenes +{ + /// + /// Collection of which can be used to load a set of scenes + /// at once. + /// + public abstract class BaseScriptableSceneCollection : ScriptableObject + { + #region Public Properties + + /// + /// Unique collection id. + /// + public abstract string Guid { get; } + + /// + /// Name of this collection. + /// + public abstract string Name { get; } + + /// + /// Count of in this collection. + /// + public abstract int SceneCount { get; } + + /// + /// Available in this collection. + /// + public abstract IEnumerable Scenes { get; } + + /// + /// Collection events invoked on this collection. + /// + public abstract ICollectionEventHandler CollectionEvents { get; } + + /// + /// Scene events invoked on scenes of this collection. + /// + public abstract ISceneEventHandler SceneEvents { get; } + + #endregion + + #region Public Methods + + /// + /// Routine which loads this collection. + /// + public abstract IEnumerator LoadRoutine(); + + /// + /// Routine which unloads this collection. + /// + public abstract IEnumerator UnloadRoutine(); + + /// + /// Routine which shows the transition of this collection. + /// + public abstract IEnumerator ShowTransitionRoutine(); + + /// + /// Routine which hides the transition of this collection. + /// + public abstract IEnumerator HideTransitionRoutine(); + + #endregion + } +} diff --git a/Runtime/BaseScriptableSceneCollection.cs.meta b/Runtime/BaseScriptableSceneCollection.cs.meta new file mode 100644 index 0000000..5a03000 --- /dev/null +++ b/Runtime/BaseScriptableSceneCollection.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b9ce0da6b3f645f8ad623073791f8a00 +timeCreated: 1653073957 \ No newline at end of file diff --git a/Runtime/CHARK.ScriptableScenes.asmdef b/Runtime/CHARK.ScriptableScenes.asmdef new file mode 100644 index 0000000..bc1dffb --- /dev/null +++ b/Runtime/CHARK.ScriptableScenes.asmdef @@ -0,0 +1,4 @@ +{ + "name": "CHARK.ScriptableScenes", + "rootNamespace": "CHARK.ScriptableScenes" +} diff --git a/Runtime/CHARK.ScriptableScenes.asmdef.meta b/Runtime/CHARK.ScriptableScenes.asmdef.meta new file mode 100644 index 0000000..bbdf9f9 --- /dev/null +++ b/Runtime/CHARK.ScriptableScenes.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7cfa357a9d1c66644b9ddbad9d55c0a3 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Events.meta b/Runtime/Events.meta new file mode 100644 index 0000000..5b0c784 --- /dev/null +++ b/Runtime/Events.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3c221ba62d3e0934a94ba7e0fad1c7a0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Events/CollectionEventArgs.cs b/Runtime/Events/CollectionEventArgs.cs new file mode 100644 index 0000000..740549d --- /dev/null +++ b/Runtime/Events/CollectionEventArgs.cs @@ -0,0 +1,97 @@ +namespace CHARK.ScriptableScenes.Events +{ + /// + /// Event arguments used in . + /// + public readonly struct CollectionLoadEventArgs + { + #region Public Properties + + /// + /// Scene collection which is loading. + /// + public BaseScriptableSceneCollection Collection { get; } + + #endregion + + #region Internal Methods + + internal CollectionLoadEventArgs(BaseScriptableSceneCollection collection) + { + Collection = collection; + } + + #endregion + } + + /// + /// Event arguments used in . + /// + public readonly struct CollectionLoadProgressEventArgs + { + #region Public Properties + + /// + /// Scene Collection which is loading. + /// + public BaseScriptableSceneCollection Collection { get; } + + /// + /// Scene which is loading. + /// + public BaseScriptableScene Scene { get; } + + /// + /// Load progress for (goes from 0 to 1). + /// + public float CollectionLoadProgress { get; } + + /// + /// Load progress for (goes from 0 to 1). + /// + public float SceneLoadProgress { get; } + + #endregion + + #region Internal Methods + + internal CollectionLoadProgressEventArgs( + BaseScriptableSceneCollection collection, + BaseScriptableScene scene, + float collectionLoadProgress, + float sceneLoadProgress + ) + { + Collection = collection; + Scene = scene; + CollectionLoadProgress = collectionLoadProgress; + SceneLoadProgress = sceneLoadProgress; + } + + #endregion + } + + /// + /// Event used in . + /// + public readonly struct CollectionUnloadEventArgs + { + #region Public Properties + + /// + /// Collection which is unloading. + /// + public BaseScriptableSceneCollection Collection { get; } + + #endregion + + #region Internal Methods + + internal CollectionUnloadEventArgs(BaseScriptableSceneCollection collection) + { + Collection = collection; + } + + #endregion + } +} diff --git a/Runtime/Events/CollectionEventArgs.cs.meta b/Runtime/Events/CollectionEventArgs.cs.meta new file mode 100644 index 0000000..303b58d --- /dev/null +++ b/Runtime/Events/CollectionEventArgs.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: af9e0f9acc95473ca95aa14d6107194a +timeCreated: 1653289584 \ No newline at end of file diff --git a/Runtime/Events/CollectionEventHandler.cs b/Runtime/Events/CollectionEventHandler.cs new file mode 100644 index 0000000..bcff8e9 --- /dev/null +++ b/Runtime/Events/CollectionEventHandler.cs @@ -0,0 +1,161 @@ +using System; +using UnityEngine; +using UnityEngine.Events; + +namespace CHARK.ScriptableScenes.Events +{ + /// + /// Event handler for assets. + /// + [Serializable] + internal sealed class CollectionEventHandler : ICollectionEventHandler + { + #region Editor Fields + + [SerializeField] + private UnityEvent onLoadEntered = + new UnityEvent(); + + [SerializeField] + private UnityEvent onLoadExited = + new UnityEvent(); + + [SerializeField] + private UnityEvent onLoadProgress = + new UnityEvent(); + + [SerializeField] + private UnityEvent onUnloadEntered = + new UnityEvent(); + + [SerializeField] + private UnityEvent onUnloadExited = + new UnityEvent(); + + #endregion + + #region Public Events + + public event CollectionLoadEvent OnLoadEntered; + + public event CollectionLoadEvent OnLoadExited; + + public event CollectionLoadProgressEvent OnLoadProgress; + + public event CollectionUnloadEvent OnUnloadEntered; + + public event CollectionUnloadEvent OnUnloadExited; + + #endregion + + #region Internal Methods + + internal void RaiseLoadEntered(BaseScriptableSceneCollection collection) + { + var args = new CollectionLoadEventArgs(collection); + RaiseLoadEntered(args); + } + + internal void RaiseLoadEntered(CollectionLoadEventArgs args) + { + try + { + onLoadEntered.Invoke(args); + OnLoadEntered?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseLoadExited(BaseScriptableSceneCollection collection) + { + var args = new CollectionLoadEventArgs(collection); + RaiseLoadExited(args); + } + + internal void RaiseLoadExited(CollectionLoadEventArgs args) + { + try + { + onLoadExited.Invoke(args); + OnLoadExited?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseLoadProgress( + BaseScriptableSceneCollection collection, + BaseScriptableScene scene, + float collectionProgress, + float sceneProgress + ) + { + var args = new CollectionLoadProgressEventArgs( + collection, + scene, + collectionProgress, + sceneProgress + ); + + RaiseLoadProgress(args); + } + + internal void RaiseLoadProgress(CollectionLoadProgressEventArgs args) + { + try + { + onLoadProgress.Invoke(args); + OnLoadProgress?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseUnloadEntered(BaseScriptableSceneCollection collection) + { + var args = new CollectionUnloadEventArgs(collection); + RaiseUnloadEntered(args); + } + + internal void RaiseUnloadEntered(CollectionUnloadEventArgs args) + { + try + { + onUnloadEntered.Invoke(args); + OnUnloadEntered?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseUnloadExited(BaseScriptableSceneCollection collection) + { + var args = new CollectionUnloadEventArgs(collection); + RaiseUnloadExited(args); + } + + internal void RaiseUnloadExited(CollectionUnloadEventArgs args) + { + try + { + onUnloadExited.Invoke(args); + OnUnloadExited?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + #endregion + } +} diff --git a/Runtime/Events/CollectionEventHandler.cs.meta b/Runtime/Events/CollectionEventHandler.cs.meta new file mode 100644 index 0000000..da4fe14 --- /dev/null +++ b/Runtime/Events/CollectionEventHandler.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3ae3265f1e5c4db4b35d2abe29073ba9 +timeCreated: 1653030197 \ No newline at end of file diff --git a/Runtime/Events/CollectionEventHandlerDelegates.cs b/Runtime/Events/CollectionEventHandlerDelegates.cs new file mode 100644 index 0000000..a23922a --- /dev/null +++ b/Runtime/Events/CollectionEventHandlerDelegates.cs @@ -0,0 +1,17 @@ +namespace CHARK.ScriptableScenes.Events +{ + /// + /// Invoked when starts to load or is loaded. + /// + public delegate void CollectionLoadEvent(CollectionLoadEventArgs args); + + /// + /// Invoked when loading process updates. + /// + public delegate void CollectionLoadProgressEvent(CollectionLoadProgressEventArgs args); + + /// + /// Invoked when starts to unload or is unloaded. + /// + public delegate void CollectionUnloadEvent(CollectionUnloadEventArgs args); +} diff --git a/Runtime/Events/CollectionEventHandlerDelegates.cs.meta b/Runtime/Events/CollectionEventHandlerDelegates.cs.meta new file mode 100644 index 0000000..5e5b039 --- /dev/null +++ b/Runtime/Events/CollectionEventHandlerDelegates.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 61701bad813d414890e968aaddccc2a2 +timeCreated: 1653289559 \ No newline at end of file diff --git a/Runtime/Events/ICollectionEventHandler.cs b/Runtime/Events/ICollectionEventHandler.cs new file mode 100644 index 0000000..c234729 --- /dev/null +++ b/Runtime/Events/ICollectionEventHandler.cs @@ -0,0 +1,34 @@ +namespace CHARK.ScriptableScenes.Events +{ + public interface ICollectionEventHandler + { + #region Public Events + + /// + /// Called when loading of begins. + /// + public event CollectionLoadEvent OnLoadEntered; + + /// + /// Called when loading of finishes. + /// + public event CollectionLoadEvent OnLoadExited; + + /// + /// Called loading of progresses. + /// + public event CollectionLoadProgressEvent OnLoadProgress; + + /// + /// Called when unloading of begins. + /// + public event CollectionUnloadEvent OnUnloadEntered; + + /// + /// Called when unloading of finishes. + /// + public event CollectionUnloadEvent OnUnloadExited; + + #endregion + } +} diff --git a/Runtime/Events/ICollectionEventHandler.cs.meta b/Runtime/Events/ICollectionEventHandler.cs.meta new file mode 100644 index 0000000..e6be377 --- /dev/null +++ b/Runtime/Events/ICollectionEventHandler.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 88d4c848560948bc8118e4676a3f4614 +timeCreated: 1653074967 \ No newline at end of file diff --git a/Runtime/Events/ISceneEventHandler.cs b/Runtime/Events/ISceneEventHandler.cs new file mode 100644 index 0000000..26c8a61 --- /dev/null +++ b/Runtime/Events/ISceneEventHandler.cs @@ -0,0 +1,44 @@ +namespace CHARK.ScriptableScenes.Events +{ + public interface ISceneEventHandler + { + #region Public Events + + /// + /// Called when loading of begins. + /// + public event SceneLoadEvent OnLoadEntered; + + /// + /// Called when loading of finishes. + /// + public event SceneLoadEvent OnLoadExited; + + /// + /// Called when loading of updates. + /// + public event SceneLoadProgressEvent OnLoadProgress; + + /// + /// Called when unloading of begins. + /// + public event SceneUnloadEvent OnUnloadEntered; + + /// + /// Called when unloading of finishes. + /// + public event SceneUnloadEvent OnUnloadExited; + + /// + /// Called when activation of beings. + /// + public event SceneActivateEvent OnActivateEntered; + + /// + /// Called when activation of finishes. + /// + public event SceneActivateEvent OnActivateExited; + + #endregion + } +} diff --git a/Runtime/Events/ISceneEventHandler.cs.meta b/Runtime/Events/ISceneEventHandler.cs.meta new file mode 100644 index 0000000..4cc6803 --- /dev/null +++ b/Runtime/Events/ISceneEventHandler.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c34c279c0248424599e4e40e2d94120a +timeCreated: 1653075028 \ No newline at end of file diff --git a/Runtime/Events/SceneEventArgs.cs b/Runtime/Events/SceneEventArgs.cs new file mode 100644 index 0000000..d90cebd --- /dev/null +++ b/Runtime/Events/SceneEventArgs.cs @@ -0,0 +1,104 @@ +namespace CHARK.ScriptableScenes.Events +{ + /// + /// Event arguments used in . + /// + public readonly struct SceneLoadEventArgs + { + #region Public Properties + + /// + /// Scene which is loading. + /// + public BaseScriptableScene Scene { get; } + + #endregion + + #region Internal Methods + + internal SceneLoadEventArgs(BaseScriptableScene scene) + { + Scene = scene; + } + + #endregion + } + + /// + /// Event arguments used in . + /// + public readonly struct SceneLoadProgressEventArgs + { + #region Public Properties + + /// + /// Scene which is loading. + /// + public BaseScriptableScene Scene { get; } + + /// + /// The progress loading. Goes from 0 to 1 (inclusive). + /// + public float Progress { get; } + + #endregion + + #region Internal Methods + + internal SceneLoadProgressEventArgs(BaseScriptableScene scene, float progress) + { + Scene = scene; + Progress = progress; + } + + #endregion + } + + /// + /// Event arguments used in . + /// + public readonly struct SceneUnloadEventArgs + { + #region Public Properties + + /// + /// Scene which is unloading. + /// + public BaseScriptableScene Scene { get; } + + #endregion + + #region Internal Methods + + internal SceneUnloadEventArgs(BaseScriptableScene scene) + { + Scene = scene; + } + + #endregion + } + + /// + /// Event arguments used in . + /// + public readonly struct SceneActivateEventArgs + { + #region Public Properties + + /// + /// Scene which is being activated. + /// + public BaseScriptableScene Scene { get; } + + #endregion + + #region Internal Methods + + internal SceneActivateEventArgs(BaseScriptableScene scene) + { + Scene = scene; + } + + #endregion + } +} diff --git a/Runtime/Events/SceneEventArgs.cs.meta b/Runtime/Events/SceneEventArgs.cs.meta new file mode 100644 index 0000000..ac165da --- /dev/null +++ b/Runtime/Events/SceneEventArgs.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b6697294263247e2bdcec3f4569a5ef2 +timeCreated: 1653289531 \ No newline at end of file diff --git a/Runtime/Events/SceneEventHandler.cs b/Runtime/Events/SceneEventHandler.cs new file mode 100644 index 0000000..780888c --- /dev/null +++ b/Runtime/Events/SceneEventHandler.cs @@ -0,0 +1,201 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using UnityEngine; +using UnityEngine.Events; + +namespace CHARK.ScriptableScenes.Events +{ + /// + /// Event handler for assets. + /// + [Serializable] + internal sealed class SceneEventHandler : ISceneEventHandler + { + #region Editor Fields + + [SerializeField] + private UnityEvent onLoadEntered = + new UnityEvent(); + + [SerializeField] + private UnityEvent onLoadExited = + new UnityEvent(); + + [SerializeField] + private UnityEvent onLoadProgress = + new UnityEvent(); + + [SerializeField] + private UnityEvent onUnloadEntered = + new UnityEvent(); + + [SerializeField] + private UnityEvent onUnloadExited = + new UnityEvent(); + + [SerializeField] + private UnityEvent onActivateEntered = + new UnityEvent(); + + [SerializeField] + private UnityEvent onActivateExited = + new UnityEvent(); + + #endregion + + #region Public Events + + public event SceneLoadEvent OnLoadEntered; + + public event SceneLoadEvent OnLoadExited; + + public event SceneLoadProgressEvent OnLoadProgress; + + public event SceneUnloadEvent OnUnloadEntered; + + public event SceneUnloadEvent OnUnloadExited; + + public event SceneActivateEvent OnActivateEntered; + + public event SceneActivateEvent OnActivateExited; + + #endregion + + #region Internal Methods + + internal void RaiseLoadEntered(BaseScriptableScene scene) + { + var args = new SceneLoadEventArgs(scene); + RaiseLoadEntered(args); + } + + internal void RaiseLoadEntered(SceneLoadEventArgs args) + { + try + { + onLoadEntered.Invoke(args); + OnLoadEntered?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseLoadExited(BaseScriptableScene scene) + { + var args = new SceneLoadEventArgs(scene); + RaiseLoadExited(args); + } + + internal void RaiseLoadExited(SceneLoadEventArgs args) + { + try + { + onLoadExited.Invoke(args); + OnLoadExited?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseLoadProgress(BaseScriptableScene scene, float progress) + { + var args = new SceneLoadProgressEventArgs(scene, progress); + RaiseLoadProgress(args); + } + + internal void RaiseLoadProgress(SceneLoadProgressEventArgs args) + { + try + { + onLoadProgress.Invoke(args); + OnLoadProgress?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseUnloadEntered(BaseScriptableScene scene) + { + var args = new SceneUnloadEventArgs(scene); + RaiseUnloadEntered(args); + } + + internal void RaiseUnloadEntered(SceneUnloadEventArgs args) + { + try + { + onUnloadEntered.Invoke(args); + OnUnloadEntered?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseUnloadExited(BaseScriptableScene scene) + { + var args = new SceneUnloadEventArgs(scene); + RaiseUnloadExited(args); + } + + internal void RaiseUnloadExited(SceneUnloadEventArgs args) + { + try + { + onUnloadExited.Invoke(args); + OnUnloadExited?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseActivateEntered(BaseScriptableScene scene) + { + var args = new SceneActivateEventArgs(scene); + RaiseActivateEntered(args); + } + + internal void RaiseActivateEntered(SceneActivateEventArgs args) + { + try + { + onActivateEntered.Invoke(args); + OnActivateEntered?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + internal void RaiseActivateExited(BaseScriptableScene scene) + { + var args = new SceneActivateEventArgs(scene); + RaiseActivateExited(args); + } + + internal void RaiseActivateExited(SceneActivateEventArgs args) + { + try + { + onActivateExited.Invoke(args); + OnActivateExited?.Invoke(args); + } + catch (Exception exception) + { + Debug.LogError(exception); + } + } + + #endregion + } +} diff --git a/Runtime/Events/SceneEventHandler.cs.meta b/Runtime/Events/SceneEventHandler.cs.meta new file mode 100644 index 0000000..669e0a0 --- /dev/null +++ b/Runtime/Events/SceneEventHandler.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b9c6c0115e8a43f38186f48c7ead10e8 +timeCreated: 1653031263 \ No newline at end of file diff --git a/Runtime/Events/SceneEventHandlerDelegates.cs b/Runtime/Events/SceneEventHandlerDelegates.cs new file mode 100644 index 0000000..1ff3e24 --- /dev/null +++ b/Runtime/Events/SceneEventHandlerDelegates.cs @@ -0,0 +1,22 @@ +namespace CHARK.ScriptableScenes.Events +{ + /// + /// Invoked when starts to load or is loaded. + /// + public delegate void SceneLoadEvent(SceneLoadEventArgs args); + + /// + /// Invoked when loading progress updates. + /// + public delegate void SceneLoadProgressEvent(SceneLoadProgressEventArgs args); + + /// + /// Invoked when starts to unload or is unloaded. + /// + public delegate void SceneUnloadEvent(SceneUnloadEventArgs args); + + /// + /// Invoked when starts to activate or is activated. + /// + public delegate void SceneActivateEvent(SceneActivateEventArgs args); +} diff --git a/Runtime/Events/SceneEventHandlerDelegates.cs.meta b/Runtime/Events/SceneEventHandlerDelegates.cs.meta new file mode 100644 index 0000000..c4be252 --- /dev/null +++ b/Runtime/Events/SceneEventHandlerDelegates.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ad618ae207b5408898a9ead34f640b8f +timeCreated: 1653289499 \ No newline at end of file diff --git a/Runtime/PropertyAttributes.meta b/Runtime/PropertyAttributes.meta new file mode 100644 index 0000000..2503791 --- /dev/null +++ b/Runtime/PropertyAttributes.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f0451f1a5c5f47c9bff0561191e359e3 +timeCreated: 1653303320 \ No newline at end of file diff --git a/Runtime/PropertyAttributes/ReadOnlyAttribute.cs b/Runtime/PropertyAttributes/ReadOnlyAttribute.cs new file mode 100644 index 0000000..51d11c8 --- /dev/null +++ b/Runtime/PropertyAttributes/ReadOnlyAttribute.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace CHARK.ScriptableScenes.PropertyAttributes +{ + /// + /// Marks an Editor field as read only. + /// + [System.AttributeUsage(System.AttributeTargets.Field)] + internal sealed class ReadOnlyAttribute : PropertyAttribute + { + } +} diff --git a/Runtime/PropertyAttributes/ReadOnlyAttribute.cs.meta b/Runtime/PropertyAttributes/ReadOnlyAttribute.cs.meta new file mode 100644 index 0000000..1baace0 --- /dev/null +++ b/Runtime/PropertyAttributes/ReadOnlyAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5c08f12a00e048fda32e25d3b20e831e +timeCreated: 1653303326 \ No newline at end of file diff --git a/Runtime/ScriptableScene.cs b/Runtime/ScriptableScene.cs new file mode 100644 index 0000000..0049cb7 --- /dev/null +++ b/Runtime/ScriptableScene.cs @@ -0,0 +1,167 @@ +using System.Collections; +using CHARK.ScriptableScenes.Events; +using CHARK.ScriptableScenes.PropertyAttributes; +using CHARK.ScriptableScenes.Utilities; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace CHARK.ScriptableScenes +{ + [CreateAssetMenu( + fileName = CreateAssetMenuConstants.BaseFileName + nameof(ScriptableScene), + menuName = CreateAssetMenuConstants.BaseMenuName + "/Scriptable Scene", + order = CreateAssetMenuConstants.BaseOrder + )] + internal sealed class ScriptableScene : + BaseScriptableScene, + ISerializationCallbackReceiver + { + #region Editor Fields + + [Header("Internal")] + [ReadOnly] + [Tooltip("Build index of the scene")] + [SerializeField] + private int sceneBuildIndex; + + [ReadOnly] + [Tooltip("Path to the scene asset")] + [SerializeField] + private string scenePath; + +#if UNITY_EDITOR + [Header("Configuration")] + [SerializeField] + private UnityEditor.SceneAsset sceneAsset; +#endif + + [Tooltip("Should this scene be activated on load?")] + [SerializeField] + private bool isActivate; + + [Tooltip("Should this scene persist between scene changes (never unloaded)?")] + [SerializeField] + private bool isPersist; + + [Header("Events")] + [SerializeField] + private SceneEventHandler sceneEvents = new SceneEventHandler(); + + #endregion + + #region Public Properties + + public override ISceneEventHandler SceneEvents => sceneEvents; + + public override int SceneBuildIndex => sceneBuildIndex; + + public override string ScenePath => scenePath; + + public override bool IsActivate => isActivate; + + public override bool IsPersist => isPersist; + + public override string Name => name; + + #endregion + + #region Private Fields + + private const float MaxSceneLoadProgress = 0.9f; + + #endregion + + #region Unity Lifecycle + + public void OnBeforeSerialize() + { +#if UNITY_EDITOR + if (sceneAsset.TryGetSceneDetails(out var newScenePath, out var newSceneBuildIndex)) + { + scenePath = newScenePath; + sceneBuildIndex = newSceneBuildIndex; + } +#endif + } + + public void OnAfterDeserialize() + { + } + + #endregion + + #region Public Methods + + public override IEnumerator LoadRoutine() + { + sceneEvents.RaiseLoadEntered(this); + + if (IsLoaded() == false) + { + yield return LoadInternalRoutine(); + } + else + { + sceneEvents.RaiseLoadProgress(this, 1f); + } + + sceneEvents.RaiseLoadExited(this); + } + + public override IEnumerator UnloadRoutine() + { + sceneEvents.RaiseUnloadEntered(this); + yield return UnloadInternalRoutine(); + sceneEvents.RaiseUnloadExited(this); + } + + public override void SetActive() + { + sceneEvents.RaiseActivateEntered(this); + SetActiveInternal(); + sceneEvents.RaiseActivateExited(this); + } + + #endregion + + #region Private Methods + + private IEnumerator LoadInternalRoutine() + { + var operation = SceneManager.LoadSceneAsync(sceneBuildIndex, LoadSceneMode.Additive); + + while (operation.isDone == false) + { + // Scene load progress range is [0, 0.9], need to remap to [0, 1] range. + var loadProgress = Mathf.Clamp01(operation.progress / MaxSceneLoadProgress); + sceneEvents.RaiseLoadProgress(this, loadProgress); + + yield return null; + } + } + + private IEnumerator UnloadInternalRoutine() + { + yield return SceneManager.UnloadSceneAsync(sceneBuildIndex); + } + + private void SetActiveInternal() + { + var scene = GetScene(); + SceneManager.SetActiveScene(scene); + } + + private bool IsLoaded() + { + var scene = GetScene(); + return scene.isLoaded; + } + + private Scene GetScene() + { + return SceneManager.GetSceneByBuildIndex(sceneBuildIndex); + } + + #endregion + } +} diff --git a/Runtime/ScriptableScene.cs.meta b/Runtime/ScriptableScene.cs.meta new file mode 100644 index 0000000..a2c226d --- /dev/null +++ b/Runtime/ScriptableScene.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d764409e2a2347eba48b0c2f5e79885f +timeCreated: 1652879070 \ No newline at end of file diff --git a/Runtime/ScriptableSceneCollection.cs b/Runtime/ScriptableSceneCollection.cs new file mode 100644 index 0000000..db5de50 --- /dev/null +++ b/Runtime/ScriptableSceneCollection.cs @@ -0,0 +1,198 @@ +using System.Collections; +using System.Collections.Generic; +using CHARK.ScriptableScenes.Events; +using CHARK.ScriptableScenes.PropertyAttributes; +using CHARK.ScriptableScenes.Transitions; +using CHARK.ScriptableScenes.Utilities; +using UnityEngine; + +namespace CHARK.ScriptableScenes +{ + [CreateAssetMenu( + fileName = CreateAssetMenuConstants.BaseFileName + nameof(ScriptableSceneCollection), + menuName = CreateAssetMenuConstants.BaseMenuName + "/Scriptable Scene Collection", + order = CreateAssetMenuConstants.BaseOrder + )] + internal sealed class ScriptableSceneCollection : + BaseScriptableSceneCollection, + ISerializationCallbackReceiver + { + #region Editor Fields + + [Header("Internal")] + [ReadOnly] + [Tooltip("Unique collection asset GUID")] + [SerializeField] + private string guid; + + [Header("Configuration")] + [Tooltip("Optional transition used to transition into and out of this collection")] + [SerializeField] + private BaseScriptableSceneTransition transition; + + [Tooltip("List of Scriptable Scenes to be loaded with this collection")] + [SerializeField] + private List scriptableScenes = new List(); + + [Header("Events")] + [SerializeField] + private CollectionEventHandler collectionEvents = new CollectionEventHandler(); + + [SerializeField] + private SceneEventHandler sceneEvents = new SceneEventHandler(); + + #endregion + + #region Public Properties + + public override IEnumerable Scenes => GetValidScriptableScenes(); + + public override ICollectionEventHandler CollectionEvents => collectionEvents; + + public override ISceneEventHandler SceneEvents => sceneEvents; + + public override string Guid => guid; + + public override int SceneCount => scriptableScenes.Count; + + public override string Name => name; + + #endregion + + #region Unity Lifecycle + + public void OnBeforeSerialize() + { +#if UNITY_EDITOR + if (this.TryGetAssetGuid(out var newGuid)) + { + guid = newGuid; + } +#endif + } + + public void OnAfterDeserialize() + { + } + + #endregion + + #region Public Methods + + public override IEnumerator LoadRoutine() + { + collectionEvents.RaiseLoadEntered(this); + yield return LoadInternalRoutine(); + collectionEvents.RaiseLoadExited(this); + } + + public override IEnumerator UnloadRoutine() + { + collectionEvents.RaiseUnloadEntered(this); + yield return UnloadInternalRoutine(); + collectionEvents.RaiseUnloadExited(this); + } + + public override IEnumerator ShowTransitionRoutine() + { + if (transition) + { + yield return transition.ShowRoutine(); + } + } + + public override IEnumerator HideTransitionRoutine() + { + if (transition) + { + yield return transition.HideRoutine(); + } + } + + #endregion + + #region Private Methods + + private IEnumerator LoadInternalRoutine() + { + var validScriptableScenes = GetValidScriptableScenes(); + var sceneCount = validScriptableScenes.Count; + + for (var index = 0; index < validScriptableScenes.Count; index++) + { + var totalLoadProgress = Mathf.Clamp01((float) index / sceneCount); + var scriptableScene = validScriptableScenes[index]; + + try + { + scriptableScene.SceneEvents.AddListeners(sceneEvents); + scriptableScene.SceneEvents.OnLoadProgress += OnLoadProgress; + + yield return scriptableScene.LoadRoutine(); + + if (scriptableScene.IsActivate) + { + scriptableScene.SetActive(); + } + } + finally + { + scriptableScene.SceneEvents.RemoveListeners(sceneEvents); + scriptableScene.SceneEvents.OnLoadProgress -= OnLoadProgress; + } + + void OnLoadProgress(SceneLoadProgressEventArgs args) + { + var collectionProgress = Mathf.Clamp01( + totalLoadProgress + args.Progress / sceneCount + ); + + collectionEvents.RaiseLoadProgress( + this, + scriptableScene, + collectionProgress, + args.Progress + ); + } + } + } + + private IEnumerator UnloadInternalRoutine() + { + foreach (var scriptableScene in GetValidScriptableScenes()) + { + if (scriptableScene.IsPersist) + { + continue; + } + + try + { + scriptableScene.SceneEvents.AddListeners(sceneEvents); + yield return scriptableScene.UnloadRoutine(); + } + finally + { + scriptableScene.SceneEvents.RemoveListeners(sceneEvents); + } + } + } + + private IReadOnlyList GetValidScriptableScenes() + { + var scenes = new List(); + foreach (var scriptableScene in scriptableScenes) + { + // Scenes might ne null if the user adds a new scene and forgets to select an asset. + if (scriptableScene) + { + scenes.Add(scriptableScene); + } + } + + return scenes; + } + + #endregion + } +} diff --git a/Runtime/ScriptableSceneCollection.cs.meta b/Runtime/ScriptableSceneCollection.cs.meta new file mode 100644 index 0000000..1365986 --- /dev/null +++ b/Runtime/ScriptableSceneCollection.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b14ae577063c44c19b953d1bcaa2f3ab +timeCreated: 1652879100 \ No newline at end of file diff --git a/Runtime/ScriptableSceneController.cs b/Runtime/ScriptableSceneController.cs new file mode 100644 index 0000000..27a3333 --- /dev/null +++ b/Runtime/ScriptableSceneController.cs @@ -0,0 +1,229 @@ +using System.Collections; +using CHARK.ScriptableScenes.Events; +using CHARK.ScriptableScenes.Utilities; +using UnityEngine; + +namespace CHARK.ScriptableScenes +{ + /// + /// Central scene controller which handles loading and unloading of + /// . + /// + [AddComponentMenu( + AddComponentMenuConstants.BaseMenuName + "/Scriptable Scene Controller" + )] + public sealed class ScriptableSceneController : MonoBehaviour + { + #region Editor Fields + + // ReSharper disable once NotAccessedField.Local + [Header("Configuration")] + [Tooltip("Scene collection which is first to be loaded when the game runs in build mode")] + [SerializeField] + private BaseScriptableSceneCollection initialCollection; + + [Header("Events")] + [Tooltip("handler for global (invoked for all collections) collection events")] + [SerializeField] + private CollectionEventHandler collectionEvents = new CollectionEventHandler(); + + [Tooltip("Handler for global (invoked for all scenes) scene events")] + [SerializeField] + private SceneEventHandler sceneEvents = new SceneEventHandler(); + + #endregion + + #region Private Fields + + private BaseScriptableSceneCollection loadingCollection; + private BaseScriptableSceneCollection loadedCollection; + + #endregion + + #region Public Properties + + /// + /// Global events invoked for all assets. + /// + public ICollectionEventHandler CollectionEvents => collectionEvents; + + /// + /// Global events invoked for all assets. + /// + public ISceneEventHandler SceneEvents => sceneEvents; + + // ReSharper disable once UnusedMember.Global + /// + /// true if there currently a collection being loaded in + /// or false otherwise. + /// + public bool IsLoading { get; private set; } + + #endregion + + #region Unity Lifecycle + + private IEnumerator Start() + { +#if UNITY_EDITOR + if (ScriptableSceneUtilities.TryGetSelectedCollection(out var selected)) + { + yield return LoadSceneCollectionRoutine(selected); + yield break; + } + + if (ScriptableSceneUtilities.TryGetLoadedCollection(out var loaded)) + { + yield return LoadSceneCollectionRoutine(loaded); + yield break; + } + + Debug.LogWarning( + $"Cannot load initial {nameof(BaseScriptableSceneCollection)}, make sure a valid " + + $"{nameof(BaseScriptableSceneCollection)} exists which matches currently loaded " + + $"scenes or use the Scene Manager Window", + this + ); + +#else + if (initialCollection == false) + { + Debug.LogWarning( + $"{nameof(initialCollection)} is not set, initial scene setup will not be " + + $"loaded", + this + ); + yield break; + } + + yield return LoadSceneCollectionRoutine(initialCollection); +#endif + } + + #endregion + + #region Public Methods + + /// + /// Reloads . + /// + public void ReloadLoadedSceneCollection() + { + if (loadedCollection == false) + { + Debug.LogWarning( + $"No {nameof(BaseScriptableSceneCollection)} is loaded, reload will be ignored", + this + ); + + return; + } + + LoadSceneCollection(loadedCollection); + } + + /// + /// Load a set of scenes using the provided and unload + /// . + /// + public void LoadSceneCollection(BaseScriptableSceneCollection collection) + { + StartCoroutine(LoadSceneCollectionRoutine(collection)); + } + + /// + /// true i`f a collection which is currently being loaded is retrieved or + /// false otherwise. + /// + public bool TryGetLoadingSceneCollection(out BaseScriptableSceneCollection collection) + { + collection = loadingCollection; + return collection != false; + } + + /// + /// true if a collection which is currently loaded is retrieved or false + /// otherwise. + /// + public bool TryGetLoadedSceneCollection(out BaseScriptableSceneCollection collection) + { + collection = loadedCollection; + return collection != false; + } + + #endregion + + #region Private Methods + + private IEnumerator LoadSceneCollectionRoutine(BaseScriptableSceneCollection collection) + { + if (collection.SceneCount == 0) + { + Debug.LogWarning( + $"Collection \"{collection.Name}\" does not contain any scenes! Load will be " + + $"ignored", + this + ); + + yield break; + } + + if (IsLoading) + { + Debug.LogWarning( + $"Can't load two collections at the same time, collection " + + $"\"{loadingCollection.Name}\" is currently being loaded", + this + ); + + yield break; + } + + loadingCollection = collection; + IsLoading = true; + + yield return LoadSceneCollectionInternalRoutine(collection); + + loadingCollection = null; + loadedCollection = collection; + IsLoading = false; + } + + private IEnumerator LoadSceneCollectionInternalRoutine( + BaseScriptableSceneCollection collection + ) + { + yield return collection.ShowTransitionRoutine(); + if (loadedCollection != false) + { + try + { + loadedCollection.CollectionEvents.AddListeners(collectionEvents); + loadedCollection.SceneEvents.AddListeners(sceneEvents); + yield return loadedCollection.UnloadRoutine(); + } + finally + { + loadedCollection.CollectionEvents.RemoveListeners(collectionEvents); + loadedCollection.SceneEvents.RemoveListeners(sceneEvents); + } + } + + try + { + collection.CollectionEvents.AddListeners(collectionEvents); + collection.SceneEvents.AddListeners(sceneEvents); + yield return collection.LoadRoutine(); + } + finally + { + collection.CollectionEvents.RemoveListeners(collectionEvents); + collection.SceneEvents.RemoveListeners(sceneEvents); + } + + yield return collection.HideTransitionRoutine(); + } + + #endregion + } +} diff --git a/Runtime/ScriptableSceneController.cs.meta b/Runtime/ScriptableSceneController.cs.meta new file mode 100644 index 0000000..fb35a4e --- /dev/null +++ b/Runtime/ScriptableSceneController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 246f0ab02f5d46df9710228de06c6874 +timeCreated: 1652879022 \ No newline at end of file diff --git a/Runtime/ScriptableSceneControllerDebugger.cs b/Runtime/ScriptableSceneControllerDebugger.cs new file mode 100644 index 0000000..17e5f93 --- /dev/null +++ b/Runtime/ScriptableSceneControllerDebugger.cs @@ -0,0 +1,218 @@ +using CHARK.ScriptableScenes.Events; +using CHARK.ScriptableScenes.Utilities; +using UnityEngine; + +namespace CHARK.ScriptableScenes +{ + /// + /// Component which can be used to print debug logs for a + /// . + /// + [AddComponentMenu( + AddComponentMenuConstants.BaseMenuName + "/Scriptable Scene Controller Debugger" + )] + [RequireComponent(typeof(ScriptableSceneController))] + internal sealed class ScriptableSceneControllerDebugger : MonoBehaviour + { + #region Editor Fields + + [SerializeField] + private bool isDebugCollectionEvents = true; + + [SerializeField] + private bool isDebugSceneEvents = true; + + #endregion + + #region Private Fields + + private ScriptableSceneController controller; + + #endregion + + #region Unity Lifecycle + + private void Awake() + { + controller = GetComponent(); + } + + private void OnEnable() + { + AddListeners(); + } + + private void OnDisable() + { + RemoveListeners(); + } + + #endregion + + #region Private Methods + + private void AddListeners() + { + if (isDebugCollectionEvents) + { + var collectionEvents = controller.CollectionEvents; + collectionEvents.OnLoadEntered += DebugLoadEntered; + collectionEvents.OnLoadExited += DebugLoadExited; + collectionEvents.OnLoadProgress += DebugLoadProgress; + collectionEvents.OnUnloadEntered += DebugUnloadEntered; + collectionEvents.OnUnloadExited += DebugUnloadExited; + } + + if (isDebugSceneEvents) + { + var sceneEvents = controller.SceneEvents; + sceneEvents.OnLoadEntered += DebugLoadEntered; + sceneEvents.OnLoadExited += DebugLoadExited; + sceneEvents.OnLoadProgress += DebugLoadProgress; + sceneEvents.OnUnloadEntered += DebugUnloadEntered; + sceneEvents.OnUnloadExited += DebugUnloadExited; + sceneEvents.OnActivateEntered += DebugActivateEntered; + sceneEvents.OnActivateExited += DebugActivateExited; + } + } + + private void RemoveListeners() + { + var collectionEvents = controller.CollectionEvents; + collectionEvents.OnLoadEntered -= DebugLoadEntered; + collectionEvents.OnLoadExited -= DebugLoadExited; + collectionEvents.OnLoadProgress -= DebugLoadProgress; + collectionEvents.OnUnloadEntered -= DebugUnloadEntered; + collectionEvents.OnUnloadExited -= DebugUnloadExited; + + var sceneEvents = controller.SceneEvents; + sceneEvents.OnLoadEntered -= DebugLoadEntered; + sceneEvents.OnLoadExited -= DebugLoadExited; + sceneEvents.OnLoadProgress -= DebugLoadProgress; + sceneEvents.OnUnloadEntered -= DebugUnloadEntered; + sceneEvents.OnUnloadExited -= DebugUnloadExited; + sceneEvents.OnActivateEntered -= DebugActivateEntered; + sceneEvents.OnActivateExited -= DebugActivateExited; + } + + #endregion + + #region Private Collection Debug Methods + + private void DebugLoadEntered(CollectionLoadEventArgs args) + { + Log( + "Collection Load Entered", + $"Name: {args.Collection.Name}" + ); + } + + private void DebugLoadExited(CollectionLoadEventArgs args) + { + Log( + "Collection Load Exited", + $"Name: {args.Collection.Name}" + ); + } + + private void DebugLoadProgress(CollectionLoadProgressEventArgs args) + { + Log( + "Collection Load Progress", + $"Collection Name: {args.Collection.Name} ({args.CollectionLoadProgress * 100f}%)", + $"Scene Name: {args.Scene.Name} ({args.SceneLoadProgress * 100f}%)" + ); + } + + private void DebugUnloadEntered(CollectionUnloadEventArgs args) + { + Log( + "Collection Unload Entered", + $"Name: {args.Collection.Name}" + ); + } + + private void DebugUnloadExited(CollectionUnloadEventArgs args) + { + Log( + "Collection Unload Exited", + $"Name: {args.Collection.Name}" + ); + } + + #endregion + + #region Private Scene Debug Methods + + private void DebugLoadEntered(SceneLoadEventArgs args) + { + Log( + "Scene Load Entered", + $"Name: {args.Scene.Name}" + ); + } + + private void DebugLoadExited(SceneLoadEventArgs args) + { + Log( + "Scene Load Exited", + $"Name: {args.Scene.Name}" + ); + } + + private void DebugLoadProgress(SceneLoadProgressEventArgs args) + { + Log( + $"Scene Load Progress", + $"Scene Name: {args.Scene.Name} ({args.Progress * 100f}%)" + ); + } + + private void DebugUnloadEntered(SceneUnloadEventArgs args) + { + Log( + "Scene Unload Entered", + $"Name: {args.Scene.Name}" + ); + } + + private void DebugUnloadExited(SceneUnloadEventArgs args) + { + Log( + "Scene Unload Exited", + $"Name: {args.Scene.Name}" + ); + } + + private void DebugActivateEntered(SceneActivateEventArgs args) + { + Log( + "Scene Activate Entered", + $"Name: {args.Scene.Name}" + ); + } + + private void DebugActivateExited(SceneActivateEventArgs args) + { + Log( + "Scene Activate Exited", + $"Name: {args.Scene.Name}" + ); + } + + #endregion + + #region Private Methods + + private void Log(string title, params string[] details) + { + Debug.Log( + $"{title}\n" + + $"{string.Join(", ", details)}", + this + ); + } + + #endregion + } +} diff --git a/Runtime/ScriptableSceneControllerDebugger.cs.meta b/Runtime/ScriptableSceneControllerDebugger.cs.meta new file mode 100644 index 0000000..3691b22 --- /dev/null +++ b/Runtime/ScriptableSceneControllerDebugger.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 82ac45f6e25d46e3923b99f16d0edd3e +timeCreated: 1653050677 \ No newline at end of file diff --git a/Runtime/Transitions.meta b/Runtime/Transitions.meta new file mode 100644 index 0000000..77d2e07 --- /dev/null +++ b/Runtime/Transitions.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 243833c9ffb94f3781d1e162768b9aee +timeCreated: 1653075595 \ No newline at end of file diff --git a/Runtime/Transitions/BaseScriptableSceneTransition.cs b/Runtime/Transitions/BaseScriptableSceneTransition.cs new file mode 100644 index 0000000..62edfbe --- /dev/null +++ b/Runtime/Transitions/BaseScriptableSceneTransition.cs @@ -0,0 +1,28 @@ +using System.Collections; +using UnityEngine; + +namespace CHARK.ScriptableScenes.Transitions +{ + /// + /// Transition used to transition between when they are + /// loaded and unloaded. + /// + public abstract class BaseScriptableSceneTransition : ScriptableObject + { + #region Public Methods + + /// + /// Enumerator which transitions into the . Called + /// before the collection is loaded. + /// + public abstract IEnumerator ShowRoutine(); + + /// + /// Enumerator which transitions out of the . Called + /// before the collection is unloaded. + /// + public abstract IEnumerator HideRoutine(); + + #endregion + } +} diff --git a/Runtime/Transitions/BaseScriptableSceneTransition.cs.meta b/Runtime/Transitions/BaseScriptableSceneTransition.cs.meta new file mode 100644 index 0000000..caffcb2 --- /dev/null +++ b/Runtime/Transitions/BaseScriptableSceneTransition.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 53075aeba1dc457aad228be543f268c1 +timeCreated: 1653074465 \ No newline at end of file diff --git a/Runtime/Transitions/FadeCanvas.cs b/Runtime/Transitions/FadeCanvas.cs new file mode 100644 index 0000000..710c6c6 --- /dev/null +++ b/Runtime/Transitions/FadeCanvas.cs @@ -0,0 +1,90 @@ +using CHARK.ScriptableScenes.Utilities; +using UnityEngine; +using UnityEngine.UI; + +namespace CHARK.ScriptableScenes.Transitions +{ + [AddComponentMenu( + AddComponentMenuConstants.BaseMenuName + "/Fade Canvas", + AddComponentMenuConstants.TransitionOrder + )] + [RequireComponent(typeof(CanvasGroup))] + [RequireComponent(typeof(Canvas))] + internal sealed class FadeCanvas : MonoBehaviour + { + #region Editor Fields + + [SerializeField] + private FadeScriptableSceneTransition transition; + + #endregion + + #region Private Fields + + // Optional raycaster. + private GraphicRaycaster graphicRaycaster; + + private CanvasGroup canvasGroup; + private Canvas canvas; + + #endregion + + #region Unity Lifecycle + + private void Awake() + { + graphicRaycaster = GetComponent(); + canvasGroup = GetComponent(); + canvas = GetComponent(); + } + + private void OnEnable() + { + if (transition) + { + transition.AddCanvas(this); + } + } + + private void OnDisable() + { + if (transition) + { + transition.RemoveCanvas(this); + } + } + + #endregion + + #region Internal Methods + + internal void SetAlpha(float alpha) + { + canvasGroup.alpha = alpha; + } + + internal void ShowCanvas() + { + if (graphicRaycaster) + { + graphicRaycaster.enabled = true; + } + + canvasGroup.enabled = true; + canvas.enabled = true; + } + + internal void HideCanvas() + { + if (graphicRaycaster) + { + graphicRaycaster.enabled = false; + } + + canvasGroup.enabled = false; + canvas.enabled = false; + } + + #endregion + } +} diff --git a/Runtime/Transitions/FadeCanvas.cs.meta b/Runtime/Transitions/FadeCanvas.cs.meta new file mode 100644 index 0000000..976cb7f --- /dev/null +++ b/Runtime/Transitions/FadeCanvas.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 975cbaf2353845d6b5e6d4ce9bcc4463 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - transition: {fileID: 11400000, guid: 9026c584bc045d74b91f5e7f2c48526c, type: 2} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Transitions/FadeScriptableSceneTransition.cs b/Runtime/Transitions/FadeScriptableSceneTransition.cs new file mode 100644 index 0000000..89901b9 --- /dev/null +++ b/Runtime/Transitions/FadeScriptableSceneTransition.cs @@ -0,0 +1,140 @@ +using System.Collections; +using System.Collections.Generic; +using CHARK.ScriptableScenes.Utilities; +using UnityEngine; + +namespace CHARK.ScriptableScenes.Transitions +{ + [CreateAssetMenu( + fileName = CreateAssetMenuConstants.BaseFileName + nameof(FadeScriptableSceneTransition), + menuName = CreateAssetMenuConstants.BaseMenuName + "/Fade Scriptable Scene Transition", + order = CreateAssetMenuConstants.TransitionOrder + )] + internal sealed class FadeScriptableSceneTransition : BaseScriptableSceneTransition + { + #region Editor Fields + + [Header("Alpha")] + [Tooltip("Canvas alpha which is used when the canvas is faded in")] + [Range(0f, 1f)] + [SerializeField] + private float fadeInAlpha = 1.0f; + + [Tooltip("Canvas alpha which is used when the canvas is faded out")] + [Range(0f, 1f)] + [SerializeField] + private float fadeOutAlpha; + + [Header("Timings")] + [Tooltip("Time taken to fade in the canvas")] + [Min(0f)] + [SerializeField] + private float fadeInDurationSeconds = 0.5f; + + [Tooltip("Amount of seconds to wait before fading out the canvas")] + [Min(0f)] + [SerializeField] + private float fadeOutWaitDurationSeconds = 2.0f; + + [Tooltip("Time taken to fade out the canvas")] + [Min(0f)] + [SerializeField] + private float fadeOutDurationSeconds = 0.5f; + + #endregion + + #region Private Fields + + private readonly List canvases = new List(); + + #endregion + + #region Public Methods + + public override IEnumerator ShowRoutine() + { + // Fade in the curtains. + ShowFadeCanvas(); + yield return FadeRoutine(fadeOutAlpha, fadeInAlpha, fadeInDurationSeconds); + } + + public override IEnumerator HideRoutine() + { + if (fadeOutWaitDurationSeconds > 0.0f) + { + yield return new WaitForSeconds(fadeOutWaitDurationSeconds); + } + + // Fade out the curtains. + yield return FadeRoutine(fadeInAlpha, fadeOutAlpha, fadeOutDurationSeconds); + HideFadeCanvas(); + } + + #endregion + + #region Internal Methods + + internal void AddCanvas(FadeCanvas canvas) + { + canvases.Add(canvas); + } + + internal void RemoveCanvas(FadeCanvas canvas) + { + canvases.Remove(canvas); + } + + #endregion + + #region Private Methods + + private IEnumerator FadeRoutine( + float from, + float to, + float duration + ) + { + var progress = 0f; + + while (progress < 1f) + { + var value = Mathf.Lerp(from, to, progress); + SetAlpha(value); + + progress += Time.unscaledDeltaTime / duration; + + yield return null; + } + + SetAlpha(to); + + yield return null; + } + + private void SetAlpha(float alpha) + { + foreach (var canvas in canvases) + { + canvas.SetAlpha(alpha); + } + } + + private void ShowFadeCanvas() + { + foreach (var canvas in canvases) + { + canvas.ShowCanvas(); + } + } + + private void HideFadeCanvas() + { + foreach (var canvas in canvases) + { + canvas.HideCanvas(); + } + } + + #endregion + } +} diff --git a/Runtime/Transitions/FadeScriptableSceneTransition.cs.meta b/Runtime/Transitions/FadeScriptableSceneTransition.cs.meta new file mode 100644 index 0000000..5313256 --- /dev/null +++ b/Runtime/Transitions/FadeScriptableSceneTransition.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 03097c79cf964f619e5637eea2046695 +timeCreated: 1653075623 \ No newline at end of file diff --git a/Runtime/Utilities.meta b/Runtime/Utilities.meta new file mode 100644 index 0000000..d1bbac2 --- /dev/null +++ b/Runtime/Utilities.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 907ba91a18dc435b9bd94e65fe4e59c4 +timeCreated: 1653037031 \ No newline at end of file diff --git a/Runtime/Utilities/AddComponentMenuConstants.cs b/Runtime/Utilities/AddComponentMenuConstants.cs new file mode 100644 index 0000000..0676030 --- /dev/null +++ b/Runtime/Utilities/AddComponentMenuConstants.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +namespace CHARK.ScriptableScenes.Utilities +{ + /// + /// Constants for . + /// + internal static class AddComponentMenuConstants + { + /// + /// Base menu name prefix. + /// + public const string BaseMenuName = "CHARK/Scriptable Scenes"; + + /// + /// Order of transition menu items. + /// + public const int TransitionOrder = 20; + } +} diff --git a/Runtime/Utilities/AddComponentMenuConstants.cs.meta b/Runtime/Utilities/AddComponentMenuConstants.cs.meta new file mode 100644 index 0000000..7a6b41c --- /dev/null +++ b/Runtime/Utilities/AddComponentMenuConstants.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6ef08274892f4914a8b6d6bde2249813 +timeCreated: 1653288951 \ No newline at end of file diff --git a/Runtime/Utilities/CreateAssetMenuConstants.cs b/Runtime/Utilities/CreateAssetMenuConstants.cs new file mode 100644 index 0000000..fd543a8 --- /dev/null +++ b/Runtime/Utilities/CreateAssetMenuConstants.cs @@ -0,0 +1,30 @@ +using UnityEngine; + +namespace CHARK.ScriptableScenes.Utilities +{ + /// + /// Constants for . + /// + internal static class CreateAssetMenuConstants + { + /// + /// Base file name (prefix). + /// + public const string BaseFileName = "New "; + + /// + /// Base menu name (sub-menu category). + /// + public const string BaseMenuName = "CHARK/Scriptable Scenes"; + + /// + /// Base menu order. + /// + public const int BaseOrder = -1000; + + /// + /// Order of transition menu items. + /// + public const int TransitionOrder = BaseOrder + 20; + } +} diff --git a/Runtime/Utilities/CreateAssetMenuConstants.cs.meta b/Runtime/Utilities/CreateAssetMenuConstants.cs.meta new file mode 100644 index 0000000..3ecc22b --- /dev/null +++ b/Runtime/Utilities/CreateAssetMenuConstants.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a043e337dedd489ebbe57f1b05325a1b +timeCreated: 1652879220 \ No newline at end of file diff --git a/Runtime/Utilities/MenuItemConstants.cs b/Runtime/Utilities/MenuItemConstants.cs new file mode 100644 index 0000000..1ca6635 --- /dev/null +++ b/Runtime/Utilities/MenuItemConstants.cs @@ -0,0 +1,20 @@ +using UnityEditor; + +namespace CHARK.ScriptableScenes.Utilities +{ + /// + /// Constants for . + /// + internal static class MenuItemConstants + { + /// + /// Base menu item name for Window menu items. + /// + public const string BaseWindowItemName = "Window/CHARK/Scriptable Scenes"; + + /// + /// Base menu item priority for Window menu items. + /// + public const int BaseWindowPriority = -1000; + } +} diff --git a/Runtime/Utilities/MenuItemConstants.cs.meta b/Runtime/Utilities/MenuItemConstants.cs.meta new file mode 100644 index 0000000..7b67213 --- /dev/null +++ b/Runtime/Utilities/MenuItemConstants.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8fddf3d6367e46b49ce317b0c2c4b0ab +timeCreated: 1653049146 \ No newline at end of file diff --git a/Runtime/Utilities/ScriptableSceneUtilities.cs b/Runtime/Utilities/ScriptableSceneUtilities.cs new file mode 100644 index 0000000..e6ec88c --- /dev/null +++ b/Runtime/Utilities/ScriptableSceneUtilities.cs @@ -0,0 +1,259 @@ +using System.Collections.Generic; +using System.Linq; +using CHARK.ScriptableScenes.Events; +using UnityEngine; +using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; + +namespace CHARK.ScriptableScenes.Utilities +{ + /// + /// Utilities for interacting with in Runtime. + /// + internal static class ScriptableSceneUtilities + { + #region Private Fields + + private static readonly string SelectedCollectionKey = + typeof(ScriptableSceneUtilities).FullName + "_" + "Guids"; + + #endregion + + #region Internal Methods + + /// + /// true if scene information is retrieved for or + /// false otherwise. + /// + internal static bool TryGetSceneDetails( + this Object obj, + out string scenePath, + out int sceneBuildIndex + ) + { + scenePath = string.Empty; + sceneBuildIndex = -1; + +#if UNITY_EDITOR + if (Application.isPlaying || obj == false) + { + return false; + } + + scenePath = UnityEditor.AssetDatabase.GetAssetPath(obj); + sceneBuildIndex = SceneUtility.GetBuildIndexByScenePath(scenePath); + return true; +#else + return false; +#endif + } + + /// + /// true if a GUID is retrieved for or false otherwise. + /// + internal static bool TryGetAssetGuid(this Object obj, out string guid) + { + guid = string.Empty; + +#if UNITY_EDITOR + if (Application.isPlaying || obj == false) + { + return false; + } + + var assetPath = UnityEditor.AssetDatabase.GetAssetPath(obj); + guid = UnityEditor.AssetDatabase.AssetPathToGUID(assetPath); + + return true; +#else + return false; +#endif + } + + /// + /// Sets currently selected collection. + /// + internal static void SetSelectedCollection(BaseScriptableSceneCollection collection) + { +#if UNITY_EDITOR + if (collection) + { + UnityEditor.EditorPrefs.SetString(SelectedCollectionKey, collection.Guid); + } +#endif + } + + /// + /// Clears currently selected collection. + /// + internal static void ClearSelectedCollection() + { +#if UNITY_EDITOR + UnityEditor.EditorPrefs.DeleteKey(SelectedCollectionKey); +#endif + } + + /// + /// true if collection which is selected in the Editor is retrieved or false + /// otherwise. + /// + internal static bool TryGetSelectedCollection( + out BaseScriptableSceneCollection collection + ) + { +#if UNITY_EDITOR + var guid = UnityEditor.EditorPrefs.GetString(SelectedCollectionKey); + if (string.IsNullOrEmpty(guid)) + { + collection = null; + return false; + } + + var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid); + var guidCollection = UnityEditor.AssetDatabase + .LoadAssetAtPath(path); + + if (guidCollection == false) + { + collection = null; + return false; + } + + collection = guidCollection; + return true; +#else + collection = null; + return false; +#endif + } + + /// + /// true if a collection which is currently loaded is retrieved or false + /// otherwise. + /// + internal static bool TryGetLoadedCollection(out BaseScriptableSceneCollection collection) + { +#if UNITY_EDITOR + collection = UnityEditor.AssetDatabase + .FindAssets($"t:{typeof(BaseScriptableSceneCollection)}") + .Select(UnityEditor.AssetDatabase.GUIDToAssetPath) + .Select(UnityEditor.AssetDatabase.LoadAssetAtPath) + .FirstOrDefault(IsLoaded); + + return collection != false; +#else + collection = null; + return false; +#endif + } + + /// + /// true if a is retrieved for given + /// or false otherwise. + /// + internal static bool TryGetLoadedScene(BaseScriptableScene scriptableScene, out Scene scene) + { + var targetSceneBuildIndex = scriptableScene.SceneBuildIndex; + scene = default; + + foreach (var loadedScene in GetLoadedScenes()) + { + var loadedSceneBuildIndex = loadedScene.buildIndex; + if (targetSceneBuildIndex == loadedSceneBuildIndex) + { + scene = loadedScene; + return true; + } + } + + return false; + } + + // TODO: how can we avoid this? + internal static void AddListeners( + this ICollectionEventHandler handler, + CollectionEventHandler otherHandler + ) + { + handler.OnLoadEntered += otherHandler.RaiseLoadEntered; + handler.OnLoadExited += otherHandler.RaiseLoadExited; + handler.OnLoadProgress += otherHandler.RaiseLoadProgress; + handler.OnUnloadEntered += otherHandler.RaiseUnloadEntered; + handler.OnUnloadExited += otherHandler.RaiseUnloadExited; + } + + // TODO: how can we avoid this? + internal static void RemoveListeners( + this ICollectionEventHandler handler, + CollectionEventHandler otherHandler + ) + { + handler.OnLoadEntered -= otherHandler.RaiseLoadEntered; + handler.OnLoadExited -= otherHandler.RaiseLoadExited; + handler.OnLoadProgress -= otherHandler.RaiseLoadProgress; + handler.OnUnloadEntered -= otherHandler.RaiseUnloadEntered; + handler.OnUnloadExited -= otherHandler.RaiseUnloadExited; + } + + // TODO: how can we avoid this? + internal static void AddListeners( + this ISceneEventHandler handler, + SceneEventHandler otherHandler + ) + { + handler.OnLoadEntered += otherHandler.RaiseLoadEntered; + handler.OnLoadExited += otherHandler.RaiseLoadExited; + handler.OnLoadProgress += otherHandler.RaiseLoadProgress; + handler.OnUnloadEntered += otherHandler.RaiseUnloadEntered; + handler.OnUnloadExited += otherHandler.RaiseUnloadExited; + handler.OnActivateEntered += otherHandler.RaiseActivateEntered; + handler.OnActivateExited += otherHandler.RaiseActivateExited; + } + + // TODO: how can we avoid this? + internal static void RemoveListeners( + this ISceneEventHandler handler, + SceneEventHandler otherHandler + ) + { + handler.OnLoadEntered -= otherHandler.RaiseLoadEntered; + handler.OnLoadExited -= otherHandler.RaiseLoadExited; + handler.OnLoadProgress -= otherHandler.RaiseLoadProgress; + handler.OnUnloadEntered -= otherHandler.RaiseUnloadEntered; + handler.OnUnloadExited -= otherHandler.RaiseUnloadExited; + handler.OnActivateEntered -= otherHandler.RaiseActivateEntered; + handler.OnActivateExited -= otherHandler.RaiseActivateExited; + } + + #endregion + + #region Private Methods + + private static IEnumerable GetLoadedScenes() + { + for (var sceneIndex = 0; sceneIndex < SceneManager.sceneCount; sceneIndex++) + { + var scene = SceneManager.GetSceneAt(sceneIndex); + if (scene.isLoaded) + { + yield return scene; + } + } + } + + private static bool IsLoaded(BaseScriptableSceneCollection collection) + { + var scriptableSceneIndices = collection.Scenes + .Select(scene => scene.SceneBuildIndex); + + var uniqueScriptableSceneIndices = new HashSet(scriptableSceneIndices); + + var loadedSceneIndices = GetLoadedScenes().Select(scene => scene.buildIndex); + var uniqueLoadedSceneIndices = new HashSet(loadedSceneIndices); + + return uniqueScriptableSceneIndices.SetEquals(uniqueLoadedSceneIndices); + } + + #endregion + } +} diff --git a/Runtime/Utilities/ScriptableSceneUtilities.cs.meta b/Runtime/Utilities/ScriptableSceneUtilities.cs.meta new file mode 100644 index 0000000..04596d8 --- /dev/null +++ b/Runtime/Utilities/ScriptableSceneUtilities.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 065e04ee9a56450e955fd93ef6d8d064 +timeCreated: 1653296951 \ No newline at end of file diff --git a/Samples~/CHARK.ScriptableScenes.Samples.asmdef b/Samples~/CHARK.ScriptableScenes.Samples.asmdef new file mode 100644 index 0000000..1e6bf1e --- /dev/null +++ b/Samples~/CHARK.ScriptableScenes.Samples.asmdef @@ -0,0 +1,16 @@ +{ + "name": "CHARK.ScriptableScenes.Samples", + "rootNamespace": "CHARK.ScriptableScenes.Samples", + "references": [ + "GUID:7cfa357a9d1c66644b9ddbad9d55c0a3" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Samples~/CHARK.ScriptableScenes.Samples.asmdef.meta b/Samples~/CHARK.ScriptableScenes.Samples.asmdef.meta new file mode 100644 index 0000000..7867c92 --- /dev/null +++ b/Samples~/CHARK.ScriptableScenes.Samples.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 50d7eea90da20ce4a8626e68fd97d26e +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests.meta b/Tests.meta new file mode 100644 index 0000000..12e0e70 --- /dev/null +++ b/Tests.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b25382c6eab644d08cea996eda6de2f1 +timeCreated: 1653052886 \ No newline at end of file diff --git a/Tests/Runtime.meta b/Tests/Runtime.meta new file mode 100644 index 0000000..97ec7b9 --- /dev/null +++ b/Tests/Runtime.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 640c0f5a96d24fbaab04a532838f6f6e +timeCreated: 1653052890 \ No newline at end of file diff --git a/Tests/Runtime/CHARK.ScriptableScenes.Tests.asmdef b/Tests/Runtime/CHARK.ScriptableScenes.Tests.asmdef new file mode 100644 index 0000000..5501842 --- /dev/null +++ b/Tests/Runtime/CHARK.ScriptableScenes.Tests.asmdef @@ -0,0 +1,22 @@ +{ + "name": "CHARK.ScriptableScenes.Tests", + "rootNamespace": "CHARK.ScriptableScenes.Tests", + "references": [ + "GUID:7cfa357a9d1c66644b9ddbad9d55c0a3", + "GUID:27619889b8ba8c24980f49ee34dbb44a" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS", + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/CHARK.ScriptableScenes.Tests.asmdef.meta b/Tests/Runtime/CHARK.ScriptableScenes.Tests.asmdef.meta new file mode 100644 index 0000000..ffaead1 --- /dev/null +++ b/Tests/Runtime/CHARK.ScriptableScenes.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d8d67b84515593f4d9dcca8a104788a5 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/ReflectionUtilities.cs b/Tests/Runtime/ReflectionUtilities.cs new file mode 100644 index 0000000..7f4e40a --- /dev/null +++ b/Tests/Runtime/ReflectionUtilities.cs @@ -0,0 +1,90 @@ +using System; +using System.Reflection; + +namespace CHARK.ScriptableScenes.Tests +{ + internal static class ReflectionUtilities + { + #region Private Fields + + private const BindingFlags PrivateFieldBindingFlags = + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; + + #endregion + + #region Internal Methods + + /// + /// Set private field value with give name on this object. + /// + internal static void SetField(this object obj, string name, object value) + { + if (obj.TryGetField(name, out var field)) + { + field.SetValue(obj, value); + } + } + + /// + /// Value named of type on + /// . + /// + /// + /// If value could not be retrieved. + /// + internal static T GetValue(this object obj, string name) + { + if (obj.TryGetValue(name, out var value)) + { + return value; + } + + throw new InvalidOperationException( + $"{obj} does not have a field of type {typeof(T)} named \"{name}\"" + ); + } + + /// + /// true if a of type is retrieved + /// from or false otherwise. + /// + internal static bool TryGetValue(this object obj, string name, out T value) + { + value = default; + + if (obj.TryGetField(name, out var rawField) == false) + { + return false; + } + + var rawValue = rawField.GetValue(obj); + if (rawValue is T typedValue) + { + value = typedValue; + return true; + } + + return false; + } + + /// + /// true if is retrieved from or + /// false otherwise. + /// + internal static bool TryGetField(this object obj, string name, out FieldInfo field) + { + var type = obj.GetType(); + + FieldInfo info; + do + { + info = type.GetField(name, PrivateFieldBindingFlags); + } while (info == null && (type = type.BaseType) != null); + + field = info; + return info != null; + } + + #endregion + } +} diff --git a/Tests/Runtime/ReflectionUtilities.cs.meta b/Tests/Runtime/ReflectionUtilities.cs.meta new file mode 100644 index 0000000..0200885 --- /dev/null +++ b/Tests/Runtime/ReflectionUtilities.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4726aa7beb214a51b1d073ed4f9c962d +timeCreated: 1653913711 \ No newline at end of file diff --git a/Tests/Runtime/ScriptableSceneControllerEventTest.cs b/Tests/Runtime/ScriptableSceneControllerEventTest.cs new file mode 100644 index 0000000..5fc089a --- /dev/null +++ b/Tests/Runtime/ScriptableSceneControllerEventTest.cs @@ -0,0 +1,234 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using CHARK.ScriptableScenes.Events; +using NUnit.Framework; +using UnityEngine.TestTools; + +namespace CHARK.ScriptableScenes.Tests +{ + internal class ScriptableSceneControllerEventTest + { + #region Private Fields + + private ScriptableSceneController controller; + private ICollectionEventHandler collectionEvents; + private ISceneEventHandler sceneEvents; + + private BaseScriptableSceneCollection collection; + private BaseScriptableScene scene; + + #endregion + + #region Public Collection Event Methods + + [SetUp] + public void SetUp() + { + controller = ScriptableSceneTestUtilities.CreateController(); + collectionEvents = controller.CollectionEvents; + sceneEvents = controller.SceneEvents; + + collection = ScriptableSceneTestUtilities.CreateCollection( + new ScriptableSceneTestUtilities.SceneDefinition {BuildIndex = 0} + ); + + scene = collection.Scenes.First(); + } + + [UnityTest] + public IEnumerator ShouldInvokeCollectionOnLoadEntered() + { + // Given: event handler with one listener. + CollectionLoadEventArgs args = default; + collectionEvents.OnLoadEntered += invokedArgs => args = invokedArgs; + + // When: loading a scene collection. + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(collection, args.Collection); + } + + [UnityTest] + public IEnumerator ShouldInvokeCollectionOnLoadExited() + { + // Given: event handler with one listener. + CollectionLoadEventArgs args = default; + collectionEvents.OnLoadExited += invokedArgs => args = invokedArgs; + + // When: loading a scene collection. + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(collection, args.Collection); + } + + [UnityTest] + public IEnumerator ShouldInvokeCollectionOnLoadProgress() + { + // Given: event handler with one listener. + var argsList = new List(); + collectionEvents.OnLoadProgress += invokedArgs => argsList.Add(invokedArgs); + + // When: loading a scene collection. + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured and load progress should be 100%. + var collections = argsList.Select(arg => arg.Collection); + Assert.That(collections, Is.All.EqualTo(collection)); + + var scenes = argsList.SelectMany(arg => arg.Collection.Scenes); + Assert.That(scenes, Is.All.EqualTo(scene)); + + var collectionLoadProgress = argsList.Select(arg => arg.CollectionLoadProgress).Max(); + Assert.AreEqual(1f, collectionLoadProgress); + + var sceneLoadProgress = argsList.Select(arg => arg.SceneLoadProgress).Max(); + Assert.AreEqual(1f, sceneLoadProgress); + } + + [UnityTest] + public IEnumerator ShouldInvokeCollectionOnUnloadEntered() + { + // Given: event handler with one listener. + CollectionUnloadEventArgs args = default; + collectionEvents.OnUnloadEntered += invokedArgs => args = invokedArgs; + + // When: loading a scene collection twice (to unload the 1st one). + yield return controller.LoadRoutine(collection); + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(collection, args.Collection); + } + + + [UnityTest] + public IEnumerator ShouldInvokeCollectionOnUnloadExited() + { + // Given: event handler with one listener. + CollectionUnloadEventArgs args = default; + collectionEvents.OnUnloadExited += invokedArgs => args = invokedArgs; + + // When: loading a scene collection twice (to unload the 1st one). + yield return controller.LoadRoutine(collection); + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(collection, args.Collection); + } + + #endregion + + #region Public Scene Event Methods + + [UnityTest] + public IEnumerator ShouldInvokeSceneOnLoadEntered() + { + // Given: event handler with one listener. + SceneLoadEventArgs args = default; + sceneEvents.OnLoadEntered += invokedArgs => args = invokedArgs; + + // When: loading a collection. + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(scene, args.Scene); + } + + [UnityTest] + public IEnumerator ShouldInvokeSceneOnLoadExited() + { + // Given: event handler with one listener. + SceneLoadEventArgs args = default; + sceneEvents.OnLoadExited += invokedArgs => args = invokedArgs; + + // When: loading a collection. + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(scene, args.Scene); + } + + [UnityTest] + public IEnumerator ShouldInvokeSceneOnLoadProgress() + { + // Given: event handler with one listener. + var argsList = new List(); + sceneEvents.OnLoadProgress += invokedArgs => argsList.Add(invokedArgs); + + // When: loading a scene collection. + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured and load progress should be 100%. + var scenes = argsList.Select(arg => arg.Scene); + Assert.That(scenes, Is.All.EqualTo(scene)); + + var sceneLoadProgress = argsList.Select(arg => arg.Progress).Max(); + Assert.AreEqual(1f, sceneLoadProgress); + } + + [UnityTest] + public IEnumerator ShouldInvokeSceneOnUnloadEntered() + { + // Given: event handler with one listener. + SceneUnloadEventArgs args = default; + sceneEvents.OnUnloadEntered += invokedArgs => args = invokedArgs; + + // When: loading a scene collection twice (to unload the 1st one). + yield return controller.LoadRoutine(collection); + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(scene, args.Scene); + } + + [UnityTest] + public IEnumerator ShouldInvokeSceneOnUnloadExited() + { + // Given: event handler with one listener. + SceneUnloadEventArgs args = default; + sceneEvents.OnUnloadExited += invokedArgs => args = invokedArgs; + + // When: loading a scene collection twice (to unload the 1st one). + yield return controller.LoadRoutine(collection); + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(scene, args.Scene); + } + + [UnityTest] + public IEnumerator ShouldInvokeSceneOnActivateEntered() + { + // Given: event handler with one listener. + SceneActivateEventArgs args = default; + sceneEvents.OnActivateEntered += invokedArgs => args = invokedArgs; + scene.SetField("isActivate", true); + + // When: loading a collection. + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(scene, args.Scene); + } + + [UnityTest] + public IEnumerator ShouldInvokeSceneOnActivateExited() + { + // Given: event handler with one listener. + SceneActivateEventArgs args = default; + sceneEvents.OnActivateExited += invokedArgs => args = invokedArgs; + scene.SetField("isActivate", true); + + // When: loading a collection. + yield return controller.LoadRoutine(collection); + + // Then: event arguments should be captured. + Assert.AreEqual(scene, args.Scene); + } + + #endregion + } +} diff --git a/Tests/Runtime/ScriptableSceneControllerEventTest.cs.meta b/Tests/Runtime/ScriptableSceneControllerEventTest.cs.meta new file mode 100644 index 0000000..0add4e6 --- /dev/null +++ b/Tests/Runtime/ScriptableSceneControllerEventTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b3f7b152f9634e2b9ee7cd50328e96e5 +timeCreated: 1653914814 \ No newline at end of file diff --git a/Tests/Runtime/ScriptableSceneControllerTest.cs b/Tests/Runtime/ScriptableSceneControllerTest.cs new file mode 100644 index 0000000..4fc5f69 --- /dev/null +++ b/Tests/Runtime/ScriptableSceneControllerTest.cs @@ -0,0 +1,73 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine.TestTools; + +namespace CHARK.ScriptableScenes.Tests +{ + internal class ScriptableSceneControllerTests + { + #region Private Fields + + private ScriptableSceneController controller; + + #endregion + + #region Public Methods + + [SetUp] + public void SetUp() + { + controller = ScriptableSceneTestUtilities.CreateController(); + } + + [UnityTest] + public IEnumerator ShouldLoadSceneCollection() + { + // Given: collection with two scenes. + var collection = ScriptableSceneTestUtilities.CreateCollection( + new ScriptableSceneTestUtilities.SceneDefinition { BuildIndex = 0 }, + new ScriptableSceneTestUtilities.SceneDefinition { BuildIndex = 1 } + ); + + // When: loading a collection. + yield return controller.LoadRoutine(collection); + + // Then: collection should be loaded. + if (controller.TryGetLoadedSceneCollection(out var loadedCollection)) + { + Assert.AreEqual(collection, loadedCollection); + } + else + { + Assert.Fail($"Collection \"{collection.Name}\" was not loaded"); + } + } + + [UnityTest] + public IEnumerator ShouldReloadSceneCollection() + { + // Given: loaded collection with two scenes. + var collection = ScriptableSceneTestUtilities.CreateCollection( + new ScriptableSceneTestUtilities.SceneDefinition { BuildIndex = 0 }, + new ScriptableSceneTestUtilities.SceneDefinition { BuildIndex = 1 } + ); + + yield return controller.LoadRoutine(collection); + + // When: reloading loaded collection. + yield return controller.ReloadRoutine(); + + // Then: collection should be loaded. + if (controller.TryGetLoadedSceneCollection(out var loadedCollection)) + { + Assert.AreEqual(collection, loadedCollection); + } + else + { + Assert.Fail($"Collection \"{collection.Name}\" was not reloaded"); + } + } + + #endregion + } +} diff --git a/Tests/Runtime/ScriptableSceneControllerTest.cs.meta b/Tests/Runtime/ScriptableSceneControllerTest.cs.meta new file mode 100644 index 0000000..8007e56 --- /dev/null +++ b/Tests/Runtime/ScriptableSceneControllerTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d1c2073fd7bc4b7ca5caeb4e420f9aba +timeCreated: 1653053117 \ No newline at end of file diff --git a/Tests/Runtime/ScriptableSceneTestUtilities.cs b/Tests/Runtime/ScriptableSceneTestUtilities.cs new file mode 100644 index 0000000..4087d08 --- /dev/null +++ b/Tests/Runtime/ScriptableSceneTestUtilities.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace CHARK.ScriptableScenes.Tests +{ + internal static class ScriptableSceneTestUtilities + { + #region Helper Classes + + /// + /// Defines how a scene should be created. + /// + internal class SceneDefinition + { + internal int BuildIndex { get; set; } + } + + #endregion + + #region Private Fields + + private const int MaxCollectionLoadWaitTicks = 120; + + #endregion + + #region Internal Methods + + /// + /// New controller which can be used to load scenes. + /// + internal static ScriptableSceneController CreateController() + { + var gameObject = new GameObject(nameof(ScriptableSceneController)); + var controller = gameObject.AddComponent(); + + return controller; + } + + /// + /// New collection with a set of scenes specified in . + /// + internal static BaseScriptableSceneCollection CreateCollection( + params SceneDefinition[] sceneDefinitions + ) + { + var collection = ScriptableObject.CreateInstance(); + collection.SetField("name", $"Test Collection ({Guid.NewGuid().ToString()})"); + + var scriptableScenes = collection + .GetValue>("scriptableScenes"); + + foreach (var sceneDefinition in sceneDefinitions) + { + var scene = ScriptableObject.CreateInstance(); + scene.SetField("sceneBuildIndex", sceneDefinition.BuildIndex); + + scriptableScenes.Add(scene); + } + + return collection; + } + + /// + /// Routine which loads provided and waits for loading to + /// finish. + /// + internal static IEnumerator LoadRoutine( + this ScriptableSceneController controller, + BaseScriptableSceneCollection collection + ) + { + controller.LoadSceneCollection(collection); + yield return controller.WaitUntilLoadedRoutine(); + } + + + /// + /// Routine which reloads currently loaded scenes and waits for reload to finish. + /// + internal static IEnumerator ReloadRoutine( + this ScriptableSceneController controller + ) + { + controller.ReloadLoadedSceneCollection(); + yield return controller.WaitUntilLoadedRoutine(); + } + + /// + /// Routine which waits for scene loading to finish. + /// + internal static IEnumerator WaitUntilLoadedRoutine( + this ScriptableSceneController controller + ) + { + var collectionLoadWaitTicks = 0; + yield return new WaitUntil(() => + { + if (controller.IsLoading == false) + { + return true; + } + + if (collectionLoadWaitTicks >= MaxCollectionLoadWaitTicks) + { + return true; + } + + collectionLoadWaitTicks++; + return false; + }); + } + + #endregion + } +} diff --git a/Tests/Runtime/ScriptableSceneTestUtilities.cs.meta b/Tests/Runtime/ScriptableSceneTestUtilities.cs.meta new file mode 100644 index 0000000..90b836c --- /dev/null +++ b/Tests/Runtime/ScriptableSceneTestUtilities.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 30b135fc3fa74d4188c1e4391fbf5ef5 +timeCreated: 1653914908 \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6dae861 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "com.chark.scriptable-scenes", + "displayName": "Scriptable Scenes", + "author": { + "name": "CHARK", + "url": "https://chark.io" + }, + "version": "0.0.1", + "unity": "2020.3", + "description": "Simple scene loading and management system for Unity Engine, implemented via scriptable objects.", + "keywords": [ + "scenes", + "scriptableobject", + "unity", + "architecture" + ], + "samples": [ + ] +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..4711956 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 62b09696cd8796448b9b09d137b4b278 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: