2 분 소요

Intro

Scene 이동과 관련하여 문제점을 발견해서 게임의 씬 구조를 바꿨습니다.

  • 문제점

기존에는

image

각 씬의 이동은 Loading 클래스가 담당하고있으며, SceneManager.LoadSceneAsync() 함수를 통해 다음 씬을 로드하였습니다.

유니티에서는 씬을 이동하면 이전씬의 객체들은 모두 파괴되며, 다시 씬을 불러오면 그 씬의 모든것이 새로 만들어지게됩니다.

따라서, 플레이어가 던전을 클리어한뒤, Portal을 통해 MainScene으로 돌아오게되면 MainScene에 있던 객체들이 모두 다시 생성되게되어 DontDestroyOnLoad()로 작성되어있던 객체들은 중복되어 생성됩니다.

이 문제점을 해결하기위하여

씬을 로드할때 Additive 방법을 사용하였습니다.

변경점

싱글톤으로 작성되어있는 객체들과, DontDestroyOnLoad로 작성되어있는 객체들(게임 내내 유지되어야 하는 객체들)은 한 씬(PersistentScene)에 몰아놓고, 이 씬을 언로드하지 않는 방식으로 구조를 바꾸었습니다.

그리고, 씬을 불러올 때 LoadSceneAsync 메서드의 LoadSceneMode.Addtive 속성을 사용하여 씬을 불러오게합니다.

LoadSceneMode.Addtive 속성은 여러 씬을 동시에 로드하여 겹쳐 사용할 수 있게 하는 기능입니다.

즉,

PersistentScene 씬은 Unload 되지 않게하고, MainScene과 DungeonScene 등의 매번 새롭게 로드되어야하는 씬들은 PersistentScene 씬이 활성화되어있는 상태로 로드되는것입니다.

따라서 PersistentScene에 존재하는 여러 싱글톤, 플레이어, UI등의 객체들을

다른씬을 불러와서도 사용할 수 있도록 하는것입니다.

image


이렇게하면 PersistentScene은 계속해서 언로드되지않고 유지되어 게임내내 필요한 객체들은 다른씬에서도 사용할 수 있으며, 씬을 이동하더라도 객체들의 중복생성을 방지할 수 있습니다.

PersistentScene

PersistentScene 에는 게임내내 유지되어야하는 매니저객체(싱글톤), UI, 플레이어, 카메라등이 위치합니다.

image


게임이 실행되면 Initializer를 통해 MainScene을 로드하고 포커싱합니다.

/*
            - 게임시작시 MainScene 로드 및 포커싱
 
 */
public class PersistentInitializer : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(InitGame());
    }

    private IEnumerator InitGame()
    {
        // Additive로 MainScene 불러오기
        yield return SceneManager.LoadSceneAsync("MainScene", LoadSceneMode.Additive);
        
        // MainScene 포커싱
        SceneManager.SetActiveScene(SceneManager.GetSceneByName("MainScene"));
    }
}

Loading.cs

public class Loading : MonoBehaviour
{
    [SerializeField] private Image progressBar;
    [SerializeField] private Text tipText;

    
    private static string nextScene;
    private static string prevSceneName;

    string[] gameTips =
    {
        "플레이어가 사망하면 마을로 돌아갑니다.",
        "포션으로 체력을 보충할 수 있습니다.",
        "더 좋은 장비는 던전을 클리어하는데 큰 도움이 됩니다."
    };

    private void Start()
    {
        StartCoroutine(LoadSceneProgress());
        ShowGameTips();
    }

    // 로딩씬 불러오기
    public static void LoadNextScene(string sceneName)
    {
        prevSceneName = SceneManager.GetActiveScene().name;
        nextScene = sceneName;
        SceneManager.LoadSceneAsync("Loading", LoadSceneMode.Additive);
    }

    private IEnumerator LoadSceneProgress()
    {
        // 로딩씬을 ActiveScene으로 설정
        SceneManager.SetActiveScene(SceneManager.GetSceneByName("Loading"));

        AsyncOperation op = SceneManager.LoadSceneAsync(nextScene, LoadSceneMode.Additive);
        op.allowSceneActivation = false;
        
        float timer = 0f;

        while(!op.isDone)
        {
            yield return null;

            if(op.progress < 0.9f)
            {
                progressBar.rectTransform.sizeDelta = new Vector2(op.progress * 1920f, 80f);
            }
            // 90% 로딩 이후로 Fake 로딩
            else
            {
                timer += Time.unscaledDeltaTime;
                progressBar.rectTransform.sizeDelta = new Vector2(1728f + timer * 20f, 80f);
                if(progressBar.rectTransform.sizeDelta.x >= 1920f)
                {
                    op.allowSceneActivation = true;
                }
            }
        }

        // 로딩 완료 후 전환
        yield return null;

        // 로드될 다음 씬을 ActiveScene으로 설정
        SceneManager.SetActiveScene(SceneManager.GetSceneByName(nextScene));

        // 이전 씬 언로드(PersistentScene 제외)
        if(prevSceneName != "PersistentScene" && prevSceneName != "Loading")
        {
            SceneManager.UnloadSceneAsync(prevSceneName);
        }

        // 로딩씬은 언로드
        SceneManager.UnloadSceneAsync("Loading");
    }
}
  • 로딩요청이 들어오면, 이동하기전 있던 씬의 이름을 기억해두고 다음씬을 불러온 뒤 이전씬을 언로드합니다.

  • 로딩씬이 먼저 로드되고, 로딩이 완료되면 로드될 씬을 포커싱하고 이전 씬과 로딩씬을 언로드합니다.

댓글남기기