using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using UnityEngine.SceneManagement; using Benchmarking; using Cinemachine; public class SceneTransitionManager : MonoBehaviour { [Tooltip("Enable to debug the transition effect")] //TODO: do we want to remove this? [SerializeField] private bool m_OverrideTransition; [SerializeField][Range(0, 1)] private float m_ManualTransition; [Tooltip("The amount of time it takes to transition between two scenes")] [SerializeField] private float m_TransitionTime; private Camera m_MainCamera; private Camera m_ScreenCamera; private CharacterController m_Player; private PlayerManager m_CameraManager; private bool m_InitialSceneLoad; private static SceneTransitionManager instance; [Tooltip("Layers to render when in a location")] [SerializeField] private LayerMask locationLayer; [Tooltip("Layers to render when in the terminal")] [SerializeField] private LayerMask m_TerminalLayer; private bool InTerminal = true; private bool m_EneableCharacterInFollowingFrame = false; private SceneLoader m_Loader; private Transform spawnTransform; private Vector3 m_PositionAtLock; private Quaternion m_RotationAtLock; private bool InTransition = false; private bool CoolingOff = false; //After teleporting private float ElapsedTimeInTransition = 0; private Dictionary registeredScenes; private SceneMetaData screenScene; private SceneMetaData currentScene; private int m_ScenesLoading; private Vector3 m_CameraPosition; private Quaternion m_CameraRotation; //Used for cinemachine transition private MediaSceneLoader m_MediaSceneLoader; private int m_TransitionAmountShaderProperty; private bool m_ScreenOff; void Awake() { if (PerformanceTest.RunningBenchmark) { Destroy(gameObject); return; } SetupSingleton(); SetupReferences(); SetupInitialState(); } #region Awake public void SetupSingleton() { if (instance != null) { Destroy(gameObject); return; } instance = this; DontDestroyOnLoad(gameObject); } public void SetupReferences() { m_Player = GameObject.Find("PlayerCapsule")?.GetComponent(); //TODO: Don't hardcode string if (m_Player == null) { Debug.Log("Couldn't find character controller"); } m_CameraManager = m_Player.transform.parent.GetComponent(); m_MainCamera = GameObject.FindGameObjectWithTag("MainCamera")?.GetComponent(); if (m_MainCamera == null) { Debug.Log("Couldn't find Main Camera"); } m_ScreenCamera = GameObject.FindGameObjectWithTag("ScreenCamera").GetComponent(); if (m_ScreenCamera == null) { Debug.Log("Couldn't find Screen Camera"); } m_ScreenCamera.GetComponent().enabled = false; m_TransitionAmountShaderProperty = Shader.PropertyToID("_TransitionAmount"); } public void SetupInitialState() { InTerminal = true; m_InitialSceneLoad = true; registeredScenes = new Dictionary(); m_ScreenOff = true; RenderSettings.defaultReflectionMode = DefaultReflectionMode.Custom; } #endregion void Update() { if (m_EneableCharacterInFollowingFrame) { m_Player.GetComponent().enabled = true; m_EneableCharacterInFollowingFrame = false; } float t = m_OverrideTransition ? m_ManualTransition : ElapsedTimeInTransition / m_TransitionTime; if (InTransition) { ElapsedTimeInTransition += Time.deltaTime; if (ElapsedTimeInTransition > m_TransitionTime) { TriggerTeleport(); } ElapsedTimeInTransition = Mathf.Min(m_TransitionTime, ElapsedTimeInTransition); } else { ElapsedTimeInTransition -= Time.deltaTime * 3; if (ElapsedTimeInTransition < 0 && CoolingOff) { CoolingOff = false; } ElapsedTimeInTransition = Mathf.Max(0, ElapsedTimeInTransition); } //Update weights of post processing volumes if (m_Loader != null && !CoolingOff) { float tSquared = t * t; m_Loader.SetVolumeWeights(1 - tSquared); } Shader.SetGlobalFloat(m_TransitionAmountShaderProperty, t); } private void TriggerTeleport() { InTransition = false; if (m_Loader != null) { m_Loader.SetVolumeWeights(1); } if (m_MediaSceneLoader) //check this some other way { CinemachineTeleport(); } else { Teleport(); } m_Loader = null; CoolingOff = true; } /// /// This function is called per camera by the render pipeline. /// We use it to set up light and render settings (skybox etc) for the different scenes as they are displayed /// void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera) { bool isMainCamera = camera.CompareTag("MainCamera"); if (!isMainCamera && screenScene == null) { //If no screen scene is loaded, no setup needs to be done for it return; } //Toggle main light if (camera.cameraType == CameraType.SceneView) { ToggleMainLight(currentScene, true); ToggleMainLight(screenScene, false); } else { ToggleMainLight(currentScene, isMainCamera); ToggleMainLight(screenScene, !isMainCamera); } //Setup render settings SceneMetaData sceneToRender = isMainCamera || camera.cameraType == CameraType.SceneView ? currentScene : screenScene; RenderSettings.fog = sceneToRender.FogEnabled; RenderSettings.skybox = sceneToRender.skybox; if (sceneToRender.reflection != null) { RenderSettings.customReflectionTexture = sceneToRender.reflection; } if (!isMainCamera && camera.cameraType == CameraType.Game) { camera.GetComponent().UpdateWithOffset(); } } private void ToggleMainLight(SceneMetaData scene, bool value) { if (scene != null && scene.mainLight != null) { scene.mainLight.SetActive(value); } } public static void CinemachineTeleport() { instance.InTerminal = !instance.InTerminal; instance.UpdateCullingMasks(); Transform flythroughRoot = instance.m_MediaSceneLoader.transform; if (!instance.InTerminal) { flythroughRoot.position = instance.m_ScreenCamera.GetComponent().GetOffset(); instance.m_MediaSceneLoader.GetTerminalSceneLoader().SetCurrentVolume(instance.m_Loader.GetDestinationVolume()); instance.screenScene.TerminalLoader.SetActive(true); } else { flythroughRoot.position = Vector3.zero; instance.m_Loader = instance.m_MediaSceneLoader.GetTerminalSceneLoader(); } (instance.screenScene, instance.currentScene) = (instance.currentScene, instance.screenScene); } public static void Teleport() { if (!instance.currentScene || !instance.screenScene) { Debug.LogError("Can't teleport without two scenes enabled"); return; } instance.InTerminal = !instance.InTerminal; instance.UpdateCullingMasks(); //Swap Camera positions Transform playerTransform = instance.m_Player.transform; //Disable character controller while manipulating positions StarterAssets.FirstPersonController controller = playerTransform.GetComponent(); controller.enabled = false; bool newPositionLocked = instance.screenScene.CameraLockTransform != null; bool comingFromLockedPosition = instance.currentScene.CameraLockTransform != null; if (newPositionLocked) { //Cache transform player before moving instance.m_PositionAtLock = playerTransform.position; instance.m_RotationAtLock = playerTransform.rotation; //Set position, parent and rotation to new locked location Transform cameraLockTransform = instance.screenScene.CameraLockTransform; playerTransform.parent.parent = cameraLockTransform; playerTransform.position = cameraLockTransform.position; playerTransform.rotation = cameraLockTransform.rotation; //Disable the player to prevent them from moving instance.m_Player.enabled = false; instance.m_MainCamera.GetComponent().renderPostProcessing = false; //TODO: this is hardcoded for the cockpit. Should probably be in the metadata DisableScene(instance.currentScene); } else { //Find the offset between the player camera and feet positions Vector3 playerCameraOffset = instance.m_MainCamera.transform.position - instance.m_Player.transform.position; //Position the player at the screen camera position playerTransform.position = instance.m_ScreenCamera.transform.position - playerCameraOffset; //Toggle the offset of the screen camera to put it where the player used to be OffsetCamera oc = instance.m_ScreenCamera.GetComponent(); oc.ToggleOffset(); //Reset transform if teleporting from a locked position if (comingFromLockedPosition) { Transform playerParent = playerTransform.parent; playerParent.rotation = Quaternion.identity; playerParent.parent = null; DontDestroyOnLoad(playerParent); playerTransform.rotation = instance.m_RotationAtLock; playerTransform.position = instance.m_PositionAtLock; instance.m_Player.enabled = true; EnableScene(instance.screenScene); } instance.m_MainCamera.GetComponent().renderPostProcessing = true; //see same line in the locked transform case } instance.m_CameraManager.FlythroughDirector = instance.screenScene.FlythroughDirector; instance.m_MainCamera.GetComponent().m_WorldUpOverride = instance.screenScene.WorldUpTransform; //Enable or disable post based on what the new scene needs UniversalAdditionalCameraData mainCameraData = instance.m_MainCamera.GetComponent(); mainCameraData.renderPostProcessing = instance.screenScene.PostProcessingEnabled; mainCameraData.SetRenderer(instance.screenScene.RendererIndex); //Reenable controller after teleporting instance.m_EneableCharacterInFollowingFrame = true; SceneManager.SetActiveScene(instance.screenScene.Scene); //This is weird RenderSettings.defaultReflectionMode = DefaultReflectionMode.Custom; //Swap references to screen and current scene (instance.screenScene, instance.currentScene) = (instance.currentScene, instance.screenScene); //Setup terminal loader so player can get back and reset the timeline director instance.SetTerminalLoaderAndDirector(instance.screenScene, false); instance.SetTerminalLoaderAndDirector(instance.currentScene, true); } private void UpdateCullingMasks() { if (instance.InTerminal) { //Add to mask instance.m_MainCamera.cullingMask |= instance.m_TerminalLayer; instance.m_ScreenCamera.cullingMask |= instance.locationLayer; //Remove from mask instance.m_MainCamera.cullingMask ^= instance.locationLayer; instance.m_ScreenCamera.cullingMask ^= instance.m_TerminalLayer; } else { //Add to mask instance.m_MainCamera.cullingMask ^= instance.m_TerminalLayer; instance.m_ScreenCamera.cullingMask ^= instance.locationLayer; //Remove from mask instance.m_MainCamera.cullingMask |= instance.locationLayer; instance.m_ScreenCamera.cullingMask |= instance.m_TerminalLayer; } } private void SetTerminalLoaderAndDirector(SceneMetaData scene, bool isActive) { if (scene.TerminalLoader != null) { scene.TerminalLoader.SetActive(isActive); } if (scene.SequenceDirector != null) { scene.SequenceDirector.time = 0; scene.SequenceDirector.enabled = isActive; if (isActive) { scene.SequenceDirector.Play(); } } } #region On Enable/Disable private void OnEnable() { RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering; SceneManager.sceneLoaded += OnSceneLoaded; } private void OnDisable() { RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering; SceneManager.sceneLoaded -= OnSceneLoaded; } #endregion #region Scene Loading /// /// This function is called by the metadata script to notify of its existence /// public static void RegisterScene(string name, SceneMetaData metaData) { instance.registeredScenes.Add(name, metaData); if (instance.currentScene == null) //First loaded scene get's assigned to current { instance.currentScene = metaData; } } public static void NotifySceneLoading() { instance.m_ScenesLoading++; instance.m_Player.GetComponent().enabled = false; } void OnSceneLoaded(Scene scene, LoadSceneMode mode) { instance.m_ScenesLoading--; if(instance.m_ScenesLoading == 0) instance.m_Player.GetComponent().enabled = true; } /// /// This function is called by the scene loader when the player enters its trigger /// public static void EnableScene(SceneLoader sceneLoader) { if (!IsAvailable()) return; SceneMetaData sceneMetaData = instance.registeredScenes[sceneLoader.SceneName]; if (sceneMetaData == null) { throw new Exception("Trying to enable unregistered scene"); } Debug.Log("Enabling this scene: " + sceneMetaData.Scene.name); instance.m_Loader = sceneLoader; instance.m_InitialSceneLoad = false; instance.screenScene = sceneMetaData; instance.currentScene.TerminalLoader = sceneLoader.ControllPanel; LightProbes.TetrahedralizeAsync(); //Enable game objects sceneMetaData.Root.SetActive(true); //Reset any director that needs to play if (sceneMetaData.SequenceDirector != null) { sceneMetaData.SequenceDirector.time = 0; sceneMetaData.SequenceDirector.Play(); } //Set the offset of the screen camera if (sceneMetaData.SpawnTransform != null) { instance.m_ScreenCamera.GetComponent().SetOffset( sceneMetaData.SpawnTransform.position - instance.m_Loader.ReferencePoint.position); } //Switch on the screens if (sceneLoader.screen != null) { sceneLoader.screen.TurnScreenOn(); } //Set the renderer index int index = sceneMetaData.RendererIndex > 1 ? sceneMetaData.RendererIndex : 1; instance.m_ScreenCamera.GetComponent().SetRenderer(index); instance.m_ScreenCamera.GetComponent().enabled = true; instance.m_ScreenOff = false; } public static void EnableScene(SceneMetaData sceneMetaData) { sceneMetaData.Root.SetActive(true); instance.m_ScreenCamera.GetComponent().enabled = true; } public static void DisableScene(SceneLoader sceneLoader) { if (!IsAvailable()) return; SceneMetaData sceneMetaData = instance.registeredScenes[sceneLoader.SceneName]; if (sceneMetaData == instance.currentScene) { Debug.Log("Trying to disable current scene"); return; } Debug.Log("Disabling this scene: " + sceneMetaData.Scene.name); LightProbes.TetrahedralizeAsync(); //Turn off the screen and disable the root object in the scene once screen is completely shut off if (sceneLoader.screen != null) { sceneLoader.screen.TurnScreenOff(() => { if (instance.m_ScreenOff) { sceneMetaData.Root.SetActive(false); instance.m_ScreenCamera.GetComponent().enabled = false; } }); } instance.m_ScreenOff = true; } public static void DisableScene(SceneMetaData sceneMetaData) { sceneMetaData.Root.SetActive(false); instance.m_ScreenCamera.GetComponent().enabled = false; } public static void DisableLoadedScene() { if (instance.m_Loader == null) return; DisableScene(instance.m_Loader); } public static void StartTransition() { instance.InTransition = true; } public static void StartTransition(MediaSceneLoader mediaSceneLoader) { if (!IsAvailable()) return; instance.m_MediaSceneLoader = mediaSceneLoader; if (!instance.InTerminal) { instance.m_Loader = instance.currentScene.TerminalLoader.GetComponentInChildren(); } StartTransition(); } public static void StopTransition() { instance.InTransition = false; } public static bool DissolveNeeded() { return instance != null && instance.ElapsedTimeInTransition > 0.001f; } #endregion #region Getters public static bool IsLoaded(string sceneName) { return instance.registeredScenes.ContainsKey(sceneName); } public static RenderTexture GetScreenRT() { return instance.m_ScreenCamera.activeTexture; } public static bool HasNotLoadedScene() { return instance.m_InitialSceneLoad; } public static bool IsAvailable() { return instance != null; } public static GameObject GetMainCamera() { return instance.m_MainCamera.gameObject; } public static SceneMetaData GetCurrentSceneData() { return instance.currentScene; } public static bool IsInTerminal() { return instance.InTerminal; } #endregion }