using System; using System.Collections; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; [Serializable] public class IntEvent : UnityEvent {} public class WorldSwitcher : MonoBehaviour { [Range(0, 5)] [SerializeField] float transitionDuration; /*[Range(0, 5)] [SerializeField] float fadeDuration;*/ [SerializeField] AnimationCurve transitionCurve; //[SerializeField] AnimationCurve fadeCurve; [SerializeField] WorldInfo[] worldInfos; [Range(0, 1)] [SerializeField] float tabWidth; [SerializeField] float healthBarPosFromTabEdge; [SerializeField] float buttonPosFromTabEdge; [SerializeField] float alertPosFromTabEdge; //TODO Hardcode [SerializeField] float quadOffset; int lastWorldIndex; int currentWorldIndex; Coroutine transition; bool supportHDR; static readonly int UVOffset = Shader.PropertyToID("_UVOffset"); //static readonly int Opacity = Shader.PropertyToID("_Opacity"); public IntEvent OnChangeWorld; public int CurrentWorldIndex => currentWorldIndex; void Awake() { if (worldInfos.Length != 3) Debug.LogWarning("For now, WorldSwitcher should have 3 worlds."); currentWorldIndex = 1; lastWorldIndex = 0; supportHDR = SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.DefaultHDR); for (int i = 0; i < worldInfos.Length; i++) { worldInfos[i].material = worldInfos[i].renderQuad.GetComponent().material; worldInfos[i].GenerateRenderTexture(quadOffset, supportHDR); } ResetQuadPositions(); } public void SwitchWorld(int index) { if (transition != null) return; if (index == currentWorldIndex || index < 0 || index > worldInfos.Length - 1) return; //TODO Block window resize during transition? foreach (WorldInfo worldInfo in worldInfos) { if (Screen.width != worldInfo.texture.width || Screen.height != worldInfo.texture.height) worldInfo.GenerateRenderTexture(quadOffset, supportHDR); } lastWorldIndex = currentWorldIndex; currentWorldIndex = index; OnChangeWorld.Invoke(currentWorldIndex); transition = StartCoroutine(TransitionCamera(currentWorldIndex - lastWorldIndex < 0)); } public void OnDimension1(InputAction.CallbackContext ctx) { if (ctx.performed) SwitchWorld(0); } public void OnDimension2(InputAction.CallbackContext ctx) { if (ctx.performed) SwitchWorld(1); } public void OnDimension3(InputAction.CallbackContext ctx) { if (ctx.performed) SwitchWorld(2); } /*void OnGUI() { for (int i = 0; i < worldInfos.Length; ++i) { if (GUILayout.Button($"World {i}")) SwitchWorld(i); } }*/ IEnumerator TransitionCamera(bool fromRight) { WorldInfo lastWorld = worldInfos[lastWorldIndex]; float startTime = Time.time; Camera lastCam = lastWorld.camera; if (fromRight) { for (int i = lastWorldIndex - 1; i >= currentWorldIndex; --i) { worldInfos[i].healthBar.gameObject.SetActive(false); worldInfos[i].button.gameObject.SetActive(false); worldInfos[i].alert.gameObject.SetActive(false); } } else { for (int i = lastWorldIndex + 1; i <= currentWorldIndex; ++i) { worldInfos[i].healthBar.gameObject.SetActive(false); worldInfos[i].button.gameObject.SetActive(false); worldInfos[i].alert.gameObject.SetActive(false); } } while (Time.time < startTime + transitionDuration) { float t = transitionCurve.Evaluate((Time.time - startTime) / transitionDuration); if (fromRight) { for (int i = lastWorldIndex - 1; i >= currentWorldIndex; --i) { worldInfos[i].renderQuad.position = GetQuadOffset(lastCam, worldInfos[i], i, true, true, t); worldInfos[i].material.SetFloat(UVOffset, GetUVOffset(t, true, i)); } } else { for (int i = lastWorldIndex + 1; i <= currentWorldIndex; ++i) { worldInfos[i].renderQuad.position = GetQuadOffset(lastCam, worldInfos[i], i, false, true, t); worldInfos[i].material.SetFloat(UVOffset, GetUVOffset(t, false, i)); } } yield return null; } /*startTime = Time.time; while (Time.time < startTime + fadeDuration) { worldInfos[currentWorldIndex].material.SetFloat( Opacity, 1f - fadeCurve.Evaluate((Time.time - startTime) / fadeDuration) ); yield return null; }*/ ResetQuadPositions(); transition = null; } Vector3 GetQuadOffset(Camera cam, WorldInfo worldInfo, int index, bool fromRight, bool moving, float t = 0f) { float x = fromRight ? Mathf.Lerp((1 + index) * tabWidth, 1f - (worldInfos.Length - 1 - index) * tabWidth, t) : Mathf.Lerp(1f - tabWidth * (worldInfos.Length - index), index * tabWidth, t); Vector3 quadHalfWidthOffset = Vector3.right * (fromRight ? -worldInfo.renderQuad.localScale.x / 2f : worldInfo.renderQuad.localScale.x / 2f); float depthOffset = moving ? Mathf.Abs(currentWorldIndex - index) * .0001f : -Mathf.Abs(currentWorldIndex - index) * .0001f; //TODO Offset epsilon return cam.ViewportToWorldPoint( new Vector3( x, .5f, quadOffset + depthOffset ) ) + quadHalfWidthOffset; } float GetUVOffset(float t, bool fromRight, int index) { return fromRight ? Mathf.Lerp(-1f + tabWidth * (index + 1), -(worldInfos.Length - 1 - index) * tabWidth, t) : Mathf.Lerp(1f - tabWidth * (worldInfos.Length - index), index * tabWidth, t); } float GetTabUIPos(float t, bool fromRight, int index, float offset) { return fromRight ? Mathf.Lerp(tabWidth * (index + 1), -(worldInfos.Length - 1 - index) * tabWidth, t) - offset : Mathf.Lerp(1f - tabWidth * (worldInfos.Length - index), index * tabWidth, t) + offset; } void ResetQuadPositions() { Camera currCam = worldInfos[currentWorldIndex].camera; LayerMask currLayer = worldInfos[currentWorldIndex].layer; for (int i = 0; i < worldInfos.Length; ++i) { bool usingRenderTexture = i != currentWorldIndex; worldInfos[i].SetUsingRenderTexture(usingRenderTexture, currLayer); //worldInfos[i].material.SetFloat(Opacity, 1f); if (usingRenderTexture) { bool fromRight = i - currentWorldIndex < 0; worldInfos[i].renderQuad.position = GetQuadOffset(currCam, worldInfos[i], i, fromRight, false); worldInfos[i].material.SetFloat(UVOffset, GetUVOffset(0f, fromRight, i)); worldInfos[i].healthBar.gameObject.SetActive(true); worldInfos[i].healthBar.position = new Vector3( Screen.width * GetTabUIPos(0f, fromRight, i, healthBarPosFromTabEdge), worldInfos[i].healthBar.position.y, worldInfos[i].healthBar.position.z ); worldInfos[i].button.gameObject.SetActive(true); worldInfos[i].button.position = new Vector3( Screen.width * GetTabUIPos(0f, fromRight, i, buttonPosFromTabEdge), worldInfos[i].button.position.y, worldInfos[i].button.position.z ); worldInfos[i].alert.gameObject.SetActive(true); worldInfos[i].alert.position = new Vector3( Screen.width * GetTabUIPos(0f, fromRight, i, alertPosFromTabEdge), worldInfos[i].alert.position.y, worldInfos[i].alert.position.z ); }else { worldInfos[i].healthBar.gameObject.SetActive(false); worldInfos[i].button.gameObject.SetActive(false); worldInfos[i].alert.gameObject.SetActive(false); } } } } [Serializable] struct WorldInfo { public Camera camera; [HideInInspector] public RenderTexture texture; public Transform renderQuad; [HideInInspector] public Material material; public RectTransform healthBar; public RectTransform button; public RectTransform alert; public int layer; static readonly int CameraTexture = Shader.PropertyToID("_CameraTexture"); public void SetUsingRenderTexture(bool usingTexture, int layer) { if (usingTexture) { camera.targetTexture = texture; renderQuad.gameObject.SetActive(true); renderQuad.gameObject.layer = layer; } else { camera.targetTexture = null; renderQuad.gameObject.SetActive(false); } } public void GenerateRenderTexture(float quadOffset, bool supportHDR) { bool usingTexture = !ReferenceEquals(camera.targetTexture, null); if (usingTexture) camera.targetTexture = null; float height = 2f * Mathf.Tan(Mathf.Deg2Rad * camera.fieldOfView / 2f) * quadOffset; renderQuad.localScale = new Vector3(camera.aspect * height, height, 1f); texture = new RenderTexture(Screen.width, Screen.height, 32); if (supportHDR) texture.format = RenderTextureFormat.DefaultHDR; material.SetTexture(CameraTexture, texture); if (usingTexture) camera.targetTexture = texture; } }