WIN-B00 | WinAPI-Isaac — 처음 직접 만든 2D 게임에서 Game Loop와 Runtime 흐름을 경험하다
이 글에서 확인할 내용
WinAPI-Isaac은 제가 게임개발을 처음 배우면서 만든 WinAPI / C++ 기반 2D 개인 프로젝트입니다.
개발 기간은 2024.02.08 ~ 2024.03.14이고, 학원 커리큘럼 안에서 시작했지만 직접 게임을 완성하고 발표까지 해본 첫 프로젝트였습니다.
캐릭터를 움직이고 몬스터와 충돌시키고, 총알을 발사하고, 아이템을 먹고, 문을 통해 다음 방으로 이동하는 흐름을 직접 구현했습니다.
지금 보면 단순하고 하드코딩된 부분도 많습니다.
그래도 이 프로젝트를 만들면서 처음으로 게임이 한 프레임씩 어떻게 움직이는지를 코드와 화면 결과로 함께 경험했습니다.
이 글에서는 세부 Class나 함수 하나하나보다, 이 프로젝트를 통해 처음 연결해서 보게 된 전체 Runtime 흐름을 정리합니다.
Text
Win32 Entry
↓
Message Loop
↓
Game Loop
↓
Scene
↓
Object
↓
Input / Collision
↓
Resource / UI
↓
Render처음에는 화면에서 동작하는 것이 먼저였습니다
처음 WinAPI를 배울 때는 Message Loop, Update, Render 같은 말이 왜 필요한지 잘 몰랐습니다.
당시에는 이런 결과가 더 중요했습니다.
- 키를 누르면 캐릭터가 움직이는가?
- 총알이 원하는 방향으로 날아가는가?
- 몬스터와 충돌하는가?
- 아이템을 먹으면 상태가 바뀌는가?
- 문에 닿으면 다음 방으로 넘어가는가?
기능 하나하나는 작았지만, 직접 연결해보니 게임 화면은 한 번 그려지고 끝나는 것이 아니었습니다.
게임이 실행되는 동안 계속 같은 흐름이 반복되고 있었습니다.
Text
입력
↓
Object 상태 변경
↓
Collision 확인
↓
게임 상태 반영
↓
Render
↓
다음 Frame이 반복을 직접 구현하면서 Game Loop라는 개념을 처음 실제 동작으로 이해하게 됐습니다.
처음 만들 당시에는 이 흐름을 지금처럼 설명하지 못했습니다.
그때는 “캐릭터가 움직인다”, “총알이 나간다”, “방이 바뀐다”처럼 화면에서 보이는 기능을 하나씩 완성하는 데 집중했습니다.
프로젝트를 끝까지 만든 뒤 다시 코드를 따라가 보면서, 그 기능들이 사실은 모두 같은 Runtime 안에서 연결되어 있었다는 것을 이해하게 됐습니다.
왜 WinAPI를 배우는지도 나중에 이해했습니다
처음에는 솔직히 왜 WinAPI로 게임을 만드는지 잘 몰랐습니다.
Unity나 Unreal Engine 같은 상용 엔진이 있는데, WinAPI로 창을 만들고 BMP를 직접 출력하는 과정이 실제 게임개발과 어떻게 이어지는지 감이 없었습니다.
이후 DirectX와 Unreal Engine을 배우면서 이 경험의 의미를 다시 보게 됐습니다.
WinAPI-Isaac의 목적이 “WinAPI만으로 상용 게임을 만들 수 있다”는 것을 보여주는 데 있었던 것은 아닙니다.
저에게 더 중요했던 것은 게임 클라이언트도 결국 OS 위에서 실행되는 프로그램이고, Window 생성과 Message 처리, Frame Loop 같은 실행 기반 위에서 Gameplay가 움직인다는 사실을 처음 직접 확인한 것이었습니다.
Unreal에서는 이런 부분을 엔진이 많이 감싸주지만, WinAPI에서는 프로그램이 시작되고 게임 쪽 Runtime으로 넘어가는 경계를 눈으로 따라갈 수 있었습니다.
Game Loop 안에서 Scene과 Object가 움직였습니다
프로그램은 Win32의 wWinMain에서 시작하고, Message Loop 안에서 게임의 Update, Late_Update, Render가 반복되는 구조였습니다.
그 안에서 현재 Scene이 Logo인지, Menu인지, Stage인지에 따라 실제로 갱신되는 Object가 달라졌습니다.
Text
Win32 Entry
↓
Message Loop
↓
CMainGame
↓
현재 Scene
↓
Object Update / Collision
↓
Render처음에는 Logo에서 Menu로 넘어가고 Stage가 열리는 것을 단순한 화면 전환이라고 생각했습니다.
하지만 코드를 다시 따라가 보니 Scene이 바뀐다는 것은 그 화면에서 사용할 Player, Monster, Item, Door 같은 Object가 새로 준비되고, 다시 Update와 Render 흐름에 들어간다는 의미였습니다.
각 Stage에서는 필요한 Object를 생성해 Manager에 등록했고, Object들은 매 Frame 갱신되다가 상태에 따라 제거됐습니다.
이때부터 화면에 보이는 결과보다 그 뒤에서 누가 Object를 만들고, 누가 갱신하고, 언제 사라지는지를 조금씩 보게 됐습니다.
Text
Scene 진입
↓
Object 생성
↓
Manager 등록
↓
Update / Late_Update
↓
상태 변화
↓
필요하면 제거
↓
Render이 경험은 이후 프로젝트에서 Object Lifecycle이나 State Ownership을 볼 때의 가장 기초적인 기준이 됐습니다.
입력은 화면 이동에서 끝나지 않았습니다
Player 입력도 처음에는 단순하게 생각했습니다.
WASD를 누르면 위치를 바꾸고, 방향키를 누르면 총알을 생성했습니다.
그런데 실제 Runtime에서는 입력 뒤에 여러 단계가 이어졌습니다.
Text
Key Input
↓
Player 위치 변경
↓
Rect 갱신
↓
Projectile 생성
↓
Manager 등록
↓
Collision 대상이 됨총알은 Player가 끝까지 처리하는 값이 아니라 하나의 Object로 생성되어 별도의 흐름에서 이동했고, 이후 Monster나 Block과 충돌했습니다.
Player와 Item이 만나면 Item이 사라지고 상태가 바뀌었고, Door Trigger와 충돌하면 다음 Stage로 넘어갔습니다.
Monster, Bullet, Item, Door는 각각 따로 존재하는 것만으로 Gameplay가 완성되는 것이 아니라, Stage에서 필요한 Collision 관계가 연결되어야 실제 결과로 이어졌습니다.
처음에는 각각 별개의 기능처럼 만들었지만, 나중에 보니 모두 입력이 게임 상태 변화로 이어지는 하나의 흐름이었습니다.
Text
Input
↓
Object State
↓
Collision
↓
Gameplay Result
↓
Render화면에 보이는 이미지도 Runtime 결과였습니다
WinAPI-Isaac은 BMP 이미지를 Resource로 불러오고, Memory DC에 보관한 뒤 BitBlt 또는 GdiTransparentBlt를 이용해 화면에 그리는 방식이었습니다.
당시에는 BMP가 화면에 나오면 된다고 생각했지만, 직접 구현하면서 Resource를 먼저 준비하고 Object의 현재 위치와 상태를 기준으로 Render해야 실제 화면 결과가 나온다는 것을 알게 됐습니다.
배경이 먼저 그려지고, 그 위에 Player와 Monster, Bullet, Item이 그려지고, 마지막에 UI가 보이는 순서도 직접 확인했습니다.
Menu Button과 Stage의 Heart UI처럼 화면에 보이는 UI도 Runtime 안에서 생성되고 관리되는 Object로 다뤘습니다.
다만 Heart UI가 보인다는 사실만으로 완성된 HP System 전체를 구현했다고 확대해서 보지는 않습니다.
이 프로젝트에서 확인한 것은 Resource와 Object 상태가 Render를 거쳐 실제 화면에 나타나는 기본 흐름입니다.
DX11의 Shader나 RenderTarget과 비교하면 아주 단순한 방식이지만, 제게는 Resource가 Runtime 상태를 거쳐 화면에 나타나는 과정을 처음 경험한 프로젝트였습니다.
Runtime
실제 실행 영상에서는 Logo와 Menu를 거쳐 Stage에 진입하고, Player 이동과 Projectile 발사, Monster와의 상호작용, Item 획득, Room 이동 같은 결과를 확인할 수 있습니다.
영상에서 보이는 기능들은 각각 따로 실행되는 것이 아니라, 같은 Game Loop 안에서 Object가 계속 갱신되고 충돌과 상태 변화가 이어진 결과입니다.
처음 발표할 당시에는 이 흐름을 지금처럼 나눠 설명하지 못했습니다.
지금은 화면 결과를 보면서 그 뒤의 Runtime 흐름을 함께 설명할 수 있다는 점이 이 프로젝트를 다시 정리하는 이유이기도 합니다.
지금 다시 보면 보이는 부분
지금 다시 만든다면 구조를 그대로 사용하지는 않을 것 같습니다.
Stage마다 Collision 관계를 직접 연결하는 부분이 많고, Door 전환도 Stage, Manager, Collision, Scene 흐름에 나뉘어 있어 Room이 많아질수록 관리하기 어려운 구조였습니다.
Player 이동과 발사 조건도 코드 안의 분기에 많이 의존했고, Door의 목적지나 Stage별 Collision 규칙도 지금이라면 Data나 공통 구조로 더 분리해보고 싶습니다.
UI의 소유 주체도 한 곳으로 명확하게 정리되어 있지 않았습니다.
하지만 당시에는 이런 구조적인 문제를 미리 설계할 수 있는 단계가 아니었습니다.
먼저 캐릭터가 움직이고, 총알이 날아가고, 충돌하고, 방이 바뀌는 게임을 끝까지 만들어 보는 것이 더 중요했습니다.
그리고 직접 만들어봤기 때문에 이후 프로젝트에서는 자연스럽게 이런 질문을 하게 됐습니다.
Text
누가 생성하는가?
누가 가지고 있는가?
누가 Update하는가?
언제 삭제되는가?
Collision 관계는 어디에서 연결되는가?
어떤 상태 변화가 화면 결과로 이어지는가?지금 보이는 아쉬움은 당시 프로젝트를 과장하거나 낮춰 보기 위한 것이 아니라, 이후 프로젝트를 경험하면서 무엇을 더 구조적으로 보게 됐는지 확인할 수 있는 지점에 가깝습니다.
다음 단계로 이어진 부분
WinAPI-Isaac 이후에는 MFC-Crazy-Arcade에서 Map Tool과 Tile 배치 흐름을 경험했고, DX9-Cat-Quest에서는 3D Gameplay와 캐릭터·몬스터·스킬 구현으로 범위를 넓혔습니다.
이후 DX11-SpongeBob-BFBB에서는 3D Rendering, Picking, Culling, Navigation, ImGui Editor, Save / Load처럼 더 넓은 Client 구조를 다뤘고, Unreal 프로젝트에서는 Engine이 제공하는 더 큰 Gameplay Framework 안에서 State와 Owner, Lifetime을 따라가게 됐습니다.
Text
WinAPI-Isaac
↓
MFC-Crazy-Arcade
↓
DX9-Cat-Quest
↓
DX11-SpongeBob-BFBB
↓
Unreal Gameplay Projects구조와 기술은 훨씬 복잡해졌지만, 제가 Runtime을 따라가는 기준 자체는 이 프로젝트에서 처음 생겼습니다.
WinAPI-Isaac은 현재 포트폴리오의 대표 프로젝트는 아닙니다.
대신 제가 처음으로 게임이 한 프레임씩 움직이는 과정과 Object가 화면 결과가 되기까지의 흐름을 직접 만들어 본 출발점으로 남아 있습니다.
정리
WinAPI-Isaac에서 제가 가장 크게 얻은 것은 특정 기능 하나를 깊게 구현했다는 주장보다, 게임 클라이언트의 기본 Runtime을 처음 끝까지 연결해봤다는 경험입니다.
Text
Window / Message
↓
Game Loop
↓
Scene
↓
Object
↓
Input
↓
Collision
↓
Resource / UI
↓
Render처음에는 각각 따로 보였던 기능들이 프로젝트를 끝내고 다시 돌아보니 하나의 흐름으로 이어져 있었습니다.
WinAPI-Isaac은 2024.02.08 ~ 2024.03.14 동안 진행한 초기 2D 개인 프로젝트이자, 제가 게임을 직접 완성하고 발표하면서 Game Loop와 Runtime 흐름을 처음 몸으로 익힌 기록입니다.