태크놀로지

인덱스를 사용한 씬관리 방법 본문

디자인 패턴

인덱스를 사용한 씬관리 방법

원택 2020. 9. 3. 21:00

가상클래스 씬 설계

/* Scenes
@ 그릴 오브젝트들이 무엇인지를 설계한다.
@ 오브젝트 생성 및 배치
*/
class Scene abstract
{
protected:
	friend class SceneManager;
	virtual void ProcessEvent(int sEvent, int argsCount = 0, ...) {};
	virtual void UIEvent(int sEvent, int argsCount = 0, ...) {};

public:
	Scene();
	virtual ~Scene();

	virtual void Initialize() = 0;
	virtual void OnResize() = 0;

public:
	virtual bool	Enter() = 0;
	virtual void	Exit() = 0;

	virtual void	Update(const float& fDeltaTime) = 0;
	virtual void	Render() = 0;
	virtual void	RenderUI() = 0;

protected:
	std::string m_SceneName;
	Controller* m_SceneController;
};

제가 생각한 씬의 정의는 어떠한 게임오브젝트를 생성하고, 어떤 로직에 따라 배치할지를 작성해놓은 설계도로 정의하였습니다.

 

Initialize

씬이 생성될때 초기화를 위해 딱 한번 호출되는 가상함수입니다. 장기간 텀프로젝트로 게임규모가 커지면 수많은 게임오브젝트들을 생성해야합니다. 따라서 코드의 가독성을 위해 생성되야할 게임오브젝트들을 씬마다 분리하였습니다.

 

OnResize

화면 해상도 크기가 변경될때, 변경된 화면 비율에 따라 객체들을 스케일해줍니다.

 

Enter

씬 전환시 씬에 입장할때, 해야할 초기화 작업들을 진행합니다.

 

Exit

현재 씬이 끝나고 전환이 될때, 값들을 초기값들로 변경해주기 위한 Exit함수입니다. 

 

Update / Render / RenderUI

컴포넌트들과 게임오브젝트들을 업데이트해주고 그려주는 함수를 호출합니다.

Render와 RenderUI로 나눈 이유는 공간좌표가 달라, 배치하는 과정이 달라짐으로 함수를 분류하였습니다.


스택구조를 사용하여 씬을 관리하는 씬-매니저

#pragma once
#include "Singleton.h"

class Scene;
class SceneManager : public TemplateSingleton<SceneManager>
{
private:
	friend class Service;
	friend class LoadingService;

	template<class NewScene>
	void CreateScene(SceneType sceneType, std::string sceneName)
	{
		Scene* scene = new NewScene;
		scene->m_SceneName = sceneName;
		scene->Initialize();
		m_Scenes[static_cast<int>(sceneType)] = scene;
	}

	void SendEvent(SceneType sceneType, int sEvent);
	void SendEventArgs(SceneType sceneType, int sEvent, int argsCount, ...);

public:
	explicit SceneManager();
	virtual ~SceneManager();
	
	virtual void InitializeScenes();
	virtual void OnResize();

public:
	/* ChangeScene
	@ ChangeScene: 다음 씬으로 넘어감
	@ ChangeScene(SceneType): 이동하고 싶은 씬으로 이동함
	*/
	void	ChangeScene();
	void	ChangeScene(SceneType sceneType);

	void	EnterScene(SceneType sceneType);
	void	ExitScene();

	void	UpdateScene(const float& deltaT);
	void	RenderScene();
	void	RenderUI();

	Scene*	GetCurScene() const;
	UINT	GetCurSceneIndex() const;

private:
	std::array<Scene*, static_cast<int>(SceneType::Count)> m_Scenes;
	UINT m_CurScene;
	std::mutex m_MutexChangeScene;
};

씬매니저는 씬을 관리해주는 클래스입니다. 관리자로서 인스턴스가 한개만 생성되야하고, 전역접근이 필요함으로 싱글턴으로 구현되었습니다.

 

씬들을 관리하는 컨테이너 선택

씬들은 게임이 시작될때 한번만 생성되고, 씬전환시 인덱스를 통해 접근하기 때문에 std::array컨테이너를 사용하였습니다. (하지만 게임내에서 사용하는 맵이 다양해지고, 게임 중 맵을 동적으로 생성해야하는 상황이 온다면 std::vector를 사용하는게 더 맞다고 판단이 됩니다.)

 

씬매니저가 생성될시 하는 작업들

void SceneManager::InitializeScenes()
{
	CreateScene<LogoScene>(SceneType::Logo, "Logo");
	CreateScene<TitleScene>(SceneType::Title, "Title");
	CreateScene<LobbyScene>(SceneType::Lobby, "Lobby");
	CreateScene<GameroomScene>(SceneType::GameRoom, "GameRoom");
	CreateScene<GameplayScene>(SceneType::GamePlay, "GamePlay");
	CreateScene<GameresultScene>(SceneType::GameResult, "GameResult");
}

게임내에서 필요한 모든 씬들을 생성하고, 배열에 넣어줍니다. 배열의 인덱스는 SceneType이라는 Enum class를 사용하여 관리합니다.

SceneManager::SceneManager() :
	m_CurScene(0)
{
}

SceneManager::~SceneManager()
{
	for (auto& s : m_Scenes)
	{
		SAFE_DELETE_PTR(s->m_SceneController);
	}

	for (auto& s : m_Scenes) {
		s->Exit();
		SAFE_DELETE_PTR(s);
	}
}

씬매니저의 생성자와 소멸자입니다. 현재 씬을 가리키는 인덱스를 0으로 초기화 해주고, 소멸시 모든 씬들의 컴포넌트와 메모리를 해제시켜줍니다.

 

씬전환 작업

void SceneManager::ChangeScene()
{
	if (m_CurScene == -1)
		cout << "NullScene" << endl;

	m_MutexChangeScene.lock();
	m_Scenes[m_CurScene]->Exit();

	m_CurScene += 1;

	if (m_CurScene >= static_cast<int>(SceneType::Count))
		m_CurScene = 1;

	m_Scenes[m_CurScene]->Enter();
	m_MutexChangeScene.unlock();
}

다음씬으로 전환시켜주는 함수입니다. 씬전환시 현재 씬을 가리키고 있는 인덱스 변수를 증가시켜주고, 씬의 최대개수가 될 경우 초기값으로 지정해줍니다. 현재 씬의 Exit 함수를 호출하고, 전환될 다음 씬의 Enter함수를 호출하여 씬들 초기값으로 세팅해줍니다.

 

void SceneManager::ChangeScene(SceneType sceneType)
{
	if (m_CurScene == -1)
		cout << "NullScene" << endl;

	m_MutexChangeScene.lock();
	m_Scenes[m_CurScene]->Exit();

	m_CurScene = static_cast<int>(sceneType);
	m_Scenes[m_CurScene]->Enter();
	m_MutexChangeScene.unlock();
}

원하는 씬으로 전환시켜주는 함수입니다. 현재 씬을 가리키고 있는 인덱스를 함수 파라미터로 넘어온값으로 세팅해주고 이외는 위와 동일합니다.


서버에서 전달된 패킷에 따른 이벤트 처리

// 인자 없을 때 사용.
void SceneManager::SendEvent(SceneType sceneType, int sEvent)
{
	m_Scenes[static_cast<int>(sceneType)]->ProcessEvent(sEvent);
}
// 인자가 있을 경우 사용
void SceneManager::SendEventArgs(SceneType sceneType, int sEvent, int argsCount, ...)
{
	va_list ap;
	MMRInfo mmrInfo = {};
	string id_str;
	switch (sEvent)
	{
	case EVENT_LOBBY_MANUALMATCH_ROOM_JOIN_DENY:
		int deny_code;
		va_start(ap, argsCount);
		deny_code = va_arg(ap, int);
		va_end(ap);

		m_Scenes[static_cast<int>(sceneType)]->ProcessEvent(sEvent, argsCount, deny_code);
		break;

	
	case EVENT_LOBBY_UPDATE_USERINFO:
		va_start( ap, argsCount );
		id_str = va_arg( ap, string );
		mmrInfo.mmr = va_arg( ap, int );
		mmrInfo.catched_student_count = va_arg( ap, int );
		mmrInfo.total_game_count = va_arg( ap, int );
		mmrInfo.winning_rate = va_arg( ap, double );
		va_end( ap );

		m_Scenes[static_cast<int>(sceneType)]->ProcessEvent( sEvent, argsCount, id_str, mmrInfo.mmr, mmrInfo.catched_student_count, mmrInfo.total_game_count, mmrInfo.winning_rate );

		break;

	case EVENT_GAMEROOM_PLAYER_ENTER:
	{
		int argID;
		va_start(ap, argsCount);
		argID = va_arg(ap, int);
		va_end(ap);

		m_Scenes[static_cast<int>(sceneType)]->ProcessEvent(sEvent, argsCount, argID);
		break;
	}
											.
                                            .
                                            .
                                            .
	default:
	{
		std::cout << "Unknown Event recv \n";
		break;
	}
	}
}

서버에서 패킷이 도착하면 패킷을 분석하여, 서비스 중개자를 통해 씬매니저의 이 함수를 사용하여 이벤트와 파라미터를 특정 씬에게 전달합니다. 패킷에 전달된 파라미터 개수는 알 수 없기 때문에 가변 인자 템플릿을 사용하여 넘겨주고 있습니다.

 

씬매니저를 작성하며 느낀 소감

어떠한 디자인 패턴을 응용하냐에 따라 각각의 코딩 스타일과 프레임워크가 만들어지기 때문에 위에 올린것이 정답이라고 생각하지는 않습니다. 배웠던 자료구조와 디자인패턴들을 실험해가며 최소한의 비용과 메모리를 사용하기 위해 이러한 구조를 설계하게 되었습니다.

네트워크 구조를 설계할때 가변 인자 템플릿을 사용하였는데, 매우 난이도가 있는 부분이라 결함이 많을 수 있다고 판단됩니다.

'디자인 패턴' 카테고리의 다른 글

단점을 보안한 옵저버 패턴  (0) 2020.09.15
옵저버 패턴이란?  (0) 2020.09.13
커맨드 패턴이란?  (0) 2020.09.11
컴포넌트 패턴이란  (0) 2020.09.08
싱글턴 패턴이란  (0) 2020.09.02