새소식

유니티

[Unity] 씬을 통한 씬 로딩

  • -
현재 문제점
- 씬을 이동하면서 발생하는 문제로
하나의 씬에서 다른 씬을 불러오면서 메모리 로드 용량이 너무 비대해지니까 
씬을 로딩하면서 게임이 멈춰버리는 현상을 발견

 

해결 방법
- 중간에 빈 LoadingScene를 불러들여 다음씬으로 이동

 

 

 

조건 1. 씬 로딩 시 로딩 시 로딩씬을 불러들려야함.

조건 2. 로딩씬을 불러들일 때 로딩의 상태를 가지고 있어야 됨[게임 동작을 위함]

조건 3. 로딩된 씬에서 다음 씬에 전달할 내용을 가지고 있어야 함[튜토리얼 시작 또는 스킵]

 

 


첫번째. 로딩에 사용할 전역 변수 생성
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

/// <summary>
/// 씬을 로딩할 때 사용하는 중간 대리자
/// </summary>
public class SceneLoader :MonoBehaviour
{
    //씬이 로드가 되었는가?
    public static bool Loaded = true;
    public static string name_Title = "TitleScene";
    public static string name_Main = "Main";

    public void LoadScene(string name, Action action = null)
    {
        DontDestroyOnLoad(gameObject);

        //게임 진행을 막기 위함
        Time.timeScale = 0.0f;

        if (!Loaded)
        {
            Debug.Log("이미 씬 로딩 중");
            return;
        }

        Loaded = false;

        //1. 로딩 씬 로드
        StartCoroutine(LoadCroutine(name, action));
    }

    IEnumerator LoadCroutine(string name, Action action)
    {
        Debug.Log("로딩 씬 로드 시작");
        AsyncOperation _operation = SceneManager.LoadSceneAsync("LoadScene");
        _operation.allowSceneActivation = false;

        //로드가 끝날때까지 기다린다.
        while (_operation.progress <0.9f)
        {
            yield return null;
        }

        _operation.allowSceneActivation = true;

        while (!_operation.isDone)
        {
            yield return null;
        }

        Debug.Log("로딩 씬 로딩이 완료 되었습니다."); 

        LoadSceneLoader loader = FindObjectOfType<LoadSceneLoader>();

        //씬 종료 후 실행할 옵션
        if (action != (null))
        {
            loader.SetLoadEndAction(action);
        }

        //씬 로딩 진행
        loader.Load(name);

        Debug.Log("로딩 글로벌 로더 오브젝트 삭제");
        Destroy(gameObject);
    }

    public static void Load(string name, Action action = null)
    {
        if (Loaded)
        {
            GameObject go = new GameObject("SceneLoaderGlobal");
            go.AddComponent<SceneLoader>().LoadScene(name, action);
        }
    }

}

 

-씬이 로드를 위한 로딩씬 로드를 하여 메모리 사용량을 감소시킨다.

- 해당 스크립트에서 로딩씬에서 사용하는 로드스크립트를 찾아 다음 씬을 로드합니다.

- action을 통해 모든 씬이 로딩이 되고나서 행해야될 동작을 수행합니다.

 

 

 

두번째. 로딩씬에 있는 씬 로더
using System;
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

using Random = UnityEngine.Random;

public class LoadSceneLoader : MonoBehaviour
{
    [SerializeField] CanvasGroup c_group;

    [SerializeField] Image image_progress;

    [SerializeField] TMP_Text loadingTip;

    string CurrentSceneName ="TitleScene";

    //로딩이 끝나고 나서의 실행 함수를 지정합니다.
    public Action action_endLoad;

    private void Start()
    {
        DontDestroyOnLoad(gameObject);
    }

    //씬을 로딩합니다.
    public void Load(string _name)
    {
        //종료 후 씬 정상 동작을 위함
        Time.timeScale = 0.0f;

        CurrentSceneName = _name;

        StartCoroutine(LoadCroutine());
    }


    IEnumerator LoadCroutine()
    {
        //텍스트 표시
        loadingTip.text = LocalLanguageSetting.Instance.GetLocalText("Tip", $"Loading{Random.Range(1, 3)}");

        //step 1 fadeinScreen
        yield return FadeInCroutine();

        Debug.Log("씬을 로드 합니다 : " + CurrentSceneName);

        AsyncOperation _operation = SceneManager.LoadSceneAsync(CurrentSceneName);
        _operation.allowSceneActivation = false;

        //step 2 view progressBar
        float _progress = 0.0f;
        while (_progress < 0.89f)
        {
            if(_progress > _operation.progress) { continue; }

            _progress += Time.unscaledDeltaTime * 0.333f;

            image_progress.fillAmount = _progress;

            yield return null;
        }


        _operation.allowSceneActivation = true;

        Debug.Log("씬 로드에 성공했습니다. : " + CurrentSceneName);

        //작업이 끝날때까지 대기 시킨다.
        while (!_operation.isDone)
        {
            Debug.Log("맵을 불러오고 있습니다.");

            image_progress.fillAmount = _operation.progress;

            yield return null;
        }

        //화면을 보여준다.
        yield return FadeOutCroutine();

        //씬 로드가 되면서 지정된 동작을 수행합니다.
        if (action_endLoad != null) { action_endLoad(); action_endLoad = null; }

        //로드의 종료를 알림
        SceneLoader.Loaded = true;

        //다시 정상 동작
        Time.timeScale = 1.0f;

        Debug.Log("씬 로더 오브젝트 삭제 : " + name);
        Destroy(gameObject);
    }

    ~LoadSceneLoader()
    {
        Debug.LogWarning($"{name} 오브젝트 삭제");
    }

    //화면을 가린다.
    IEnumerator FadeInCroutine()
    {
        float t = 0.0f;

        while (t < 1.0f)
        {
            c_group.alpha = t;

            t += Time.unscaledDeltaTime * 0.5f;

            yield return null;
        }
        yield return null;

        c_group.alpha = 1;

        Debug.Log("화면 Fadein 되었습니다.");
    }

    //화면을 보여준다.
    IEnumerator FadeOutCroutine()
    {
        float t = 1.0f;

        while (t > 0.0f)
        {
            c_group.alpha = t;

            t -= Time.unscaledDeltaTime * 0.5f;

            yield return null;
        }

        c_group.alpha = 0;


        Debug.Log("화면 Fadeout 되었습니다.");
    }


    //씬 종료 후 실행할 동작 지정
    public void SetLoadEndAction(Action _action)
    {
        action_endLoad = _action;
    }
}

 

- 다음으로 호출할 씬 이름을 통해 다음 씬을 호출

-  호출된 씬은 Action에 정의된 내용을 통해 다음 동작을 수행하고 씬이 변경됨

 

 

 

 

동작

 

 

 

 


정리

 

씬 용량이 크다보니 씬을 통한 로딩이 필요함을 느꼈고, 이를 적용하였다.

 

씬을 로딩할 때,

AsyncOperation _operation = SceneManager.LoadSceneAsync(CurrentSceneName);

의 기능을 통해 씬 로드 상태를 판단할 수 있어 좋았다.

 

씬이 로드되면 오브젝트가 모두 삭제되기 때문에 오브젝트를 임시로 DontDestroyOnLoad()로 지정했다가 

모든 씬이 로드되고 난 후에 삭제한다.

 

이럴 줄 알았으면...한 스크립트에서 모두 해결할 것을 그랬다..

Contents

아핫

땡큐하다