SceneManager 기반 게임 오브젝트 통합 관리 시스템 설계
이 강의의 핵심 목표는 게임 프로젝트의 구조를 개선하여, 오브젝트를 Game 클래스에서 직접 생성하고 관리하는 방식에서 벗어나, Scene 단위로 구성하고 이를 SceneManager를 통해 관리하는 구조로 리팩토링하는 것이다. 유니티와 유사하게 하나의 Scene에서 다양한 GameObject를 묶고, 게임의 생명 주기 함수를 체계적으로 실행할 수 있도록 설계한다.
추가적으로, 이후 다양한 Manager(Input, Time 등)를 확장하여 엔진 구조의 기본 틀을 완성하게 된다.
| 개념 | 설명 |
|---|---|
| Scene | GameObject들을 묶어 하나의 맵 또는 상태를 구성하는 논리 단위 |
| SceneManager | Scene을 선택하고 활성화하며 생명주기를 관리하는 관리자 |
| GameObject | Transform, Component(Camera, MeshRenderer 등)를 조합한 게임 오브젝트 |
| 게임 루프 단계 | Awake → Start → Update → LateUpdate → FixedUpdate 순서로 실행되는 루프 |
| Graphics/Pipeline | 렌더링 디바이스와 파이프라인. 디바이스 컨텍스트와 연결되어 렌더링 처리 |
| Manager 구조 | Input, Time, Scene 등 기능별 클래스를 분리해 체계적으로 관리하는 설계 |
| 전역 접근 | Game 클래스를 전역으로 선언(GGame), 매크로(SCENE, TIME 등)를 통해 간편하게 접근 |
| 용어 | 설명 |
|---|---|
shared_ptr<T> | C++ 스마트 포인터로 참조 카운트를 관리해 메모리 자동 해제 |
Scene, SceneManager | Scene은 오브젝트를 담는 단위, SceneManager는 그것을 관리 |
GameObject | 게임 오브젝트. Transform 및 다양한 컴포넌트를 포함 |
Awake~FixedUpdate | 오브젝트의 생명주기 함수. 순차적으로 호출됨 |
GGame | Game 객체를 전역으로 접근하기 위한 포인터 |
SCENE, INPUT, TIME | 각각 SceneManager, InputManager, TimeManager에 접근하기 위한 매크로 |
class Scene
{
public:
void Awake();
void Start();
void Update();
void LateUpdate();
void FixedUpdate();
void AddGameObject(shared_ptr<GameObject> gameObject);
void RemoveGameObject(shared_ptr<GameObject> gameObject);
const vector<shared_ptr<GameObject>>& GetGameObjects();
private:
vector<shared_ptr<GameObject>> _gameObjects;
};
_gameObjects: Scene에 소속된 GameObject를 저장하는 리스트AddGameObject, RemoveGameObject: GameObject를 Scene에 추가하거나 제거Awake~FixedUpdate: 루프 단계별로 GameObject를 순회하며 대응 함수 호출void Scene::Awake() {
for (const auto& obj : _gameObjects)
obj->Awake();
}
Awake는 초기화용, Start는 시작 세팅, Update는 매 프레임 호출class SceneManager
{
public:
SceneManager(shared_ptr<Graphics> graphics);
void Init();
void Update();
void LoadScene(wstring sceneName);
shared_ptr<Scene> GetActiveScene();
private:
shared_ptr<Scene> LoadTestScene();
shared_ptr<Graphics> _graphics;
shared_ptr<Scene> _activeScene;
};
_graphics: 렌더링 디바이스 접근용_activeScene: 현재 실행 중인 SceneLoadScene: 현재는 하드코딩된 LoadTestScene()을 통해 테스트 씬을 생성SceneManager::SceneManager(shared_ptr<Graphics> graphics)
: _graphics(graphics) {}
void SceneManager::Init() {
if (_activeScene)
{
_activeScene->Awake();
_activeScene->Start();
}
}
void SceneManager::Update() {
if (_activeScene)
{
_activeScene->Update();
_activeScene->LateUpdate();
_activeScene->FixedUpdate();
}
}
shared_ptr<Scene> SceneManager::LoadTestScene()
{
auto scene = make_shared<Scene>();
auto camera = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
camera->GetOrAddTransform();
camera->AddComponent(make_shared<Camera>());
scene->AddGameObject(camera);
auto cat = make_shared<GameObject>(_graphics->GetDevice(), _graphics->GetDeviceContext());
cat->GetOrAddTransform();
cat->AddComponent(make_shared<MeshRenderer>(_graphics->GetDevice(), _graphics->GetDeviceContext()));
scene->AddGameObject(cat);
return scene;
}
class Game
{
public:
Game();
~Game();
void Init(HWND hwnd);
void Update();
void Render();
shared_ptr<SceneManager> GetSceneManager();
shared_ptr<InputManager> GetInputManager();
shared_ptr<TimeManager> GetTimeManager();
shared_ptr<Pipeline> GetPipeline();
private:
HWND _hwnd;
shared_ptr<Graphics> _graphics;
shared_ptr<Pipeline> _pipeline;
shared_ptr<SceneManager> _scene;
shared_ptr<InputManager> _input;
shared_ptr<TimeManager> _time;
};
extern unique_ptr<Game> GGame;
#define GAME GGame
#define SCENE GAME->GetSceneManager()
#define INPUT GAME->GetInputManager()
#define TIME GAME->GetTimeManager()
GGame 전역 인스턴스 생성 → 매크로로 간편하게 Manager 접근void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
_graphics = make_shared<Graphics>(hwnd);
_pipeline = make_shared<Pipeline>(_graphics->GetDeviceContext());
_input = make_shared<InputManager>();
_input->Init(hwnd);
_time = make_shared<TimeManager>();
_time->Init();
_scene = make_shared<SceneManager>(_graphics);
_scene->Init();
SCENE->LoadScene(L"Test");
}
void Game::Update()
{
_graphics->RenderBegin();
TIME->Update();
INPUT->Update();
SCENE->Update();
_graphics->RenderEnd();
}
SceneManager는 내부에서 Scene의 오브젝트들 순회 업데이트좋습니다. 이어서 SceneManager 시스템 통합 학습 교재 – Part 2를 완성해드리겠습니다. 이 파트에서는 InputManager, TimeManager의 구현과 게임 전체 프레임 흐름, 그리고 MeshRenderer의 실제 렌더링 적용까지의 통합 구조를 설명합니다. 역시 모든 내용을 한 글자도 빠짐없이 분석하고 재구성했습니다.
Input/Time 시스템 통합 및 렌더링 흐름 구성
이번 파트는 앞서 구성한 Scene, SceneManager, GameObject 구조 위에, 입력(Input) 처리와 시간(Time) 관리 시스템을 추가하고, 게임의 프레임 루프 안에서 렌더링 흐름까지 통합하는 과정이다.
특히 렌더링을 위한 MeshRenderer의 업데이트 로직과, 전역 접근을 위한 매크로 구성까지 포함된다.
| 개념 | 설명 |
|---|---|
| InputManager | 키보드/마우스 입력을 감지하고 상태를 저장 및 갱신하는 클래스 |
| TimeManager | 시간 경과 측정 및 FPS 계산을 처리하는 클래스 |
| MeshRenderer | GameObject의 Mesh를 화면에 렌더링하는 컴포넌트 |
| Update 루프 | 게임에서 매 프레임마다 실행되는 함수 호출 구조 |
| 전역 접근 매크로 | INPUT, TIME 등의 매크로를 통해 Manager 클래스에 쉽게 접근 가능 |
| 용어 | 설명 |
|---|---|
VK_ | Windows API의 키보드 입력 상수 |
POINT | 마우스 좌표를 담는 WinAPI 구조체 |
QueryPerformanceCounter | 고해상도 타이머를 사용하는 시간 측정 함수 |
Camera::S_MatView, S_MatProjection | 전역 카메라 View/Projection 행렬 |
RenderBegin/RenderEnd | 그래픽 렌더링의 시작과 끝 처리 |
CopyData | ConstantBuffer에 데이터 복사하는 함수 (GPU로 전달) |
enum class KEY_TYPE { UP = VK_UP, DOWN = VK_DOWN, W = 'W', A = 'A', ... };
enum class KEY_STATE { NONE, PRESS, DOWN, UP, END };
class InputManager
{
public:
void Init(HWND hwnd);
void Update();
bool GetButton(KEY_TYPE key);
bool GetButtonDown(KEY_TYPE key);
bool GetButtonUp(KEY_TYPE key);
const POINT& GetMousePos();
private:
KEY_STATE GetState(KEY_TYPE key);
HWND _hwnd;
vector<KEY_STATE> _states;
POINT _mousePos = {};
};
KEY_TYPE: 키의 종류 정의 (방향키, WASD, 마우스 클릭 등)KEY_STATE: 키 입력의 상태 정의 (NONE, PRESS, DOWN, UP)GetButton 계열 함수: 외부에서 상태 체크 시 사용POINT: 마우스 위치void InputManager::Init(HWND hwnd)
{
_hwnd = hwnd;
_states.resize(KEY_TYPE_COUNT, KEY_STATE::NONE);
}
void InputManager::Update()
{
if (_hwnd != ::GetActiveWindow()) {
std::fill(_states.begin(), _states.end(), KEY_STATE::NONE);
return;
}
BYTE asciiKeys[KEY_TYPE_COUNT] = {};
::GetKeyboardState(asciiKeys);
for (uint32 key = 0; key < KEY_TYPE_COUNT; key++) {
bool isDown = asciiKeys[key] & 0x80;
KEY_STATE& state = _states[key];
if (isDown)
state = (state == KEY_STATE::PRESS || state == KEY_STATE::DOWN) ? KEY_STATE::PRESS : KEY_STATE::DOWN;
else
state = (state == KEY_STATE::PRESS || state == KEY_STATE::DOWN) ? KEY_STATE::UP : KEY_STATE::NONE;
}
::GetCursorPos(&_mousePos);
::ScreenToClient(_hwnd, &_mousePos);
}
class TimeManager
{
public:
void Init();
void Update();
uint32 GetFps();
float GetDeltaTime();
private:
uint64 _frequency = 0;
uint64 _prevCount = 0;
float _deltaTime = 0.f;
uint32 _frameCount = 0;
float _frameTime = 0.f;
uint32 _fps = 0;
};
void TimeManager::Init()
{
::QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&_frequency));
::QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&_prevCount));
}
void TimeManager::Update()
{
uint64 currentCount;
::QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(¤tCount));
_deltaTime = (currentCount - _prevCount) / static_cast<float>(_frequency);
_prevCount = currentCount;
_frameCount++;
_frameTime += _deltaTime;
if (_frameTime > 1.f) {
_fps = static_cast<uint32>(_frameCount / _frameTime);
_frameCount = 0;
_frameTime = 0.f;
}
}
void MeshRenderer::Update()
{
_cameraData.matView = Camera::S_MatView;
_cameraData.matProjection = Camera::S_MatProjection;
_cameraBuffer->CopyData(_cameraData);
_transformData.matWorld = GetTransform()->GetWorldMatrix();
_transformBuffer->CopyData(_transformData);
Render(GGame->GetPipeline());
}
Render()를 호출해 실제 렌더링 실행void Game::Update()
{
_graphics->RenderBegin();
TIME->Update(); // 시간 갱신
INPUT->Update(); // 입력 갱신
SCENE->Update(); // Scene 갱신 (모든 오브젝트 업데이트)
_graphics->RenderEnd();
}
Game::Update() 안에서 모든 시스템이 한 프레임 단위로 실행[ 프레임 루프 흐름 ]
1. RenderBegin()
2. TimeManager::Update()
3. InputManager::Update()
4. SceneManager::Update() → Scene::Update() → GameObject::Update() ...
5. RenderEnd()
Game::Update()에서 하나의 프레임 루프 내에서 실행됨GGame을 통한 전역 접근 및 SCENE, INPUT, TIME 매크로로 호출 간소화MeshRenderer는 Update() 안에서 카메라/변환 정보를 복사하고 Render() 수행