유니티 씬(Scene) 멋있게 넘어가기

씬과 씬 사이를 넘어갈 때 각종 꿀팁이나 로고 등을 삽입하고 싶으시죠?
 
오늘은 씬과 씬 사이를 넘어갈 때 멋있게 넘어가는 방법에 대해 알아보고자 합니다!

씬 멋있게 넘어가기

 


구조

구조

 
구조는 위와 같습니다. 먼저 Loading Screen오브젝트를 하나 생성해준 다음 그 아래로 Camera, Model을 넣어줍니다.
모델이 없다고요?! 아래에서 다운받아주세요.

UI_TransitionScreen_Model.zip
0.01MB

 

Model

 
Model을 다운받으시면 왼쪽처럼 정육면체의 큐브가 생겨나는 것을 보실 수 있습니다. Model의 메테리얼을 살펴보시면 우측 사진처럼 로고를 넣을 수 있는 공간이 있습니다.
 

Shader가 없을 경우🔽

더보기
 Shader "Custom/UnlitTransparentAlwaysOnTop"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Color ("Tint Color", Color) = (1, 1, 1, 1)
        
        _StencilComp ("Stencil Comparison", Float) = 8
        _Stencil ("Stencil ID", Float) = 0
        _StencilOp ("Stencil Operation", Float) = 0
        _StencilWriteMask ("Stencil Write Mask", Float) = 255
        _StencilReadMask ("Stencil Read Mask", Float) = 255
    }
    
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        
        Stencil
        {
            Ref [_Stencil]
            Comp [_StencilComp]
            Pass [_StencilOp]
            ReadMask [_StencilReadMask]
            WriteMask [_StencilWriteMask]
        }

        ZWrite Off
        ZTest Off
        
        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };
            
            sampler2D _MainTex;
            fixed4 _Color;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                col.a = _Color.a; // Preserve the original alpha value
                return col;
            }
            ENDCG
        }
    }
}


해당 공간에 png파일을 삽입하시면 됩니다. 이때 테스트용으로 함께 들어있는 저의 "디스가이즈" 아이콘 png을 사용하시면 안됩니다! 
 

Camera


카메라 부분에는 Background Type을 Solid Color로 설정해주신 다음 원하는 색을 지정해줍니다.

Culling Mask에는 새로운 Layer를 하나 만들어준 뒤 해당 Layer만 보이게합니다.


코드

using System.Collections;
using TMPro;
using UnityEngine;


public class LoadingScreen : MonoBehaviour
{

	// 페이드 아웃 전의 지연 시간
	[SerializeField] float m_DelayBeforeFadeOut = 0.5f;

	// 페이드 아웃의 지속 시간
	[SerializeField] float m_FadeOutDuration = 0.1f;

	// 로딩 화면이 실행 중인지 여부를 나타내는 플래그
	private bool m_LoadingScreenRunning;

	// 페이드 아웃을 처리하는 코루틴에 대한 참조
	private Coroutine m_FadeOutCoroutine;

	// 페이드가 발생하는 속도
	[SerializeField] private float m_FadeSpeed = .01f;

	// 타임아웃을 처리하는 코루틴에 대한 참조 
	private Coroutine m_TimeoutCoroutine;

	// 페이드 배경을 표시하는 MeshRenderer에 대한 참조
	[SerializeField] private MeshRenderer m_FadeBackground;

	// 오버레이를 렌더링하는 카메라에 대한 참조
	[SerializeField] private Camera m_OverlayCamera;

	// 장면 이름을 표시하는 TextMeshPro 컴포넌트에 대한 참조
	[SerializeField] private TextMeshPro m_SceneNameText;

	// 오버레이 카메라의 변환에 대한 참조
	private Transform m_OverlayCamTransform;

	// 메인 플레이어 카메라에 대한 참조
	private Transform m_PlayerCamera;

	// 플레이어 카메라의 루트 변환에 대한 참조
	private Transform m_PlayerCameraRig;

	// 로고를 페이드에 포함할지 여부를 나타내는 플래그
	private bool m_IncludeLogo = true;

	// 현재 페이드 값 (완전히 투명한 0에서 완전히 불투명한 1까지)
	private float m_CurrentFade;

	// 현재 화면 페이드의 색상
	private Color m_CurrentScreenFade = new(1, 1, 1, 0);

	// 페이드 배경의 재질에 대한 참조
	private Material m_FadeBackroundMat;
	
	// 화면이 현재 페이드인 상태인지 여부를 나타내는 플래그
	private bool m_IsFadingIn;

	// 플레이어 카메라 리그가 null이 아닌지 여부를 나타내는 플래그
	private bool m_IsPlayerCameraRigNotNull;


	public bool IsFadingIn
	{
		set
		{
			if (m_IsFadingIn != value)
			{
				m_IsFadingIn = value;
			}
		}
	}

	private void Awake()
	{
		if (m_OverlayCamera == null)
		{
			Debug.LogError(
				$"{gameObject.name}: Cannot show transitions! Please ensure that the Transitioner component is set up correctly to use transitions.");
			enabled = false;
		}

		m_OverlayCamTransform = m_OverlayCamera.transform;

		m_PlayerCamera = Camera.main.transform;
		m_FadeBackroundMat = m_FadeBackground.material;
		m_FadeBackroundMat.color = m_CurrentScreenFade;
		DontDestroyOnLoad(this);
	}

	private void Start()
	{
		if (m_PlayerCamera.root == m_PlayerCamera)
		{
			Debug.LogWarning(
				"The player camera does not have a parent! The TransitionalFade will keep its default orientation this session.");
			return;
		}

		m_PlayerCameraRig = m_PlayerCamera.root;
		m_IsPlayerCameraRigNotNull = m_PlayerCameraRig != null;
	}

	public void StopLoadingScreen()
	{
		if (m_LoadingScreenRunning)
		{
			if (m_FadeOutCoroutine != null)
			{
				StopCoroutine(m_FadeOutCoroutine);
			}

			m_FadeOutCoroutine = StartCoroutine(FadeOutCoroutine());
		}
	}

	public void StartLoadingScreen(string sceneName)
	{
		IsFadingIn = true;
		m_LoadingScreenRunning = true;
		UpdateLoadingScreen(sceneName);
	}

	public void UpdateLoadingScreen(string sceneName)
	{
		if (m_LoadingScreenRunning)
		{
			if (m_FadeOutCoroutine != null)
			{
				StopCoroutine(m_FadeOutCoroutine);
			}
		}
	}


	private IEnumerator FadeOutCoroutine()
	{
		yield return new WaitForSeconds(m_DelayBeforeFadeOut);
		m_LoadingScreenRunning = false;
		IsFadingIn = false;
		float currentTime = 0;
		while (currentTime < m_FadeOutDuration)
		{
			yield return null;
			currentTime += Time.deltaTime;
		}

	}

	private void LateUpdate()
	{
		if (m_IsPlayerCameraRigNotNull)
		{
			transform.rotation = m_PlayerCameraRig.rotation;
			transform.position = m_PlayerCameraRig.position;
		}

		m_OverlayCamTransform.position = m_PlayerCamera.position;
		m_OverlayCamTransform.rotation = m_PlayerCamera.rotation;
		AnimateFade();
	}

	private void AnimateFade()
	{
		if (m_IsFadingIn && m_CurrentFade < 1)
		{
			m_CurrentFade = Mathf.MoveTowards(m_CurrentFade, 1, m_FadeSpeed * Time.deltaTime);
			//m_CurrentFade = 1f;
			m_CurrentScreenFade.a = m_CurrentFade;
			var screenColor = m_IncludeLogo ? 1 : 0;
			m_CurrentScreenFade.r = screenColor;
			m_CurrentScreenFade.g = screenColor;
			m_CurrentScreenFade.b = screenColor;
			//m_SceneNameText.alpha = m_CurrentFade;
		}
		else if (!m_IsFadingIn && m_CurrentFade > 0)
		{
			m_CurrentFade = Mathf.MoveTowards(m_CurrentFade, 0, m_FadeSpeed * Time.deltaTime);				m_CurrentScreenFade.a = m_CurrentFade;
			if (m_CurrentScreenFade.a != 0)
			{
				var screenColor = m_IncludeLogo ? 1 : 0;
					m_CurrentScreenFade.r = screenColor;
					m_CurrentScreenFade.g = screenColor;
					m_CurrentScreenFade.b = screenColor;
				}

			/*if (m_SceneNameText.alpha != 0)
			{
				m_SceneNameText.alpha = m_CurrentFade;
			}*/
		}

		if (m_CurrentFade == 0 && m_OverlayCamera.enabled)
		{
			m_OverlayCamera.enabled = false;
			m_FadeBackground.enabled = false;
		}
		else if (m_CurrentFade > 0 && !m_OverlayCamera.enabled)
		{
			m_OverlayCamera.enabled = true;
			m_FadeBackground.enabled = true;
		}

		m_FadeBackroundMat.color = m_CurrentScreenFade;
	}
}

 
코드를 생성한 다음 LoadingScreen오브젝트에 붙여줍니다.

 

DelayBeforeFadeOut 변수는 화면이 페이드 아웃(점점 사라지는 효과) 시작하기 전에 대기하는 시간을 의미합니다. 설정된 시간 동안 로딩 화면이 그대로 유지되고, 그 후에 페이드 아웃 애니메이션이 시작됩니다.

 

DelayBeforeFadeOut변수를 적절하게 사용하면 화면 전환을 부드럽게 할 수 있습니다. 

예를 들어, DelayBeforeFadeOut 0.5f로 설정되어 있다면, 로딩 화면이 사라지기 시작하기 전에 0.5초 동안 로딩화면이 대기합니다. 이 시간 동안 로딩 화면은 완전히 불투명한 상태로 유지됩니다.
 
코드 사용 예시는 아래와 같습니다.

using UnityEngine;
using UnityEngine.SceneManagement;


public class SceneLoaderWrapper : MonoBehaviour
{
        [SerializeField] private ClientLoadingScreen m_ClientLoadingScreen;

        public static SceneLoaderWrapper Instance { get; protected set; }

        public void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            DontDestroyOnLoad(this);
        }
        
        public void Start()
        {
            SceneManager.sceneLoaded += OnSceneLoaded;
        }

        public void OnDestroy()
        {
            SceneManager.sceneLoaded -= OnSceneLoaded;
        }
        

        public virtual void LoadScene(string sceneName, LoadSceneMode loadSceneMode = LoadSceneMode.Single)
        {
            // Load using SceneManager
            var loadOperation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
            if (loadSceneMode == LoadSceneMode.Single)
            {
                m_ClientLoadingScreen.StartLoadingScreen(sceneName);
            }
            
        }
        
        private void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
        {
             m_ClientLoadingScreen.StopLoadingScreen();
        }
  
}

 

SceneLoaderWrapper를 사용