SceneManager를 잘 만들려면 어떻게 해야할까?
SceneManager란?
어디선가 듣기로는 SceneManager, GameManager, SoundManager등 이런식으로 처리 하는 것이 좋은게 아니라고 들었는데 이유를 듣지는 못해서 나는 이대로 쓰고 있다.내가 생각하는 SceneManager란, 스테이지 1-1, 1-2, 1-3 등의 씬이 이동될때 이를 세팅해주는 매니저라고 생각한다. 현재 사용중인 Godot의 경우에는 유니티와 다르게 씬 기반은 아니지만 위와 같은 역할을 해줄 주체는 필요하다고 생각되어 만들어 보게 되었다.
쉬운 SceneManager 만들기
가장 쉽게 생각나는 SceneManager만드는 방법은 아래와 같다.
SceneSetting("Start_Scene");
void SceneSetting(String sceneName)
{
if(sceneName == "Start_Scene"){
setup();
}
else if(sceneName == "Stage_1_1_Scene"){
setup2();
}
else if(sceneName == "Stage_1_2_Scene"){
setup3();
}
}
void setup();
void setup2();
void setup3();
스테이지가 늘어나면? else if를 늘리고 구현함수를 아래에 넣으면 된다.
장점은 구현이 쉽다는 것이고 단점은 스테이지가 200개면? 중간에 수정을 위해서 코드 찾기도 힘들어 질 것이고 내가 수정을 잘못 하면 뭐가 바뀌었는지 알기도 어렵다는 것이다. 이러한 단점을 극복하기 위해 객체지향설계를 한 코드를 작성하는 것이다.
객체지향적 SceneManager
위 코드를 보면 우리는 두가지를 연결해야한다. 어떠한 Scene이 왔을 때 어떠한 함수를 실행해야하는지 이다. 하지만, SceneManager가 이걸 관리해야 할까? 그 주체가 잘못된 것이다. 어떠한 Scene이 왔을 때, 어떤 함수가 실행되야 되는지는 해당 Scene이 알아야 하고 SceneManager는 해당 객체의 함수만 실행시켜주면 되는것이다.
나의 Godot의 SceneManager는 아래와 같다.
extends Node2D
func PrepareScene(sceneObject: Object) -> void :
sceneObject.sceneSetUp()
그럼 이제 sceneObject들에 대해서 볼 것이다. 모든 씬은 sceneSetUp()함수를 가지고 있어야 된다. 객체지향 설계중 상속에 해당하는 내용이다. 이를 interface로 구현해서 각 씬 Node에 상속하면 구현을 강제 할 수 있다.
Godot 에는 인터페이스가 없어 아래와 같이 assert문으로 구현을 강제 할 수 있다.
extends Node2D
class_name SceneBase
#인터페이스
func sceneSetUp() -> void:
assert(false, "sceneSetUp() must be implemented in child classes!")
마지막으로 각 씬에서는 이를 구현해서 사용하면 된다. 구현하지 않으면 assert문이 오류를 낸다.
extends SceneBase
func _ready() -> void:
pass
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func sceneSetUp() -> void :
print(123)
이러면 최종적으로 내 설계에는 GameManager가 SceneManager를 호출하는데 있어서 아래의 코드가 결과를 잘 출력한다.
func _ready() -> void:
var start_scene = preload("res://2. script/Scene/start_scene.gd").new()
$"/root/SceneManager".PrepareScene(start_scene)
시행착오
1. Godot도 C++을 제외한 다른 언어들과 같이 다중상속을 제한한다. extends가 여러개가 안되기 때문에 그 아랫단 Base로 Node2D를 내려서 커스텀 노드 형식으로 만들었지만 다른 방법도 존재한다.
Base Class를 인스턴스화 해서 속성으로서 사용하는 방법이다.
extends Node2D
var scene_setting : SceneSetting
func _ready() -> void:
scene_setting = SceneSetting.new()
scene_setting.setup()
gpt가 말하기로는 결합도의 차이라고 한다. Scene class간의 강한 결합도를 형성하냐 독립적으로 두냐의 선택인 것 같다.
2. load 와 preload
기존 코드에서 load를 사용했더니 정적인 메서드 처럼 호출할 수 없다는 오류가 나왔다.
load와 preload는 의미처럼 load는 런타임에 로드되는 방식이기에 게임중에 리소스가 필요한 순간 로드된다. preload의 경우에는 컴파일중에 로드되어 게임시작시점에 미리 로드된다.
preload : 자주 사용되는 씬, 게임 내내 필요할 리소스
load : 동적으로 필요할 때