Default Defense | 다섯번째 프로젝트 # 1 : Scene 구조, 싱글톤

2024. 6. 3. 23:55·개발/Default Defense

개요

글을 시작하기 전에 간단하게 할말을 적고 시작하곤 했는데 지금은 별로 적을게 없습니다.

바로 가죠/

 


 

 

구조 구상

 

게임의 전체적인 구조는 간단하...진 않지만 Unity처럼 Scene을 나누는 구조를 사용하는 것이 좋다고 생각 했습니다.

블로그에 정리하진 않았지만 저번 2시간에 객체지향 프로그래밍, 싱글톤, 추상 클래스, 상속을 배웠습니다.

2시간만에 급하게 배운거라 뇌에 전부다 담진 못했지만 그래도 어떻게든 따라가면서 적어놓은 소스코드를 사용해서 적어봅시다.

 

일단 저희가 만드는 게임은 게임 로직을 처리하는 Update와 처리가 끝난 후 모든걸 그려주는 Render로 모든게 가능해야 합니다.

그렇기 때문에 각 Scene은 각자의 Update와 Render를 가지고 있어야 하고 이 Scene들을 가지고 있는 SceneManager, 모든 Scene의 부모가 되는 추상 클래스가 있어야 하겠죠.

 

 

SceneManager는 현재 활성화 되고 있는 Scene을 저장해서 그걸 Update, Render해주면 되겠죠.

 

좋습니다. 구조는 대충 눈에 들어왔습니다.

 

 

 


 

 

구조 짜기

Core

일단 게임의 전체적인 흐름을 담당할 Core 클래스를 만들어줍니다.

#pragma once
class Core
{
public:
	Core();
	~Core();

public:
	void init();
	void update();
	void render();
};

main 함수에선 이 Core의 update와 render만 계속해서 돌려줄겁니다.

 

#include "Core.h"
Core::Core()
{
}
Core::~Core()
{
}
void Core::init()
{

}
void Core::update()
{

}
void Core::render()
{

} 

 

그 후 대충 구현만 해주고 Main.cpp 파일을 파서 딱 이것만 해줍니다.

#include"Core.h"

int main()
{
	Core core = Core();

	core.init();
	while (true)
	{
		core.update();
		core.render();
	}
}

앞으로 이 Main.cpp는 건드릴일이 없을겁니다.

 

 

SceneManager, Scene (추상 클래스)

그 후 모든 Scene의 부모가 될 추상 클래스인 Scene을 만들어줍시다.

#pragma once
class Scene
{
public:
	Scene();
	~Scene();

public:
	virtual void init() abstract;
	virtual void update() abstract;
	virtual void render() abstract;
};

 

C#과 달리 클래스 자체에 abstract가 붙는 것이 아닌 함수에 붙습니다.

저렇게 정의된 함수들을 '순수 가상 함수' 라고 부르고 이게 없으면 추상 클래스가 아닙니다.

 

아무튼 Scene 클래스 작성은 끝났으니 이제 SceneManager를 적어줍시다.

 

일단 SceneManager는 모든 Scene을 가지고 있어야 하니 그 Scene을 꺼내올 수 있도록 map을 통해 string과 함께 묶어 저장해주는 함수인 registerScene, 그리고 Scene을 옮길때 사용할 함수인 loadScene을 적어줍시다.

public:
	void registerScene(const string& sceneName, Scene* scene);
	void loadScene(const string& sceneName);

 

 

그 뒤에 현재 들어와있는 씬이 필요하니 포인터 Scene 변수를 하나 선언해줍시다.

private:
	Scene* _pActiveScene = nullptr;
	map<string, Scene*> _sceneMap;

 

여기서 키 포인트! 왜 Scene 변수가 아닌 Scene 포인터 변수를 사용하는가?

 

Scene은 추상 클래스 입니다. 그쵸?

추상 클래스란, 완전히 구현되지 않은 클래스입니다.

어떤 함수가 어떻게 동작해야 하는가에 대한 내용을 갖고 있지 않다는 뜻입니다.

 

현재 Scene 클래스엔 init, render, update 같은 구현되지 않은 함수들이 있고 abstract 키워드를 통해

이 함수들은 나중에 Scene 클래스를 상속받는 클래스에 의해 작성되어야 한다고 명시했습니다.

 

그렇기 때문에 구현되지 않은 클래스인 Scene 클래스는 객체화 될 수 없어서 변수 선언 자체가 불가능합니다.

 

하지만 포인터 변수는 객체를 만들지 않습니다.

그저 Scene이라는 객체를 가르킬 수 있을 뿐이죠. 

 

여기서 또 다시 한번 질문하자면 어떻게 Scene을 가르키는 포인터가 그 Scene을 상속받는 자식 클래스를 가르킬 수 있을까요?

그 이유는 부모 클래스와 자식 클래스가 공통된 인터페이스를 갖기 때문입니다.

Scene이라는 클래스를 상속받은 TitleScene 클래스가 있다고 해봅시다. 

이 TitleScene은 Scene의 모든 멤버 함수, 변수를 갖고 있기 때문에 Scene 포인터는 TitleScene을 가르킬 수 있습니다.

 

 

음... 설명 좀 잘 적은 것 같은데 나중에 수업 정리할때 한번 더 써먹어야겠네요.

 

 

아무튼 이제 천천히 하나씩 구현해주면 됩니다.

SceneManager.cpp 를 열어줍시다.

 

먼저 registerScene은 만들어 둔 맵에 들어온 값을 넣어주기만 하면 됩니다.

void SceneManager::registerScene(const string& sceneName, Scene* scene)
{
	_sceneMap.insert({ sceneName, scene });
}

 

 

loadScene은 넘어온 string 값을 기반으로 맵에서 값을 찾아보고 맵에 값이 존재한다면 _pActiveScene을 바꿔주고 그 Scene의 init함수를 호출합니다.

void SceneManager::loadScene(const string& sceneName)
{
	auto iter = _sceneMap.find(sceneName);
	if (iter != _sceneMap.end())
	{
		_pActiveScene = iter->second;
		_pActiveScene->init();
	}
}

 

 

 

그 뒤엔 그냥 _pActiveScene의 update와 render를 호출해주면 끝입니다.

void SceneManager::init()
{
	_pActiveScene = nullptr;
}
void SceneManager::update()
{
	_pActiveScene->update();
}

void SceneManager::render()
{
	_pActiveScene->render();
}

 

초기화 잊지마시구요.

 

 

싱글톤

이제 SceneManager를 싱글톤화 시킬겁니다.

 

싱글톤이 뭔지 가볍게 짚고 넘어가자면 특정 클래스의 객체가 단 1개만 존재해서 뒤에 호출을 여러번 하더라도 처음에 생성된 객체를 계속해서 재활용 하는 디자인 패턴입니다.

이 인스턴스는 전역변수처럼 어디서든 얻을 수 있습니다.

 

그렇기 때문에 이 싱글톤 클래스들은 마음대로 생성할 수 없게 생성자 함수를 private로 놓아 사용하며 그 클래스의 단 하나뿐인 객체를 저장하기 위해 static 클래스 변수를 선언하여 가지고 있습니다.

 

private:
	SceneManager() {}
    ~SceneManager() {}
    static SceneManager* m_pInst;

이렇게 말이죠.

 

그 후 호출 하지 않아도 객체가 생성되는 걸 막기 위해 호출 될때 만약 m_pInst 가 nullptr이라면 생성해주도록 객체를 받아오는 함수를 만들어줍시다.

static SceneManager* getInstance()
{
	if(m_pInst == nullptr)
    {
    	m_pInst = new SceneManager;
    }
    return m_pInst;
}

이러면 끝입니다.

 

 

이제 이런식으로 하나의 객체에 접근할 수 있습니다.

SceneManager::getInstance()->update();

 

 

하지만 싱글톤이 하나는 아닐거 아닙니까? 싱글톤 하나하나에 전부다 이걸 적어주는건 좀 귀찮죠.

 

그렇기 때문에 Define.h 라는 헤더를 파서 매크로를 적어줍니다,

#define DECLARE_SINGLETON(template) \
private:\
	template() {}\
	~template() {}\
	static template* m_pInst;\
public:\
static template* getInstance()\
{\
	if(m_pInst == nullptr)\
	{\
		m_pInst = new template;\
	}\
	return m_pInst;\
}\

매개변수?에 들어가는 template은 매크로를 호출할때 넣어둔 값으로 자동 변환됩니다.

 

 

이왕 이렇게 된거 getInstance도 매크로화 시킵시다.

#define GET_SINGLETON(template) template::getInstance()

 

 

싱글톤까지 끝냈다면 SceneManager 작성 끝입니다.

#pragma once
#include"Scene.h"
#include"Define.h"
class SceneManager
{
	DECLARE_SINGLETON(SceneManager)
public:
	void registerScene(const string& sceneName, Scene* scene);
	void loadScene(const string& sceneName);
public:
	void init();
	void update();
	void render();
private:
	Scene* _pActiveScene = nullptr;
	map<string, Scene*> _sceneMap;
};

 

 

이제 테스트 겸 타이틀이 될 예정인 TitleScene을 작성해서 Scene을 상속해주고,

class TitleScene : public Scene

 

 

테스트를 위해 아무 문자나 출력하게 해준 다음,

void TitleScene::update()
{
	cout << "EFEFEF";
}
void TitleScene::render()
{
	cout << "EFEFEF";
}

 

 

 

 

Core.cpp에서 SceneManager에 TitleScene을 등록 해주고 로드해주면...

#include "Core.h"
#include "Define.h"
#include"SceneManager.h"
#include "TitleScene.h"
Core::Core()
{
}
Core::~Core()
{
}
void Core::init()
{
	GET_SINGLETON(SceneManager)->init();

	GET_SINGLETON(SceneManager)->registerScene("TitleScene", new TitleScene);

	GET_SINGLETON(SceneManager)->loadScene("TitleScene");
}
void Core::update()
{
	GET_SINGLETON(SceneManager)->update();
}
void Core::render()
{
	GET_SINGLETON(SceneManager)->render();
}

 

 

 

 

성!!!!!!공!!!!!!!!!!!!!!!!! 와!!!!!!!1

 

 

 


 

마치는 말

기본 구조를 다 짰으니 다음 글부턴 본격적으로 인게임 구현에 들어가보죠.

'개발 > Default Defense' 카테고리의 다른 글

Default Defense | 다섯번째 프로젝트 # 5 : Enemy Movement  (1) 2024.06.21
Default Defense | 다섯번째 프로젝트 # 4 : Entity  (2) 2024.06.15
Default Defense | 다섯번째 프로젝트 # 3 : 맵  (2) 2024.06.15
Default Defense | 다섯번째 프로젝트 # 2 : Transition  (0) 2024.06.14
Default Defense | 다섯번째 프로젝트 # 0 : 기획  (0) 2024.06.03
'개발/Default Defense' 카테고리의 다른 글
  • Default Defense | 다섯번째 프로젝트 # 4 : Entity
  • Default Defense | 다섯번째 프로젝트 # 3 : 맵
  • Default Defense | 다섯번째 프로젝트 # 2 : Transition
  • Default Defense | 다섯번째 프로젝트 # 0 : 기획
SundG0162
SundG0162
블로그 프로필은 머핀입니다.
  • SundG0162
    게임개발고수가될거야
    SundG0162
  • 전체
    오늘
    어제
    • 분류 전체보기 (78)
      • 주저리 (3)
        • 잡담 (2)
        • 장현우 (0)
        • 회고록 (1)
      • 개발 (50)
        • CITADEL : 성채 (1)
        • HEXABEAT (1)
        • FRACTiLE (6)
        • UNNAMED (9)
        • Default Defense (10)
        • T-Engine (1)
        • Project EW (0)
        • 졸업작품 (0)
        • Unity (3)
        • C# (4)
        • C++ (13)
        • WinAPI (1)
        • 그 외 (0)
      • 알고리즘 (13)
        • C# (1)
        • C++ (12)
      • 자료구조 (2)
        • C++ (1)
        • C# (0)
        • 공용 (1)
      • 기타 (10)
        • 아트 (6)
        • AI (2)
        • 수학 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    LLM
    생성형ai
    코딩
    C#
    티스토리챌린지
    코딩트리조별과제
    AI
    코딩테스트
    유니티
    코드트리
    오블완
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
SundG0162
Default Defense | 다섯번째 프로젝트 # 1 : Scene 구조, 싱글톤
상단으로

티스토리툴바