일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 네트워크 게임 프로그래밍
- Deferred Rendering
- DirectX
- 장치 초기화
- 디퍼드 렌더링
- C++
- 네트워크
- 조명 처리
- 큐브 매핑
- 게임 디자인 패턴
- gitscm
- TCP/IP
- 게임 클래스
- 동적 색인화
- 입방체 매핑
- FrameResource
- Dynamic Indexing
- Render Target
- 게임 프로그래밍
- DirectX12
- 직교 투영
- gitlab
- Frustum Culling
- InputManager
- light
- effective C++
- direct3d
- Direct3D12
- 절두체 컬링
- 노멀 맵핑
- Today
- Total
코승호딩의 메모장
[SceneManager] 본문
이번 글에서는 여러 오브젝트들을 Game이 아닌 Scene에서 관리하기로 한다. 따라서 Scene은 여러 개의 게임 오브젝트들을 가지고 있으며 이 씬들을 관리하는 SceneManager을 구현하기로 한다. 씬은 게임 플레이의 하나의 장면이며 실제 게임에서는 여러 개의 씬을 가지고 다음 씬을 이동하거나 이전 씬으로 이동하거나 한다. 그러나 현재는 하나의 씬만 사용하도록 하고 SceneManager에서는 스마트 포인터를 통해 하나의 ActiveScene만 관리하도록 한다. 이후에 씬을 늘릴 것이다.
실제 DirectX 게임을 구현하기 위해서는 씬을 로드하는 방식을 사용해야 할 것이다. 유니티나 언리얼에서 편리하게 씬을 제작하여 파일로 내보내고 이를 우리의 응용 프로그램에서 파싱 하여 로드하는 방식이 맞는 방법일 테지만 아직은 간단하게 오브젝트 몇 개만 사용할 것이기 때문에 LoadTestScene이라는 하드 코딩된 씬을 부르도록 한다.
Scene
class Scene
{
public:
void Awake();
void Start();
void Update();
void LateUpdate();
void AddGameObject(shared_ptr<GameObject> gameObject);
void RemoveGameObject(shared_ptr<GameObject> gameObject);
private:
vector<shared_ptr<GameObject>> _gameObjects;
};
씬은 다음과 같이 멤버 변수로 게임 오브젝트들의 동적 배열을 가지고 있다. 씬 또한 게임 오브젝트처럼 라이프 사이클에 따라서 진행되어야 하기 때문에 각 함수들은 현재 자신이 가지고 있는 게임 오브젝트들의 해당 함수를 부르게 된다. AddGameObject에서는 간단하게 _gameObjects 배열에 넣어주고 RemoveGameObject에서는 _gameObjects에 있는 해당 오브젝트를 찾아 삭제한다.
SceneManager
class SceneManager
{
SINGLETON(SceneManager)
public:
void Update();
void LoadScene(wstring sceneName);
public:
shared_ptr<Scene> GetActiveScene() { return _activeScene; }
private:
shared_ptr<Scene> LoadTestScene();
private:
shared_ptr<Scene> _activeScene;
};
씬을 관리하는 씬 매니저는 편의를 위해 싱글턴 객체로 생성하도록 한다. 현재는 간단하게 Update에서 현재 씬의 Update와 LateUpdate를 호출하며 LoadScene에서는 현재 씬의 Awake와 Start를 함께 호출하고 현재 씬에 LoadTestScene을 반환받는다.
shared_ptr<Scene> SceneManager::LoadTestScene()
{
shared_ptr<Scene> scene = make_shared<Scene>();
//...
gameObject->Init();
shared_ptr<MeshRenderer> meshRenderer = make_shared<MeshRenderer>();
{
shared_ptr<Mesh> mesh = make_shared<Mesh>();
mesh->Init(vec, indexVec);
meshRenderer->SetMesh(mesh);
}
{
shared_ptr<Shader> shader = make_shared<Shader>();
shared_ptr<Texture> texture = make_shared<Texture>();
shared_ptr<Material> material = make_shared<Material>();
//... Set Material Params
meshRenderer->SetMaterial(material);
}
gameObject->AddComponent(meshRenderer);
scene->AddGameObject(gameObject);
return scene;
}
간단하게 구현하기 위한 LoadTestScene에서는 Game.cpp에 있던 오브젝트들의 정보를 여기로 이동해 주고 생성된 게임 오브젝트를 씬에 추가해 준다. 그리고 해당 씬을 반환하면 된다.
void Game::Init(const WindowInfo& info)
{
GET_SINGLE(SceneManager)->LoadScene(L"TestScene");
//...
}
void Engine::Render()
{
RenderBegin();
GET_SINGLE(SceneManager)->Update();
RenderEnd();
}
이제 Game에서 그냥 싱글턴을 이용해 TestScene을 생성하여 _activeScene에 저장하고 엔진의 렌더 함수에서 씬 매니저를 업데이트하도록 변경하면 끝이다. 이제 실행해 보도록 하자.
(???...)
어디서 많이 봤던 장면이 나온다. FrameResource방식을 사용하다보니 GPU명령이 다 수행되지 않았을 경우 종료시에 생기던 문제가 자주 발생하게 된다. 아마도 싱글턴 객체인 SceneManager가 Engine보다 늦게 소멸되어서 생긴 문제가 아닐까 싶어서 싱글턴에 Release를 통하여 Engine의 소멸자에서 동기화를 하기 전 먼저 RELEASE_SINGLE(SceneManger)을 통해 해제해줬다. 그러나 문제는 해결되지 않았다.
class Engine
{
public:
void LoadScene(wstring sceneName) { _sceneManager->LoadScene(sceneName); }
private:
uptr<SceneManager> _sceneManager;
};
void Engine::Init(const WindowInfo& info)
{
_sceneManager = make_unique<SceneManager>();
}
한참 고민한 끝에 그냥 엔진에 멤버 변수로 SceneManager를 넣어주도록 했다. 이렇게 내부 변수로 만들어 주니 문제 없이 잘 실행 되었다. 그러나 Manager 클래스들은 싱글턴으로 통일시켜주고 싶었고 결국 다른 방법을 찾아봤다. 앞에서 Release를 하였음에도 불구하고 소멸 시점이 문제가 아니라면 어디가 문제였을까? 그렇다면 생성 시점을 다르게 해보도록 하였다.
#define DECLARE_SINGLE(type) \
private: \
type() {} \
~type() {} \
public: \
static type* GetInstance() \
{ \
static type instance; \
return &instance; \
} \
현재 문제가 발생할 때 정의한 싱글턴 패턴이다. 현재는 정적 지역 변수로 선언하였기 때문에 싱글턴으로 생성되는 객체는 프로그램이 처음 시작될 때 생성될 것이다. 그렇다면 시점을 지연 초기화할 수 있는 두 번째 방법을 사용하기로 해봤다.
#define DECLARE_SINGLE(type) \
private: \
type() {} \
~type() {} \
public: \
static type* GetInstance() \
{ \
if (!instance) instance = new type(); \
return instance; \
} \
private: \
static type* instance; \
두 번째 방법을 사용한 것은 instance의 정적 포인터를 사용하였기 때문에 이 경우는 프로그램이 처음 시작될 때 생성되는 것이 아닌 GetInstance를 호출한 이 후에 생성된다. 두 싱글턴 방법 모두 소멸 시점은 같지만 생성 시점이 다르다. 이렇게 싱글턴의 생성 시점을 바꿔주니 놀랍게도 잘 실행이 되었다.
개인적인 생각으로는 소멸 시점이 엔진의 소멸자보다 늦게 불려 아직 SceneManager에 있던 객체들이 소멸이 안되어 문제인가 싶었는데 뜻밖에도 생성 시점에 문제가 있었던 것이었다. 정확한 오류 원인은 파악하지 못하였지만 이 후에 알게 되면 따로 글을 다시 작성하도록 하겠다. 두 번째 방법대로 싱글턴을 사용한다면 Game::Init의 부분에서 처음으로 호출되게 된다. 이보다 더 빨리 생성되더라도 별 다른 문제가 없을 것 같은데 왜 정적 지역 변수로 싱글턴을 만들면 실행에 오류가 뜨는지 미지수이다. 아는 분 있으면 댓글 써주시면 정말 감사드리겠습니다.
'DirectX12 > DirectX12 응용' 카테고리의 다른 글
[Light-1] (1) | 2023.10.10 |
---|---|
[Resources] (1) | 2023.10.10 |
[Camera] (1) | 2023.10.09 |
[Component] (0) | 2023.10.07 |
[InputManager와 GameTimer] (1) | 2023.10.07 |
[Material] (0) | 2023.10.07 |
[Texture Mapping] (0) | 2023.10.07 |