구조를 잡아본다.
MeshRenderer.h에서 보면,
나중에 Mesh는 Rsource로 빼줄 것이고, Material도 빼 줄 것이고, 일부분은 MeshRenderer라고 해서 그리는 역할을 맡게 될 무엇인가를 만들어 줄 건데 그걸 통합해 관리하기 위해 매니저 시스템을 도입하도록 한다.
여러 방식이 있을 수 있다.
00.Engine 필터에 Manager 필터를 만든 다음에 공용으로 관리할 애들을 빼줄 것이다.
현재는 Game클래스가 총괄 대장인 느낌이고, 여기에 이런 저런 기능을 넣어줄 것이다.
대표적으로 InputManager, FrameManager(시간 관리), SceneManager(오브젝트 관리)
그걸 하려면 Manager 클래스에서 다양한 기능들을 제공해야 한다.
이를 위해 Manager 필터 안에 Input, Scene, Time 필터들을 만든다. 이런 걸 여러 개 만들어 줄 것이다.
Input과 Time은 WindAPI에서 쓰던 걸 복붙해서 쓸 것이다. 지금은 SceneManager위주로 만들어 본다.
Scene 필터에 SceneManager클래스와 Scene클래스를 만든다.
Game클래스에서 관리하고 있었던 _monster, _camera같은 오브젝트들을 묶어서 통합해서 관리하는 역할을 한다.
1. Scene 클래스
기본적인 고정 함수들을 가지고 있게 한다.
#pragma once
class GameObject;
class Scene
{
public:
void Awake();
void Start();
void Update();
void LateUpdate();
void FixedUpdate();
public:
void AddGameObject(shared_ptr<GameObject> gameObject);
void RemoveGameObject(shared_ptr<GameObject> gameObject);
const vector<shared_ptr<GameObject>>& GetGameObjects() { return _gameObjects; }
private:
vector<shared_ptr<GameObject>> _gameObjects;
};
함수들을 구현한다.
#include "pch.h"
#include "Scene.h"
#include "GameObject.h"
void Scene::Awake()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->Awake();
}
}
void Scene::Start()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->Start();
}
}
void Scene::Update()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->Update();
}
}
void Scene::LateUpdate()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->LateUpdate();
}
}
void Scene::FixedUpdate()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->FixedUpdate();
}
}
void Scene::AddGameObject(shared_ptr<GameObject> gameObject)
{
_gameObjects.push_back(gameObject);
}
void Scene::RemoveGameObject(shared_ptr<GameObject> gameObject)
{
auto findIt = std::find(_gameObjects.begin(), _gameObjects.end(), gameObject);
if (findIt != _gameObjects.end())
_gameObjects.erase(findIt);
}
2. SceneManager 클래스
Scene은 GameObject를 들고 있는 껍질에 불과한 것이고, Scene을 이용해 나중엔 화면을 관리할 것인데,
SceneManager는 무엇을 해야 되는 것일까?
여러개의 Scene들 중에서 하나를 골라서 걔를 들고 있게끔 하는 게 목적이다.
원래 Scene도 하나의 리소스로 만들어서 리소스들 중에서 하나를 로드해서 사용하게끔 만들어야 한다. 지금은 툴 상으로 그렇게 지원하는 게 힘들기 때문에 그냥 임의의 테스트 씬을 로드하는 LoadTestScene 여기서 만들어 볼 것이다.
#pragma once
class Scene;
class SceneManager
{
public:
SceneManager();
void Init();
void Update();
void LoadScene(wstring sceneName);
public:
shared_ptr<Scene> GetActiveScene() { return _activeScene; }
private:
shared_ptr<Scene> LoadTestScene();
private:
shared_ptr<Graphics> _graphics;
private:
shared_ptr<Scene> _activeScene;
};
만약 툴을 이용한 진지한 엔진까지 지원한다면 리소스들 중에 골라서 씬을 여는 것까지 포함이 되어야 한다. LoadScene함수에 씬의 이름을 넣어서 로드하는 방식으로 될 텐데 일단 지금은 안 해주고 넘어간다.
지금은 간단하게 임의로 개발 단계에서 사용할 씬을 만들어 줘서 이런저런 내용을 집어넣어 테스트하는 식으로 작업을 해 볼 것이다.
LoadTestScene에는 Game::Init에서 임시로 넣어 줬던 _monster, _camera를 만들어줬던 부분인
_monster = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
_monster->GetOrAddTransform();
_monster->AddComponent(make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext()));
// _monster->GetTransform()->SetScale(Vec3(100.f, 100.f, 1.f));
// ..
}
_camera = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
_camera->GetOrAddTransform();
_camera->AddComponent(make_shared<Camera>());
}
이 코드를 LoadTestScene에 옮겨 수정하고,
// TEMP
shared_ptr<GameObject> _monster;
shared_ptr<GameObject> _camera;
도 삭제한다.
임시로 TestScene을 만들어 준다.
#include "pch.h"
#include "SceneManager.h"
#include "Scene.h"
#include "GameObject.h"
#include "Camera.h"
#include "Transform.h"
#include "MeshRenderer.h"
SceneManager::SceneManager()
{
}
void SceneManager::Init()
{
if (_activeScene == nullptr)
return;
_activeScene->Awake();
_activeScene->Start();
// 간단하게 묘사하기 위해 이렇게만 해본다.실제론 복잡한 순서
}
void SceneManager::Update()
{
if (_activeScene == nullptr)
return;
_activeScene->Update();
_activeScene->LateUpdate();
// _activeScene->FixedUpdate();
// 원래대로라면 흐름에 맞게 이루어져 있다. FixedUpdate면 고정 프레임으로 실행되는 무엇인가 등등
}
void SceneManager::LoadScene(wstring sceneName)
{
// Resource
_activeScene = LoadTestScene();
Init();
}
shared_ptr<Scene> SceneManager::LoadTestScene()
{
shared_ptr<Scene> scene = make_shared<Scene>();
// Camera
{
shared_ptr<GameObject> camera = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
camera->GetOrAddTransform();
camera->AddComponent(make_shared<Camera>());
scene->AddGameObject(camera);
}
}
// Monster
{
shared_ptr<GameObject> monster = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
{
monster->GetOrAddTransform();
monster->AddComponent(make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext()));
// _monster->GetTransform()->SetScale(Vec3(100.f, 100.f, 1.f));
// ..
scene->AddGameObject(monster);
}
}
return scene;
}
이렇게 구현해 주면 된다.
LoadTestScene에서 camera와 monster 오브젝트를 만들어서 scene에 넣어주었다.
빌드를 하면 에러가 나는데 Game::Update와 Game::Render에서 사용하던 원래 있던 _monster, _camera 오브젝트 변수를 삭제했어서 그렇다.
3. SceneManager 변수 _scene을 Game에 선언해 Game이 SceneManager를 관리하게 하고, Game 변수 GGame을 Game에서 전역으로 선언해 언제 어디서든 접근할 수 있게 하기
선택해야 할 것은 Manager를 어떻게 관리해야 할 것인가다
Singleton으로 GetManager를 언제 어디서든 편하게 접근할 수 있게 하는 방법이 있고,
두 번째 방법은 전역으로 Game에 누구나 쉽게 접근할 수 있게 하고, 나머지 애들은 그 대장인 Game이 들고 있게 하는 것도, 굉장히 편리한 방식 중 하나다.
두 번째 방법으로 해본다.
Game.h에
private:
shared_ptr<SceneManager> _scene;
Scene을 Game안에서 들고 있게끔 유도한다.
class SceneManager;
전방선언도 한다.
shared_ptr<SceneManager> GetSceneManager() { return _scene; }
_scene을 바로 사용할 수 있는 함수도 만들어 준다.
그리고 Game을 언제 어디서든 접근할 수 있게 만들어 줄 건데, 싱글톤으로 만들어주거나, 전역변수로 이용하는 것을 고려할 수 있다. 여기선 전역 변수로 만들어 준다.
extern unique_ptr<Game> GGame;
수정한 Game의 코드는 다음과 같다.
#pragma once
#include "Graphics.h"
#include "GameObject.h"
class SceneManager;
class Game
{
public:
Game();
~Game();
public:
void Init(HWND hwnd);
void Update();
void Render();
shared_ptr<SceneManager> GetSceneManager() { return _scene; }
private:
HWND _hwnd;
shared_ptr<Graphics> _graphics;
shared_ptr<Pipeline> _pipeline;
private:
shared_ptr<SceneManager> _scene;
};
extern unique_ptr<Game> GGame;
그리고 Game.cpp 에
unique_ptr<Game> GGame = make_unique<Game>();
이렇게 해서 언제 어디서든 GGame을 이용해 Game에 접근할 수 있게 된 거니, GGame→GetSceneManager()를 하면, 언제 어디서든 SceneManagr를 편리하게 사용할 수 있게 된다.
4. 전역 GGame을 간편하게 쓸 수 있게 매크로를 정의하고, Game::Init, Game::Update에서 이를 이용해 SceneManager를 사용하기
GGame→GetSceneManager() 이렇게 치는 게 귀찮다 싶으면
pch.h에 매크로 하나를 파서 관리하는 것도 괜찮은 방법이다.
#define GAME GGame
#define SCENE GAME->GetSceneManager()
이렇게 파주면, 언제 어디서든 편리하게 사용할 때 대문자 SCENE만 사용하면 된다.
void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
_graphics = make_shared<Graphics>(hwnd);
_pipeline = make_shared<Pipeline>(_graphics->GetDeviceContext());
SCENE->LoadScene(L"Test");
}
이렇게 사용하는 것을 고려할 수 있다.
Game.cpp에
#include "SceneManager.h"
를 추가하고,
void Game::Update()
{
SCENE->Update();
}
이렇게 해주면 되고,
Render 할 때도
void Game::Render()
{
_graphics->RenderBegin();
SCENE->
_graphics->RenderEnd();
}
이런 식으로 SCENE을 통해 뭔가를 해주면 된다.
void Game::Render()
{
_graphics->RenderBegin();
// IA - VS - RS - PS - OM
{
_monster->GetMeshRenderer()->Render(_pipeline);
}
_graphics->RenderEnd();
}
이 기존의 코드의 기능을 구현하면 된다.
(SCENE이 GetSceneManager를 호출해서 _scene이라는 SceneManager 변수를 return 하는데 LoadTestScene을 호출해서 camera, monster 오브젝트를 만들고 scene에 넣어 scene을 리턴하는데 그걸 받아서 monster의 오브젝트로 Render를 실행 하면 된다. )
5. SCENE을 통해 SceneManager 변수인 _scene을 return 받는데, 이 _scene이 비어 있으니 Game::Init에서 SceneManager 생성자에 _graphic을 전달해 채워주기
SCENE 매크로에서 GetSceneManager가 리턴해주는shared_ptr<SceneManager> _scene의 내용을 채워줘야 한다.
Game::Init에서
_scene = make_shared<SceneManager>();
이렇게 만들어 줄 것인데,
SceneManager 클래스를 보면 생성자에서 Graphics만 받아주면 된다.
SceneManager.h에서
SceneManager(shared_ptr<Graphics> graphics);
이렇게 받아주도록 한다.
ScneeManager 생성자 구현부에서
SceneManager::SceneManager(shared_ptr<Graphics> graphics)
: _graphics(graphics)
{
}
이렇게 _graphics를 기억하고 있어야지 렌더링을 할 때 _graphics를 넘겨줄 수가 있다.
Game::Init에서
void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
_graphics = make_shared<Graphics>(hwnd);
_pipeline = make_shared<Pipeline>(_graphics->GetDeviceContext());
_scene = make_shared<SceneManager>(_graphics);
SCENE->LoadScene(L"Test");
}
shared_ptr<SceneManager> _scene; 을 채워줄 때 _graphics를 전달한다.
6. Game::Render의 코드를 Game::Update로 이전하고, SceneManager의 Update를 실행하기
SceneManager에서 Update를 할 때는
void SceneManager::Update()
{
if (_activeScene == nullptr)
return;
_activeScene->Update();
_activeScene->LateUpdate();
_activeScene->FixedUpdate();
}
이런 걸 해줌과 동시에 결국에는 원했던 Rendering을 같이 해주려고 하면,
SceneManager.h에 임시적으로 Render함수를 넣어준다.
void Render();
구현하면
Rendering을 할 때는 모든 애들을 대상으로
처음에 Gmae::Render 부분에 들어갔던 부분을 넣어주면 된다.
void Game::Render()
{
_graphics->RenderBegin();
// IA - VS - RS - PS - OM
{
// TEMP
_monster->GetMeshRenderer()->Render(_pipeline);
}
_graphics->RenderEnd();
}
이렇게 들어가 있었는데
편리하게 하기 위해 Game::Render의 코드들을 Game::Update에 같이 넣도록 한다.
void Game::Update()
{
_graphics->RenderBegin();
SCENE->Update();
_graphics->RenderEnd();
}
SCENE이 GetSceneManager매크로니까,
void SceneManager::Update()
{
if (_activeScene == nullptr)
return;
_activeScene->Update();
_activeScene->LateUpdate();
_activeScene->FixedUpdate();
// 원래대로라면 흐름에 맞게 이루어져 있다. FixedUpdate면 고정 프레임으로 실행되는 무엇인가 등등
}
SceneManager의 Update가 실행되면 _activeScene->Update();이 실행 되면서,
void Scene::Update()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->Update();
}
}
이렇게 모든 오브젝트들의 Update가 실행이 된다.
모든 물체를 대상으로 Update가 호출이 되면서, Rendering 하는 부분도 같이 달려서 실행되게 할 것이다.
원래는 MeshRenderer의 Render부분이 따로 호출되는 게 흠이긴 하지만 이건 MeshRenderer를 고치면 된다.
지금 실행을 하면 야매 처리한 코드를 복원하지 않았기 때문에 에러가 발생한다.
SceneManager 클래스에서
void Render();
는 삭제한다.
'DirectX' 카테고리의 다른 글
28. 엔진구조_ RenderManager (0) | 2024.01.23 |
---|---|
27. 엔진구조_ResourceManager (0) | 2024.01.22 |
25. 엔진구조_MeshRenderer (0) | 2024.01.19 |
24. 엔진구조_Component (0) | 2024.01.17 |
22. 프레임워크 제작_GameObject (0) | 2024.01.05 |
댓글