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

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

etc-image-0
씬 멋있게 넘어가기

 


구조

etc-image-1
구조

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

UI_TransitionScreen_Model.zip
0.01MB

 

etc-image-2etc-image-3
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을 사용하시면 안됩니다! 
 

etc-image-4etc-image-5
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();
        }
  
}📋

 

etc-image-6
SceneLoaderWrapper를 사용