WIN-B04 | WinAPI-Isaac — BMP Resource에서 UI·Render까지, 그리고 다음 단계로
이 글에서 확인할 내용
WinAPI-Isaac에서 마지막으로 많이 부딪혔던 부분은
준비한 Resource와 현재 Object 상태를 실제 화면에 보여주는 과정이었습니다.
처음에는 BMP 파일을 불러와 화면에 출력하면 Render가 끝난다고 생각했습니다.
하지만 직접 구현해보니 이미지가 보이기까지는
Resource를 먼저 준비하고, 필요한 Object가 그 Resource를 찾아오고,
현재 위치와 상태를 기준으로 화면에 그리는 흐름이 필요했습니다.
Player, Monster, Bullet, Item뿐 아니라
Menu Button이나 Heart UI도 같은 Runtime 안에서 생성되고 Render되었습니다.
Text
BMP Resource
↓
Memory DC
↓
Object / UI
↓
현재 위치와 상태
↓
BitBlt / GdiTransparentBlt
↓
화면 결과이 경험은 이후 DirectX에서 Texture와 Render Pipeline을 공부하고,
Unreal에서 Material과 Widget을 다룰 때도
“화면에 보이기 전까지 어떤 단계가 있는가”를 먼저 생각하게 만든 출발점이었습니다.
BMP 파일이 바로 화면에 그려지는 것은 아니었습니다
처음에는 이미지 파일이 있으면
그 파일을 바로 화면에 출력하는 것으로 생각했습니다.
WinAPI-Isaac에서는 먼저 BMP Resource를 불러와
다시 사용할 수 있는 형태로 준비했습니다.
대략적인 흐름은 다음과 같았습니다.
Text
BMP 파일 경로
↓
CBmpMgr::Insert_Bmp
↓
CMyBmp::Load_Bmp
↓
LoadImage
↓
Memory DC 준비
↓
CBmpMgr::Find_Image
↓
Object Render
↓
화면 출력CBmpMgr::Insert_Bmp()는 파일 경로와 Key를 받아 Resource를 등록했고,
CMyBmp::Load_Bmp()에서는 BMP를 읽어 GDI에서 사용할 수 있는 형태로 준비했습니다.
그 이후 Player나 Monster 같은 Object는
매번 파일을 다시 읽는 것이 아니라
자신에게 필요한 Resource를 Key로 찾아 사용했습니다.
Text
Object Render
↓
Resource Key
↓
CBmpMgr::Find_Image
↓
Memory DC
↓
Draw처음에는 단순한 구조라고 생각했지만,
이 과정을 직접 만들면서 처음으로
Resource를 준비하는 단계와 실제 Render에서 사용하는 단계가 분리될 수 있다는 점을 경험했습니다.
CMyBmp는 BMP와 Memory DC 사이를 연결했습니다
CMyBmp에서는 LoadImage로 BMP를 읽고,
CreateCompatibleDC로 Memory DC를 준비한 뒤
Bitmap을 선택해 Render에서 사용할 수 있도록 보관했습니다.
처음에는 왜 이미지를 바로 화면에 그리지 않고
중간에 Memory DC를 만드는지 이해하기 어려웠습니다.
하지만 실제 흐름을 따라가 보니 구조는 비교적 단순했습니다.
Text
파일에서 Bitmap Load
↓
Memory DC 준비
↓
Bitmap 선택
↓
Render 시 화면 DC로 복사지금 기준에서 보면 DirectX의 Texture Resource와 같은 구조라고 볼 수는 없습니다.
다만 당시에는
Resource를 미리 준비하는 단계와 실제 Frame에서 사용하는 단계가 분리된다는 감각을 처음 얻었습니다.
이 작은 경험이 이후 DirectX에서 Resource 생성과 Render 사용 시점을 구분해서 보는 데 도움이 됐습니다.
Render는 현재 Runtime 상태를 화면에 보여주는 마지막 단계였습니다
Object의 Render(HDC hDC)에서는
현재 위치와 Resource를 이용해 화면에 이미지를 그렸습니다.
투명 처리가 필요한 Sprite는 GdiTransparentBlt를 사용했고,
배경처럼 그대로 복사하는 경우에는 BitBlt를 사용했습니다.
일반적인 흐름은 다음과 같았습니다.
Text
Object Update
↓
현재 위치 / 상태 결정
↓
Resource 찾기
↓
BitBlt / GdiTransparentBlt
↓
화면 결과Player가 화면에서 오른쪽으로 이동해 보인다면
Render 함수 하나만 잘 동작해서 나온 결과는 아니었습니다.
그 전에 Input으로 위치가 바뀌고,
Rect가 갱신되고, Collision 결과가 반영된 뒤
마지막으로 현재 위치에 맞춰 이미지가 그려졌습니다.
즉 Render는 독립된 기능이라기보다
그 Frame 동안 바뀐 Runtime 상태를 마지막에 화면에 보여주는 단계였습니다.
배경과 Object, UI는 그리는 순서가 달랐습니다
CMainGame::Render()에서는 Background를 먼저 그리고
그 이후 현재 Scene의 Render 흐름으로 넘어갔습니다.
Scene은 다시 자신이 가진 Object와 UI를 Render했습니다.
Text
CMainGame::Render
↓
Background
↓
CSceneMgr::Render
↓
현재 Scene
↓
Player / Monster / Bullet / Item
↓
UI처음에는 그림을 순서대로 출력하는 정도로 생각했습니다.
하지만 직접 결과를 확인하면서
무엇을 먼저 그리고 나중에 그리는지에 따라
화면에 보이는 결과가 달라진다는 것을 알게 됐습니다.
지금은 DX11에서 Render Group, Depth, RenderTarget 같은 더 큰 개념을 다루고 있지만,
제가 Render Order를 처음 눈으로 확인한 경험은 이 프로젝트였습니다.
UI도 Runtime 안에서 생성되고 관리됐습니다
WinAPI-Isaac에서 UI는 한 가지 Manager에만 모여 있지 않았습니다.
Menu의 Start, Edit, Exit Button은
CObjMgr::OBJ_UI 쪽에 들어가는 CMyButton Object였고,
Stage의 Heart UI는
CUiMgr::UI_HEART 쪽에서 CHP Object로 관리됐습니다.
Text
Menu Button
→ CObjMgr::OBJ_UI
→ CMyButton
→ Render
Heart UI
→ CUiMgr::UI_HEART
→ CHP
→ Render처음에는 둘 다 단순히 “화면 위에 보이는 UI”라고 생각했습니다.
하지만 다시 보면
같은 UI처럼 보여도 생성 위치와 관리 주체가 달랐습니다.
이 경험을 통해 UI도 결국 누군가 생성하고,
Manager에 등록하고, Update하고, Scene이 끝날 때 정리해야 하는
Runtime 대상이라는 것을 처음 알게 됐습니다.
Heart UI는 상태 표시의 시작점에 가까웠습니다
Heart UI를 화면에 출력하면서
Gameplay 상태를 화면에 보여주는 흐름도 처음 경험했습니다.
다만 이 부분은 범위를 정확히 구분하고 있습니다.
Heart 이미지가 보인다는 것은
UI Object가 생성되고 Manager를 거쳐 Render되고 있다는 뜻이지,
그 자체로 복잡한 Damage 계산이나 Data Binding까지 포함된
완성된 HP System 전체를 구현했다는 뜻은 아닙니다.
Text
UI Object 생성
↓
Manager 등록
↓
Update / Render
↓
화면에 상태 표시이 프로젝트에서 제가 경험한 것은
Gameplay 상태와 화면 표시가 서로 다른 책임일 수 있다는 기초적인 감각에 가까웠습니다.
화면에 무언가 보이는 것과
실제 Gameplay State가 제대로 연결되어 있는 것은 구분해서 봐야 한다는 점도
지금 다시 코드를 보면서 더 분명해졌습니다.
화면에 보이지 않을 때는 Render만 보면 안 됐습니다
이미지가 화면에 나오지 않는 문제가 생기면
처음에는 Render 함수부터 확인하기 쉬웠습니다.
하지만 Resource와 Render를 직접 연결해보면서
확인해야 할 지점이 더 많다는 것을 알게 됐습니다.
Text
Resource가 등록됐는가?
↓
등록한 Key와 찾는 Key가 같은가?
↓
Load_Bmp가 성공했는가?
↓
Memory DC가 준비됐는가?
↓
Object가 실제로 생성됐는가?
↓
Render가 호출되고 있는가?
↓
현재 위치가 올바른가?예를 들어 Object가 Find_Image()로 특정 Key를 찾는데
Insert_Bmp()에서 다른 Key로 등록했다면
Render 함수 자체가 정상이어도 원하는 이미지가 나오지 않을 수 있습니다.
이 프로젝트 안에서도 Logo 쪽 Resource 등록 Key와
Render에서 찾는 Key를 함께 확인해야 하는 지점이 있었습니다.
이 경험을 통해
화면에 안 보인다는 마지막 증상만 보고 마지막 함수만 수정하면 원인을 놓칠 수 있다는 것을 처음 체감했습니다.
이 확인 방식은 이후 DX11 프로젝트에서도
Resource → Runtime Object → Render Result 순서로 문제를 따라가는 습관으로 이어졌습니다.
Resource와 UI는 Lifetime도 같이 봐야 했습니다
Resource나 UI가 화면에 정상적으로 보이는 것만으로
Runtime 흐름이 끝나는 것은 아니었습니다.
Menu Button은 CObjMgr 쪽에 있고,
Heart UI는 CUiMgr 쪽에 있었기 때문에
Scene이 바뀌거나 Stage가 종료될 때
어떤 Manager가 어떤 Object를 정리하는지도 같이 봐야 했습니다.
Text
생성
↓
Manager 등록
↓
Update / Render
↓
Scene Change
↓
Manager 정리당시에는 기능이 화면에 보이게 만드는 데 더 집중했지만,
지금 다시 보면 UI나 Resource 역시
누가 소유하고 언제 정리하는가가 중요한 문제였습니다.
이 관점은 B02에서 다룬 Object Lifecycle과도 이어집니다.
Runtime
실제 영상에서는 Background 위에 Player와 Monster, Projectile, Item, UI가 함께 출력되고,
입력과 Collision에 따라 화면 결과가 계속 바뀌는 모습을 확인할 수 있습니다.
영상에서 Player가 보인다는 것은
Player Object가 생성되고, 필요한 BMP Resource를 찾아
현재 위치에 맞게 Render되고 있다는 뜻입니다.
Bullet이 보인다는 것도 마찬가지입니다.
Player 입력으로 Bullet이 생성되고,
Manager에 등록된 뒤 Update와 Render를 계속 거치고 있습니다.
UI가 보이는 장면은
UI Object가 Runtime에 등록되어 Render 순서 안에서 화면에 그려지고 있다는 결과입니다.
Text
Resource 준비
↓
Object 생성
↓
Manager 등록
↓
Update / Collision
↓
Render
↓
화면 결과처음 발표할 때는 이런 장면을
“화면에 잘 나온다” 정도로 설명했을 가능성이 큽니다.
지금 다시 보면 같은 장면도
Resource, Object State, Manager, Render가 이어진 결과로 설명할 수 있습니다.
지금 다시 보면
지금 다시 만든다면 Resource와 UI의 소유 관계를 더 명확하게 나누고 싶습니다.
당시에는 기능이 화면에 보이도록 만드는 것이 우선이었기 때문에
Manager가 여러 곳으로 나뉘어 있고,
Resource Key와 UI Lifetime도 지금 기준에서는 더 정리할 수 있는 부분이 있습니다.
예를 들어:
Text
Resource 등록
Resource Lookup
UI Ownership
Scene Lifetime
Render Order를 좀 더 명확하게 분리하고 싶습니다.
하지만 그때는 오히려 이런 직접적인 구조가 도움이 됐습니다.
BMP 하나가 화면에 나오기까지 직접 따라가면서
“Resource를 불러온다”와 “화면에 보인다” 사이에
여러 단계가 있다는 것을 처음 알게 됐기 때문입니다.
또 Resource가 준비되어 있어도
Object가 생성되지 않았거나, 잘못된 Key를 사용하거나,
Render 흐름에 들어오지 않으면 결과가 보이지 않는다는 것도 경험했습니다.
MFC에서는 하드코딩된 배치의 불편함을 다시 보게 됐습니다
WinAPI-Isaac에서는 Stage마다 Object를 직접 배치하고
필요한 관계를 코드로 연결하는 방식이 많았습니다.
당시에는 기능을 구현하는 데 집중했지만,
Object와 Stage가 많아질수록 이런 방식이 불편해질 수 있다는 점도 느꼈습니다.
다음 MFC-Crazy-Arcade에서는
이 경험이 Map Tool과 Tile 배치 Workflow를 다루는 방향으로 이어졌습니다.
Text
WinAPI
직접 Object 배치
↓
배치가 많아질수록 수정 불편
↓
MFC
Map Tool / Tile Workflow 경험즉 WinAPI-Isaac에서 배운 것은 Runtime 기초뿐 아니라,
게임 데이터를 코드 안에 직접 배치하는 방식이 커졌을 때
Tool이 왜 필요한지 체감하게 된 출발점이기도 했습니다.
DX9에서는 3D Gameplay로 범위를 넓혔습니다
DX9-Cat-Quest에서는
WinAPI에서 경험한 기본 Object / Input / Collision 흐름이
3D Gameplay 쪽으로 확장됐습니다.
캐릭터, Monster, Skill, 상호작용을 구현하면서
2D Rect와 GDI Render에 머물렀던 경험이
더 큰 3D Client Runtime으로 이어졌습니다.
WinAPI에서 배웠던 질문은 그대로 남아 있었습니다.
Text
Object는 어떻게 생성되는가?
누가 Update하는가?
Input은 어떤 상태 변화를 만드는가?
상호작용 결과는 어디에서 처리되는가?
화면 결과는 어떤 Render 흐름에서 나오는가?DX11에서는 Resource와 Render 구조가 훨씬 커졌습니다
DX11-SpongeBob-BFBB에서는
WinAPI에서 BMP와 Memory DC로 경험했던 Resource / Render 흐름이
Texture, Shader, RenderTarget, Deferred Rendering 같은 더 큰 구조로 확장됐습니다.
또 Picking, Frustum Culling, Navigation, ImGui Editor, Save / Load까지 다루면서
단순히 화면에 Object를 그리는 것을 넘어
더 넓은 Client 구조를 보게 됐습니다.
Text
WinAPI
BMP / Memory DC / BitBlt
↓
DirectX
Texture / Shader / RenderTarget / Rendering Pipeline두 프로젝트의 기술 수준을 같은 선상에 놓으려는 것은 아닙니다.
오히려 WinAPI-Isaac은
그 이후 더 복잡한 Rendering과 Resource 구조를 배우기 전에
Resource가 준비되고 Runtime 상태와 만나 화면 결과가 되는 가장 단순한 형태를 직접 경험한 단계였습니다.
Unreal에서는 엔진이 감싼 구조 안에서 같은 질문을 하게 됐습니다
Unreal 프로젝트에서는 개발자가 직접
BitBlt나 GdiTransparentBlt를 호출하지 않습니다.
Texture, Material, Widget, Actor, Component 같은
더 큰 Engine 구조 안에서 작업하게 됩니다.
그래도 제가 계속 보는 질문은 비슷했습니다.
Text
Resource는 어디에서 준비되는가?
누가 이 State를 가지고 있는가?
언제 생성되는가?
언제 화면에 반영되는가?
UI는 Gameplay 상태와 어떻게 연결되는가?
Lifetime은 어디까지인가?RemnantSoul에서는 Unreal C++ 기반의 Input, AbilitySet, Weapon Style, Animation, Gameplay Flow로 이어졌고,
RiteSeekers에서는 Experience, PawnData, ASC, Inventory, Equipment, GAS Combat처럼
더 큰 Gameplay Framework 안에서 State와 책임 위치를 따라가게 됐습니다.
구조 자체는 완전히 달라졌지만,
WinAPI-Isaac에서 처음 생긴 **“화면 결과 뒤의 Runtime 흐름을 따라가 보는 습관”**은 계속 남았습니다.
WinAPI-Isaac 이후 달라진 점
WinAPI-Isaac은 지금의 대표 프로젝트처럼
복잡한 Architecture나 Rendering 기술을 보여주기 위한 프로젝트는 아닙니다.
WinAPI와 GDI 기반으로 만든 초기 2D 게임 프로젝트이고,
제가 처음으로 게임 클라이언트의 기본 Runtime을 직접 연결해본 경험에 가깝습니다.
Text
Game Loop
↓
Scene
↓
Object
↓
Input
↓
Collision
↓
Resource
↓
UI
↓
Render이후 프로젝트 흐름은 다음처럼 이어졌습니다.
Text
WinAPI-Isaac
↓
MFC-Crazy-Arcade
↓
DX9-Cat-Quest
↓
DX11-SpongeBob-BFBB
↓
RemnantSoul
↓
RiteSeekersMFC에서는 Tool과 배치 Workflow를 경험했고,
DX9에서는 3D Gameplay 구현으로 범위를 넓혔으며,
DX11에서는 Rendering과 Client 구조를 더 깊게 다뤘습니다.
Unreal로 넘어온 뒤에는
Engine이 많은 부분을 대신 처리해주더라도
State가 어디에서 만들어지고, 누가 소유하고, 어떤 흐름을 거쳐 화면 결과가 되는지를 먼저 보려고 했습니다.
WinAPI-Isaac은 그 기준을 처음 만들어 준 프로젝트였습니다.
정리
WinAPI-Isaac을 만들 당시에는
각 기능을 하나씩 동작시키는 것만으로도 어려웠습니다.
BMP를 불러오고, Player를 움직이고, Bullet을 만들고,
Monster와 충돌시키고, UI를 그리고, 다음 방으로 넘어가게 만드는 일이
각각 별개의 문제처럼 보였습니다.
하지만 프로젝트를 끝까지 만들고 다시 코드를 따라가 보니
모든 기능은 하나의 Runtime 흐름 안에서 연결되어 있었습니다.
Text
Resource 준비
↓
Input
↓
Object 상태 변화
↓
Manager 갱신
↓
Collision
↓
UI / 상태 반영
↓
Render
↓
다음 Frame이 프로젝트를 설명할 때
“고급 Rendering 구조를 설계했다”거나
“Engine Architecture를 만들었다”고 말하는 것은 맞지 않습니다.
제가 확실하게 말할 수 있는 것은
BMP Resource가 Memory DC에 준비되고, Object와 UI의 현재 상태가 Render를 거쳐 실제 화면 결과가 되는 흐름을 직접 구현했다는 것,
그리고 그 경험을 시작으로 이후 MFC, DirectX, Unreal 프로젝트에서 더 큰 Client Runtime을 보게 됐다는 점입니다.
WinAPI-Isaac은 제가 처음으로
코드 안의 상태 변화와 Resource가 실제 화면 결과가 되는 과정을 끝까지 연결해본 프로젝트였습니다.