Files
Ipseity-Demo/Packages/OctoCore/Runtime/Scripts/Navigation/OctoNavigation.cs
T

386 lines
15 KiB
C#

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
{
/// <summary>
/// Scene management module.
/// Handles scene loading, transitions, and teleportation.
/// </summary>
[System.Serializable]
public class OctoNavigation : IOctoModule
{
/// <summary>
/// Currently loaded scene data.
/// </summary>
[OctoReadOnly] public OctoSceneData CurrentScene;
/// <summary>
/// Pending teleporter between scene changes (non-null only during scene transitions).
/// </summary>
[OctoReadOnly] public OctoTeleporterData PendingTeleporter;
/// <summary>
/// Invoked when scene loading starts.
/// </summary>
public Action<OctoSceneData> OnSceneLoadStarted;
/// <summary>
/// Invoked when scene loading completes.
/// </summary>
public Action<OctoSceneData> OnSceneLoadCompleted;
/// <summary>
/// Invoked when teleport starts.
/// </summary>
public Action<OctoTeleporterData, OctoTeleporterData> OnTeleportStarted;
/// <summary>
/// Invoked when teleport completes.
/// </summary>
public Action<OctoTeleporterData, OctoTeleporterData> 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() { }
/// <summary>
/// Load a scene using OctoSceneData.
/// </summary>
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));
}
/// <summary>
/// Teleport using OctoTeleporterData.
/// </summary>
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));
}
}
/// <summary>
/// Resolves the video clip to use for a transition: the scene/teleporter's own
/// clip if assigned, otherwise the global default from FadeSettings.
/// </summary>
private UnityEngine.Video.VideoClip ResolveClip(UnityEngine.Video.VideoClip specific, OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings)
{
return specific != null ? specific : fadeSettings.DefaultTransitionClip;
}
/// <summary>
/// Resolves the material to use for a transition: the scene/teleporter's own
/// material if assigned, otherwise the global default from FadeSettings.
/// </summary>
private Material ResolveMaterial(Material specific, OctoSettings.ScenesSettings.FadeTransitionSettings fadeSettings)
{
return specific != null ? specific : fadeSettings.DefaultTransitionMaterial;
}
/// <summary>
/// Waits for IOctoSceneTransition.CloseEyes() to complete. Bridges the callback-based
/// transition API with this class's coroutine-based flow.
/// </summary>
private IEnumerator CloseEyesRoutine(IOctoSceneTransition transition, float duration)
{
bool done = false;
transition.CloseEyes(duration, () => done = true);
while (!done)
yield return null;
}
/// <summary>
/// Waits for IOctoSceneTransition.OpenEyes() to complete. Bridges the callback-based
/// transition API with this class's coroutine-based flow.
/// </summary>
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<SceneInstance> 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<SceneInstance> 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);
}
}
}
}