본문 바로가기

Unity3D/C#

유니티 씬 전환 · 로딩 바 (SceneManager.LoadScene, LoadSceneAsync)

반응형

SCENE LOAD LoadScene

 

유니티에서 다른 씬으로 넘어가려면 UnityEngine.SceneManagement 의 SceneManager.LoadScene 에 씬 이름을 넣습니다. 넘어갈 씬은 미리 Scene List(예전 Build Settings 창의 Scenes In Build)에 들어 있어야 하고, 로딩 바를 보여 주려면 LoadSceneAsync 로 바꿔 progress 를 읽습니다.

 

using UnityEngine.SceneManagement;

SceneManager.LoadScene("Game");                           // 이름으로

 

타이틀 화면에서 시작 버튼을 누르면 게임 화면으로, 게임이 끝나면 결과 화면으로 넘어가는 것처럼 화면을 통째로 바꿀 때는 씬을 나눠 두고 SceneManager 로 불러옵니다. 그런데 처음 해 보면 「씬을 찾을 수 없다」 는 오류가 나거나, 씬이 큰 경우 버튼을 누른 뒤 화면이 잠깐 멈춘 것처럼 보이기도 합니다. 이번 글에서는 이름으로 바로 넘어가는 LoadScene 과, 뒤에서 읽으면서 로딩 바를 채우는 LoadSceneAsync 를 버튼 두 개에 나눠 단 SceneLoader 를 만들겠습니다. 씬 이름을 잘못 적었을 때 오류 대신 경고만 남기고 멈추는 확인과, 버튼을 두 번 눌러도 한 번만 불러오게 막는 처리도 함께 넣었습니다.

 

C# (UNITY 3D)

 

 

button_load 를 누르면 다음 프레임에 바로 Game 씬으로 넘어갑니다. button_load_async 를 누르면 로딩 화면이 켜지고, 씬을 읽는 동안 로딩 바와 % 글자가 0% 에서 100% 까지 차오른 뒤 넘어가는 예시입니다. 씬 이름이 Scene List 에 없으면 어느 버튼이든 넘어가지 않고 Console 에 경고만 남깁니다.

 

1. 준비 — 씬을 Scene List 에 넣기

 

File > Build Profiles 를 열고 Scene List 에 시작 씬(예: Title)과 넘어갈 씬(예: Game)을 끌어다 넣습니다. 예전 버전에서는 File > Build Settings 의 Scenes In Build 입니다. 목록 맨 위가 0번이고 앱을 켜면 처음 열리는 씬입니다. 체크가 꺼진 씬은 빌드에 들어가지 않습니다.

 

using System;
using System.Collections;
using System.IO;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

 

SceneManager 와 SceneUtility 는 UnityEngine.SceneManagement 에, 로딩 바 Image 와 Button 은 UnityEngine.UI 에, % 글자 TMP_Text 는 TMPro 에 있습니다. System.IO 는 씬 경로에서 이름만 떼어 낼 때 씁니다.

 

Title 씬의 빈 오브젝트에 SceneLoader 를 붙이고 인스펙터에서 scene_name 에 Game 을 적은 뒤, 버튼 두 개 · 로딩 화면 패널 · 로딩 바 Image · % 글자를 끌어다 넣습니다. 로딩 바 Image 는 Image Type 을 Filled 로 두어야 fillAmount 로 채워집니다.

 

2. 씬 전환 · 로딩 바 코드

 

using System;
using System.Collections;
using System.IO;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

//CodeFinder 코드파인더
//From https://codefinder.janndk.com/
public class SceneLoader : MonoBehaviour
{
    public string scene_name = "Game";       // 넘어갈 씬 이름(Scene List 에 들어 있어야 함)
    public Button button_load;               // 바로 넘어가기
    public Button button_load_async;         // 로딩 바를 보며 넘어가기
    public GameObject panel_loading;         // 로딩 화면(처음엔 꺼 둠)
    public Image image_bar;                  // 로딩 바(Image Type : Filled)
    public TMP_Text text_percent;            // 몇 % 인지

    bool is_loading;                         // 두 번 누르기 막기

    void Start()
    {
        if (panel_loading != null)
            panel_loading.SetActive(false);

        if (button_load != null)
        {
            button_load.onClick.RemoveAllListeners();
            button_load.onClick.AddListener(Load_Scene);
        }
        if (button_load_async != null)
        {
            button_load_async.onClick.RemoveAllListeners();
            button_load_async.onClick.AddListener(Load_SceneAsync);
        }
    }

    public void Load_Scene()
    {
        if (is_loading || !Can_Load(scene_name))
            return;

        is_loading = true;
        SceneManager.LoadScene(scene_name);                 // 다음 프레임에 바로 넘어감
    }

    public void Load_SceneAsync()
    {
        if (is_loading || !Can_Load(scene_name))
            return;

        is_loading = true;
        StartCoroutine(Load_Routine(scene_name));
    }

    public static bool Can_Load(string name)
    {
        for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
        {
            string path = SceneUtility.GetScenePathByBuildIndex(i);     // 예 : Assets/Scenes/Game.unity
            if (string.Equals(Path.GetFileNameWithoutExtension(path), name, StringComparison.OrdinalIgnoreCase))   // 씬 이름은 대소문자를 안 가림
                return true;
        }

        Debug.LogWarning("씬 " + name + " 이 Scene List 에 없습니다");
        return false;
    }

    IEnumerator Load_Routine(string name)
    {
        if (panel_loading != null)
            panel_loading.SetActive(true);
        Show_Progress(0f);

        AsyncOperation op = SceneManager.LoadSceneAsync(name);
        op.allowSceneActivation = false;                    // 다 읽어도 넘어가지 말고 기다림

        while (op.progress < 0.9f)                          // 기다리는 동안 progress 는 0.9 에서 멈춤
        {
            Show_Progress(op.progress / 0.9f);
            yield return null;
        }

        Show_Progress(1f);
        yield return new WaitForSeconds(0.2f);              // 100% 를 잠깐 보여 줌
        op.allowSceneActivation = true;                     // 이제 넘어감
    }

    public void Show_Progress(float value)
    {
        value = Mathf.Clamp01(value);
        if (image_bar != null)
            image_bar.fillAmount = value;
        if (text_percent != null)
            text_percent.text = Mathf.RoundToInt(value * 100f) + "%";
    }
}

 

LoadScene 은 부르자마자가 아니라 다음 프레임에 씬을 바꿉니다. LoadSceneAsync 는 씬을 뒤에서 읽으면서 AsyncOperation 을 돌려주고, 그 progress 로 얼마나 읽었는지 알 수 있습니다. allowSceneActivation 을 false 로 두면 다 읽어도 progress 가 0.9 에서 멈춘 채 기다리기 때문에, 로딩 바는 progress 를 0.9 로 나눠 0~1 로 바꿔 채우고, 100% 를 잠깐 보여 준 뒤 true 로 바꿔 넘어갑니다. 넘어가기 전에 버튼을 또 누르면 같은 씬을 두 번 불러오게 되므로 is_loading 으로 막았습니다.

Can_Load 는 Scene List 에 있는 씬의 경로를 하나씩 꺼내 파일 이름만 떼어 비교합니다. Unity 문서에 씬 이름은 대소문자를 가리지 않는다고 되어 있어서 비교도 대소문자를 무시하게 맞췄습니다.

이 스크립트는 Unity 6000.6.3f1 에서 컴파일 오류 없이 들어갔고, 배치 모드에서 Start 를 불러도 예외가 없었습니다. 배치 모드는 플레이 모드가 아니라서 실제로 씬이 넘어가는 장면까지는 보지 못했고, Scene List 확인과 로딩 바 계산 값만 직접 넣어 확인했습니다(3절).

 

변수(variable)
scene_name(string) : 넘어갈 씬 이름
button_load(Button) : 바로 넘어가는 버튼
button_load_async(Button) : 로딩 바를 보며 넘어가는 버튼
panel_loading(GameObject) : 로딩 화면
image_bar(Image) : 로딩 바(Filled)
text_percent(TMP_Text) : % 글자
is_loading(bool) : 이미 불러오는 중인지

 

함수(function)
Start : 로딩 화면을 끄고 두 버튼에 Load_Scene · Load_SceneAsync 를 연결합니다.
Load_Scene : 씬이 목록에 있으면 LoadScene 으로 바로 넘어갑니다.
Load_SceneAsync : 씬이 목록에 있으면 Load_Routine 코루틴을 시작합니다.
Can_Load : Scene List 에 그 이름의 씬이 있는지 대소문자 없이 찾고, 없으면 경고를 남깁니다.
Load_Routine : LoadSceneAsync 로 읽으면서 로딩 바를 채우고, 다 읽으면 넘어갑니다.
Show_Progress : 0~1 값을 로딩 바 fillAmount 와 % 글자로 보여 줍니다.

 

3. 넣어 본 값으로 보기

 

SceneLoader 에 넣어 본 값 — Scene List 에 Title 과 Game 을 넣었을 때 Can_Load 결과와, 로딩 바 progress 에 따른 fillAmount · % 글자

 

Scene List 에 Title 과 Game 을 넣고 확인하니 Game 과 game 은 찾았고, 철자를 틀린 Gmae 와 목록이 비었을 때는 False 가 나와 넘어가지 않았습니다. 로딩 바는 progress 0.45 에서 50%, 0.9 에서 100% 가 되었고, 범위 밖의 값은 Clamp01 로 0% · 100% 에 묶였습니다.

 

4. 번호로 · 다시 시작 · 겹쳐 열기

 

SceneManager.LoadScene(1);                                            // Scene List 의 번호로
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);     // 지금 씬 다시 시작
SceneManager.LoadScene("Pause", LoadSceneMode.Additive);              // 지금 씬 위에 하나 더 얹기

 

이름 대신 Scene List 의 번호를 넣어도 됩니다. 다만 목록 순서를 바꾸면 번호도 바뀌므로 이름이 더 안전합니다. 게임 오버 뒤 「다시 하기」 는 지금 씬의 buildIndex 를 다시 불러오면 처음 상태로 돌아갑니다. LoadSceneMode.Additive 를 주면 지금 씬을 닫지 않고 그 위에 다른 씬을 함께 열어서, 일시정지 화면처럼 따로 만든 씬을 겹쳐 보여 줄 때 씁니다.

 

5. 안 넘어갈 때 — 자주 하는 실수

 

Scene List 에 안 넣었다
씬 파일이 프로젝트에 있어도 Scene List 에 없으면 불러올 수 없습니다. 이 글의 Can_Load 는 이때 경고만 남기고 멈추지만, LoadScene 을 바로 부르면 씬을 찾을 수 없다는 오류가 납니다.

 

로딩 바가 90% 에서 멈춘다
allowSceneActivation 을 false 로 두면 progress 는 0.9 에서 멈추고 isDone 도 true 가 되지 않습니다. 「progress 가 1 이 되면 넘어가기」 처럼 짜면 영원히 기다리게 되니, 0.9 에 닿았는지로 보고 allowSceneActivation 을 true 로 바꿔야 합니다.

 

넘어가면 앞 씬의 오브젝트가 사라진다
LoadScene 은 기본으로 지금 씬을 닫고 새 씬을 엽니다. 배경음악이나 점수처럼 씬을 넘어서도 남아야 하는 것은 DontDestroyOnLoad 로 따로 남기거나 PlayerPrefs 같은 곳에 저장해 두어야 합니다.

 

에디터 스크립트에서 부르면 오류가 난다
SceneManager.LoadScene · LoadSceneAsync 는 플레이 중에만 됩니다. 이 글을 확인한 배치 모드(플레이 모드 아님)에서 부르니 「This can only be used during play mode」 라는 InvalidOperationException 이 났습니다. 에디터에서 씬을 여는 도구를 만들 때는 EditorSceneManager.OpenScene 을 씁니다.

 

함께 보기: 로딩 바를 채우는 fillAmount 는 유니티 Image.fillAmount 로 쿨타임 · 로딩 바 만들기, 버튼에 함수를 코드로 다는 법은 유니티 Button onClick 코드로 연결 · interactable 로 잠금 에 있습니다.

 

씬 불러오기 동작은 Unity 공식 문서 SceneManager.LoadScene · SceneManager.LoadSceneAsync · AsyncOperation.allowSceneActivation 과 Unity 6000.6.3f1 에디터(Windows)에서 값을 넣어 본 결과를 기준으로 적었습니다.

 

 

반응형

Google Play 및 Google Play 로고는 Google LLC의 상표입니다.