게임은 월드 내에서 캐릭터를 조정해서 모험하고 게임에서 얻는 재화를 통해 캐릭터를 업그레이드 하는 재미를 줍니다. 그래서 게임 내 캐릭터를 포함한 각 오브젝트들은 컴포넌트 간에 데이터를 주고 받게 되는데 같은 scene 내에서 주고 받는 방식이 아닌 씬 전환에서도 데이터를 주고 받을 수 있습니다. 아주 간단한 게임은 씬 전환 없이도 같은 씬에서 데이터를 처리하지만 조금 복잡하거나 또는 아주 복잡하지 않더라도 게임 내에 성장 시스템이 약간이라도 있다면 씬 전환은 필요합니다. 예를 들어서 던전이나 필드에서 몬스터를 사냥하면서 게임 내 재화인 골드를 얻으면 마을로 돌아가서 장비를 수리하고 좋은 장비를 구입해서 더 강한 캐릭터를 느낄 수 있는 것이 액션이나 RPG 장르 게임의 기본적인 시스템입니다. 사냥 같은 게임을 즐기는 플레이는 필드나 던전에서 하고, 장비 업그레이드나 새 장비 구입 등 게임 강화 시스템은 마을에서 합니다. 따라서 마을과 필드 간의 씬은 다를 수 밖에 없고, 씬이 전환될 때에는 데이터간 상호작용이 불가피합니다. 이럴 때 필요한 것이 싱글톤입니다.
싱글톤은 특정 클래스의 인스턴스를 하나만 생성하고, 이를 전역에서 접근할 수 있도록 합니다. 그래서 대개 게임에서는 게임 매니저나 오디오 매니저에 많이 사용합니다. 게임 매니저는 필드에서 얻은 골드 데이터를 그대로 가지고 있다가 마을 씬으로 넘어와서 플레이어가 사용할 때 해당 데이터를 제공하기 때문입니다. 오디오의 경우도 필드, 던전, 마을을 오가면서 바뀌는 BGM 등을 적합하게 바꾸어 주기 때문에 싱글톤이 사용됩니다. 다만 예시로 만드는 이 로봇 청소기 게임에서는 게임 매니저인 GameController에 플레이씬에서 필요하지만 레벨씬에서는 불필요한 UI를 붙여 놓았기 때문에 해당 게임 매니저는 사용할 수 없습니다. 즉 싱글톤 패턴을 사용하려면 특정 지역에서만 사용되는 UI나 이미지 변수들을 선언하지 않는 것이 좋습니다. 청소기 게임에서는 싱글톤을 레벨 데이터에 적용하는데 사용해보겠습니다. 싱글톤을 먼저 만들기 위해서 c# 스크립트를 하나 생성하고 이름을 Singleton이라고 해줍니다.
public class SingletonBehaviour<T> : MonoBehaviour where T : MonoBehaviour { private static T _instance; public static T Instance { get { if (_instance == null) { _instance = FindObjectOfType<T>(); DontDestroyOnLoad(_instance.gameObject); } return _instance; } } // Start is called before the first frame update void Awake() { if (Instance != null) { if(_instance != this) { Destroy(gameObject); } return; } } } |
싱글톤 패턴은 여러가지 방법이 있지만 위 싱글톤 클래스는 Monobehaviour에 사용할 때 '싱글톤<싱글톤으로 사용될 클래스>' 형식을 취하면 싱글톤으로 사용된다는 것입니다. 전역변수로 인스턴스를 만들고 인스턴스가 null 값일 경우 오브젝트타입 즉 싱글톤 타입을 찾아서 지정해 주고 씬이 전환되어도 싱글톤이 있는 오브젝트는 파괴되지 않도록 합니다. 이 인스턴스는 게임이 시작될 때 인스턴스가 null 값이 아니고 인스턴스가 존재하지만 이 스크립트의 싱글톤 인스턴스와 같지 않은 인스턴스라면 즉시 이 스크립트의 오브젝트를 파괴한다는 것입니다. 예를 들면 처음 게임을 켜서 마을씬(첫번째 마을씬의 싱글톤)에 들어섰을 때 싱글톤 오브젝트가 있습니다. 필드 사냥을 가기 위해 마을을 떠나면 씬 전환이 이루어지고 필드에는 아까 마을에서 있었던 싱글톤 오브젝트가 파괴되지 않고 그대로 따라 옵니다. 사냥을 끝내고 다시 마을로 돌아오면 역시 씬전환에도 파괴되지 않기 때문에 싱글톤은 그대로 오지만 마을(두번째 마을씬의 싱글톤)에 올 때마다 싱글톤과 충돌하게 됩니다. 둘 중 한 개는 파괴되어야만 하기 때문에 첫 마을씬에서 접한 싱글톤이 파괴되지 않고 두번째 이후에 나오는 싱글톤은 파괴되면서 오직 한 개의 싱글톤만 존재하도록 짜여지는 것입니다. 그래야만 데이터를 한 개의 오브젝트에서 주고 받고 할 수 있기 때문입니다.
싱글톤이 있다면 이를 적용시킬 스크립트를 만들어야 합니다. 이 게임에서는 게임 매니저보다는 레벨 이동 시 필요한 데이터를 담을 레벨 데이터를 리스트로 만들 스크립트를 만들겠습니다. 레벨 데이터는 List<>를 만들어서 List 내에 있는 element에 접근해서 데이터를 가져가서 사용하고 바꾸면서 레벨에 대한 정보를 관리하는 스크립트입니다. 먼저 빈 오브젝트를 하나 생성하고 LevelDataList로 이름을 정해줍니다. 그리고 스크립트를 LevelDataList로 생성해 줍니다. 이 오브젝트는 레벨 선택 씬과 각 스테이지 플레이 씬을 오고 가면서 레벨에 대한 정보를 전달하고 전달 받으면서 각 스테이지의 클리어 상황과 현재 플레이 스테이지와 다음 스테이지에 대한 데이터를 제공하는 역할을 해야 하기 때문에 싱글톤으로 해줍니다. 싱글톤을 작성했기 때문에 지정하는 것은 간단합니다.
using System.Collections; using System.Collections.Generic; using UnityEngine; public class LevelDataList : SingletonBehaviour<LevelDataList> { public string levelName; public int isOpen; public int currentLevel; [SerializeField] public List<LevelData> data; public void GetLevel (int lv) { for(int i=0; i<data.Count; i++) { currentLevel = lv; if (data[i].level == lv) { levelName = data[i].name; isOpen = data[i].isOpen; break; } } SceneManager.LoadScene(currentLevel); } } |
LevelDataList에 SingletonBehaviour 가져오고 클래스를 LevelDataList로 지정해주면 싱글톤으로 사용됩니다. 레벨씬에 사용할 데이터에는 스테이지 이름, 클리어 여부, 현재 선택해서 플레이하는 스테이지가 필요합니다. 이런 정보들을 한 개의 스크립트에서 데이터를 사용 하려면 List<>로 만들어서 관리를 하면 좋습니다. List는 int, string, float, bool 등의 타입을 element로 나열을 해서 데이터를 사용하도록 만듭니다. 따라서 List에 사용할 클래스를 만들어야 합니다.
스크립트를 LevelData로 이름 짓고 생성합니다. 참고로 List를 사용하기 위해서는 using System.Collections.Generic; 을 해줘야 합니다.
using System.Collections; using System.Collections.Generic; using UnityEngine; [System.Serializable] public class LevelData { public string name; public int level; public int isOpen; } |
LevelData 클래스는 단순하게 name, level, isOpen으로 구성되어 있습니다. 이 클래스를 List<LevelData>에 담아서 사용하며 [SerializeField]를 써줌으로써 LevelDataList가 있는 오브젝트를 클릭했을 때 inspector에서 List 값을 설정하거나 변경되는 값을 확인할 수 있습니다.
이 데이터는 LevelDataList에서 public List 의 변수인 data로 만들어서 사용합니다. 각각의 5개 스테이지에서 사용할 예정인데 그림에서는 점수, 튜토리얼 등도 필요한 데이터라고 생각해서 만들었으나 실제로 사용할 부분은 레벨을 구분하는 Level, 레벨을 알려주는 UI 용도의 Name, 레벨 클리어 여부를 체크해주는 isOpen 정도입니다. LevelDataList에서 GetLevel(int lv) 함수는 public으로 선언을 해놓아서 버튼을 누를 때 이 함수를 호출해서 사용할 것입니다. 이 함수가 호출되면 lv인자를 통해 레벨 값을 받고 for 루푸문을 돌립니다. for 문법에서 int i=0;는 정수타입 int의 변수 i를 선언하고 0을 지정해서 i는 0부터 시작된다는 뜻입니다. i < data.Count;는 data의 Element 개수를 뜻하는데 위 그림의 inspector에서 Data 옆 숫자 5를 뜻하는 것입니다. 즉 Data의 개수를 뜻하며 i++;를 통해서 data.Count 범위인 5보다 작은 정수인 4까지 루프(반복)를 돌린다는 뜻입니다. 풀어서 말하면 i는 0부터 4까지의 정수를 계속 돌리겠다는 명령어가 됩니다. 참고로 정수타입 변수명은 꼭 i가 아니어도 됩니다. LevelDataList가 갖고 있는 currentLevel은 레벨 씬에서 선택된 레벨 정보를 그대로 갖고 플레이 씬으로 가져갑니다. for루프문에서 선택한 lv값과 data[i].level 값이 일치할 때까지 루프를 돌리다가 일치하면 해당 i 값에 해당되는 data[i].name과 data[i].isOpen를 Data에서 찾아서 각각 levelName과 isOpen에 값을 할당하고 루프를 멈춥니다. 이 싱글톤은 해당 스테이지에 대한 정보를 currentLevel, levelName, isOpen 변수에 담아서 플레이 씬으로 가져갈 수 있습니다. currentLevel은 해당 스테이지 클리어 이후 미션성공 팝업창에서 다룬 Next 버튼에서 활용되는데 GameController 스크립트에서 임의로 SceneManager.LoadScene(2)로 아예 지정을 해주어서 무조건 2로 지정된 스테이지로만 이동했지만 이 부분을 SceneManager.LoadScene(currentLevel+1); 수정 해주면 Next버튼을 누를 때 그에 맞는 다음 스테이지로 넘어가게 할 수 있습니다.
levelName은 플레이 씬에서 UI로 현재의 스테이지를 보여줄 때 사용할 예정이며, isOpen은 레벨 씬에서 클리어한 상태를 보여줘야 하는 것으로 이 부분은 저장(Save)과 불러오기(Load) 기능을 다룰 때 하겠습니다.
이제 레벨 씬에서 할 일이 한 개 남았습니다. 스크립트를 한 개 생성한 후 StageDataSender라고 이름을 바꿔 줍니다. 이름은 짓기 나름인데 '스테이지 데이터를 보내는자'라는 뜻으로 지었습니다.
using UnityEngine; public class StageDataSender : MonoBehaviour { public int Level; LevelDataList levelDataList; // Start is called before the first frame update void Start() { levelDataList = GameObject.Find("LevelDataList").GetComponent<LevelDataList>(); } public void ClickLevel () { levelDataList.GetLevel(Level); } } |
이전에 레벨 씬 Hierarchy뷰에서 Canvas - Stage1 ~ 5까지 만들었습니다. 왼쪽 Shift키를 누른 상태에서 Stage1 오브젝트를 클릭하고 Stage5 오브젝트를 클릭하면 Stage1 ~ 5까지 오브젝트가 모두 선택됩니다. StageDataSender 스크립트를 드래그 앤 드롭해서 컴포넌트를 추가해줍니다. 각 버튼을 클릭하면 inspector뷰에 public으로 선언된 Level이 나타납니다. 좀 번거롭기는 하지만 각 레벨에 맞는 정수 1~5까지 각각 기입해 줍니다. 버튼에 추가된 StageDataSender 스크립트를 드래그 해서 해당 버튼의 on Click에 드롭해 줍니다. 이 때 Stage1이면 Stage1 Button의 on Click에 드래그 앤 드롭해야 합니다. 이 부분에서 꼬이면 엉뚱한 스테이지를 불러오게 됩니다. 오브젝트가 지정되면 public으로 선언된 ClickLevel ()를 버튼에 할당하면 해당 버튼을 누르면 levelDataList.GetLevel(Level)를 통해서 LevelDataList에 있는 GetLevel함수를 호출하며 호출하는 레벨은 설정된 값으로 나오게 됩니다. 이제 레벨 씬에서 플레이를 해서 각 레벨을 클릭해서 원하는 레벨로 이동하는지 확인하고 플레이를 한 스테이지를 클리어 했을 때 미션 성공 팝업창에서 Next 버튼에 의한 정상적인 씬 전환이 이루어지는지도 확인합니다. 다음은 레벨 씬에서 미션 성공을 통해 클리어 한 스테이지와 아직 플레이가 안 된 스테이지의 버튼 노출에 대해 알아보겠습니다.