유니티 RPG - 56. 게임 씬 구조 변경
Intro
Scene 이동과 관련하여 문제점을 발견해서 게임의 씬 구조를 바꿨습니다.
- 문제점
기존에는
각 씬의 이동은 Loading 클래스가 담당하고있으며, SceneManager.LoadSceneAsync() 함수를 통해 다음 씬을 로드하였습니다.
유니티에서는 씬을 이동하면 이전씬의 객체들은 모두 파괴되며, 다시 씬을 불러오면 그 씬의 모든것이 새로 만들어지게됩니다.
따라서, 플레이어가 던전을 클리어한뒤, Portal을 통해 MainScene으로 돌아오게되면 MainScene에 있던 객체들이 모두 다시 생성되게되어 DontDestroyOnLoad()로 작성되어있던 객체들은 중복되어 생성됩니다.
이 문제점을 해결하기위하여
씬을 로드할때 Additive 방법을 사용하였습니다.
변경점
싱글톤으로 작성되어있는 객체들과, DontDestroyOnLoad로 작성되어있는 객체들(게임 내내 유지되어야 하는 객체들)은 한 씬(PersistentScene)에 몰아놓고, 이 씬을 언로드하지 않는 방식으로 구조를 바꾸었습니다.
그리고, 씬을 불러올 때 LoadSceneAsync 메서드의 LoadSceneMode.Addtive 속성을 사용하여 씬을 불러오게합니다.
LoadSceneMode.Addtive 속성은 여러 씬을 동시에 로드하여 겹쳐 사용할 수 있게 하는 기능입니다.
즉,
PersistentScene 씬은 Unload 되지 않게하고, MainScene과 DungeonScene 등의 매번 새롭게 로드되어야하는 씬들은 PersistentScene 씬이 활성화되어있는 상태로 로드되는것입니다.
따라서 PersistentScene에 존재하는 여러 싱글톤, 플레이어, UI등의 객체들을
다른씬을 불러와서도 사용할 수 있도록 하는것입니다.
이렇게하면 PersistentScene은 계속해서 언로드되지않고 유지되어 게임내내 필요한 객체들은 다른씬에서도 사용할 수 있으며, 씬을 이동하더라도 객체들의 중복생성을 방지할 수 있습니다.
PersistentScene
PersistentScene 에는 게임내내 유지되어야하는 매니저객체(싱글톤), UI, 플레이어, 카메라등이 위치합니다.
게임이 실행되면 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");
}
}
-
로딩요청이 들어오면, 이동하기전 있던 씬의 이름을 기억해두고 다음씬을 불러온 뒤 이전씬을 언로드합니다.
-
로딩씬이 먼저 로드되고, 로딩이 완료되면 로드될 씬을 포커싱하고 이전 씬과 로딩씬을 언로드합니다.
댓글남기기