using System; using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; using OctoCore.Navigation.Data; using OctoCore.Transitions; #if OCTO_ADDRESSABLES using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceProviders; #endif namespace OctoCore.Navigation { /// /// Scene management module. /// Handles scene loading, transitions, and teleportation. /// [System.Serializable] public class OctoNavigation : IOctoModule { /// /// Currently loaded scene data. /// [OctoReadOnly] public OctoSceneData CurrentScene; /// /// Pending teleporter between scene changes (non-null only during scene transitions). /// [OctoReadOnly] public OctoTeleporterData PendingTeleporter; /// /// Invoked when scene loading starts. /// public Action OnSceneLoadStarted; /// /// Invoked when scene loading completes. /// public Action OnSceneLoadCompleted; /// /// Invoked when teleport starts. /// public Action OnTeleportStarted; /// /// Invoked when teleport completes. /// public Action OnTeleportCompleted; private Coroutine _loadingCoroutine; private bool _setPlayerRotation; public void Init(OctoSettings settings) { // Find current scene in settings string currentSceneName = SceneManager.GetActiveScene().name; foreach (var sceneData in settings.Scenes.SceneDatas) { if (sceneData.SceneName == currentSceneName) { CurrentScene = sceneData; break; } } // The concrete transition effect (eyelid blink, plain fade, etc.) lives outside // this package and registers itself with OctoTransitionProvider at runtime. // Nothing to instantiate here. if (OctoTransitionProvider.Current == null) Debug.LogWarning("[OctoNavigation] No IOctoSceneTransition registered with OctoTransitionProvider. Scene transitions will have no visual effect."); if (OctoEvents.Instance) OctoEvents.Instance.SetScenesInitialized(); if (OctoCore.Instance.Development.Log) Debug.Log($"[OctoNavigation] Initialized (Addressables: {settings.Scenes.UseAddressables})"); } public void Tick() { } public void OnSceneChanged(Scene current, Scene next) { // Update CurrentScene reference if (OctoCore.Instance && OctoCore.Instance.Settings) { foreach (var sceneData in OctoCore.Instance.Settings.Scenes.SceneDatas) { if (sceneData.SceneName == next.name) { CurrentScene = sceneData; break; } } } } public void OnQuit() { } /// /// Load a scene using OctoSceneData. /// public void LoadScene(OctoSceneData sceneData) { if (sceneData == null) { Debug.LogError("[OctoNavigation] SceneData is null"); return; } if (OctoCore.Instance.Development.Log) Debug.LogError($"[OctoNavigation] LoadScene({sceneData.name}) called at t={Time.time:F2}\n{StackTraceUtility.ExtractStackTrace()}"); _loadingCoroutine = OctoCore.Instance.StartCoroutine(LoadSceneCoroutine(sceneData)); } /// /// Teleport using OctoTeleporterData. /// public void Teleport(OctoTeleporterData teleporter) { if (teleporter == null) { Debug.LogError("[OctoNavigation] Teleporter is null"); return; } if (OctoCore.Instance.Development.Log) Debug.LogError($"[OctoNavigation] Teleport({teleporter.name}) called at t={Time.time:F2}\n{StackTraceUtility.ExtractStackTrace()}"); OnTeleportStarted?.Invoke(null, teleporter); // Same scene teleport if (teleporter.TargetScene == null || teleporter.TargetScene == CurrentScene) { OctoCore.Instance.StartCoroutine(TeleportSameSceneCoroutine(teleporter)); } // Different scene teleport else { OctoCore.Instance.StartCoroutine(TeleportDifferentSceneCoroutine(teleporter)); } } /// /// Resolves the video clip to use for a transition: the scene/teleporter's own /// clip if assigned, otherwise the global default from FadeSettings. /// private UnityEngine.Video.VideoClip ResolveClip(UnityEngine.Video.VideoClip specific, OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings) { return specific != null ? specific : fadeSettings.DefaultTransitionClip; } /// /// Resolves the material to use for a transition: the scene/teleporter's own /// material if assigned, otherwise the global default from FadeSettings. /// private Material ResolveMaterial(Material specific, OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings) { return specific != null ? specific : fadeSettings.DefaultTransitionMaterial; } /// /// Waits for IOctoSceneTransition.CloseEyes() to complete. Bridges the callback-based /// transition API with this class's coroutine-based flow. /// private IEnumerator CloseEyesRoutine(IOctoSceneTransition transition, float duration) { bool done = false; transition.CloseEyes(duration, () => done = true); while (!done) yield return null; } /// /// Waits for IOctoSceneTransition.OpenEyes() to complete. Bridges the callback-based /// transition API with this class's coroutine-based flow. /// private IEnumerator OpenEyesRoutine(IOctoSceneTransition transition, float duration) { bool done = false; transition.OpenEyes(duration, () => done = true); while (!done) yield return null; } private IEnumerator LoadSceneCoroutine(OctoSceneData sceneData) { OnSceneLoadStarted?.Invoke(sceneData); // Get global fade settings OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings = OctoCore.Instance.Settings.Scenes.FadeSettings; IOctoSceneTransition transition = OctoTransitionProvider.Current; // Fade out if (sceneData.UseFadeTransition && transition != null) { transition.SetClip(ResolveClip(sceneData.TransitionClip, fadeSettings), ResolveMaterial(sceneData.TransitionMaterial, fadeSettings)); yield return CloseEyesRoutine(transition, fadeSettings.FadeOutDuration); transition.ShowVideoAfterDelay(0f); yield return new WaitForSeconds(fadeSettings.HoldClosedDuration); } // Check if using Addressables bool useAddressables = OctoCore.Instance.Settings.Scenes.UseAddressables; #if OCTO_ADDRESSABLES if (useAddressables) { yield return LoadSceneAddressables(sceneData); } else #endif { yield return LoadSceneStandard(sceneData); } // Fade in if (sceneData.UseFadeTransition && transition != null) { yield return OpenEyesRoutine(transition, fadeSettings.FadeInDuration); transition.HideVideo(); } OnSceneLoadCompleted?.Invoke(sceneData); if (OctoCore.Instance.Development.Log) Debug.Log($"[OctoNavigation] Scene loaded: {sceneData.SceneName}"); } private IEnumerator LoadSceneStandard(OctoSceneData sceneData) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneData.SceneName); yield return asyncLoad; } #if OCTO_ADDRESSABLES private IEnumerator LoadSceneAddressables(OctoSceneData sceneData) { AsyncOperationHandle handle = Addressables.LoadSceneAsync(sceneData.SceneName, LoadSceneMode.Single); yield return handle; if (handle.Status != AsyncOperationStatus.Succeeded) { Debug.LogError($"[OctoNavigation] Failed to load scene: {sceneData.SceneName}"); } } #endif private IEnumerator TeleportSameSceneCoroutine(OctoTeleporterData teleporter) { // Get global fade settings OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings = OctoCore.Instance.Settings.Scenes.FadeSettings; IOctoSceneTransition transition = OctoTransitionProvider.Current; // Fade out if (teleporter.UseFadeTransition && transition != null) { transition.SetClip(ResolveClip(teleporter.TransitionClip, fadeSettings), ResolveMaterial(teleporter.TransitionMaterial, fadeSettings)); yield return CloseEyesRoutine(transition, fadeSettings.FadeOutDuration); transition.ShowVideoAfterDelay(0f); yield return new WaitForSeconds(fadeSettings.HoldClosedDuration); } // Find target teleporter OctoTeleporter destination = OctoTeleporter.Find(teleporter); if (destination) { MovePlayerToTeleporter(destination, teleporter.SetPlayerRotation); destination.TriggerTeleport(); } else { Debug.LogError("[OctoNavigation] Target teleporter not found: " + (teleporter ? teleporter.name : "null")); } // Fade in if (teleporter.UseFadeTransition && transition != null) { yield return OpenEyesRoutine(transition, fadeSettings.FadeInDuration); transition.HideVideo(); } OnTeleportCompleted?.Invoke(null, teleporter); } private IEnumerator TeleportDifferentSceneCoroutine(OctoTeleporterData teleporter) { // Get global fade settings OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings = OctoCore.Instance.Settings.Scenes.FadeSettings; IOctoSceneTransition transition = OctoTransitionProvider.Current; // Fade out if (teleporter.UseFadeTransition && transition != null) { transition.SetClip(ResolveClip(teleporter.TransitionClip, fadeSettings), ResolveMaterial(teleporter.TransitionMaterial, fadeSettings)); yield return CloseEyesRoutine(transition, fadeSettings.FadeOutDuration); transition.ShowVideoAfterDelay(0f); yield return new WaitForSeconds(fadeSettings.HoldClosedDuration); } // Store pending teleporter PendingTeleporter = teleporter; _setPlayerRotation = teleporter.SetPlayerRotation; if (OctoCore.Instance.Settings.Scenes.UseAddressables) { #if OCTO_ADDRESSABLES AsyncOperationHandle handle = Addressables.LoadSceneAsync(teleporter.TargetScene.SceneName, LoadSceneMode.Single); yield return handle; if (handle.Status != AsyncOperationStatus.Succeeded) { Debug.LogError("[OctoNavigation] Failed to load scene: " + teleporter.TargetScene.SceneName); PendingTeleporter = null; yield break; } #endif } else { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(teleporter.TargetScene.SceneName); yield return asyncLoad; } // CompletePendingTeleport will be handled by OctoTeleporters } public void EvaluatePendingTeleport(OctoTeleporter teleporter) { if (PendingTeleporter != null && teleporter != null && teleporter.TeleporterData != null && teleporter.TeleporterData == PendingTeleporter) { PendingTeleporter = null; OctoCore.Instance.StartCoroutine(CompletePendingTeleport(teleporter)); } } private IEnumerator CompletePendingTeleport(OctoTeleporter teleporter) { if (teleporter) { MovePlayerToTeleporter(teleporter, _setPlayerRotation); teleporter.TriggerTeleport(); } else { Debug.LogError("[OctoNavigation] Target teleporter not found in new scene: " + teleporter.name); } // Get global fade settings OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings = OctoCore.Instance.Settings.Scenes.FadeSettings; IOctoSceneTransition transition = OctoTransitionProvider.Current; // Fade in if (transition != null) { yield return OpenEyesRoutine(transition, fadeSettings.FadeInDuration); transition.HideVideo(); } OnTeleportCompleted?.Invoke(null, teleporter.TeleporterData); } private void MovePlayerToTeleporter(OctoTeleporter teleporter, bool setRotation) { if (Components.OctoPlayer.Instance) { Components.OctoPlayer.Instance.Teleport(teleporter.transform.position, setRotation ? teleporter.transform.rotation : Components.OctoPlayer.Instance.transform.rotation); } else if (OctoTeleporterProxy.Instance) { if (setRotation) OctoTeleporterProxy.Instance.Teleport(teleporter.transform.position, teleporter.transform.rotation); else OctoTeleporterProxy.Instance.Teleport(teleporter.transform.position); } } } }