WinApi/TBI(더 바인딩 오브 아이작) 모작
[Win32 API TBI 모작] 5. Manager(4) - 아이템, 엔티티, 플레이어, 씬
Vfly
2025. 5. 29. 00:16
이번 포스트에서는 게임 컨텐츠 관련 매니저에 대해 알아보자
게임 컨텐츠 관련 매니저는 다음과 같다.
- 아이템, 엔티티, 플레이어, 맵, 씬
- 아이템, 엔티티, 플레이어 매니저는 대부분 이미지 분할, 병합 작업을 수행한다
전체 소스코드 : https://github.com/vfly1189/TBI
아이템 매니저 ( CItemMgr )
개요
- CItemMgr은 게임 내 아이템 시스템을 총괄하는 매니저 클래스.
- Isaac-like 로그라이크 게임에서 플레이어의 아이템 소지, 픽업 아이템 관리, 그래픽 리소스 처리를 담당.
클래스 구조 분석
핵심 데이터 구조
PickUp 구조체
typedef struct _PickUp {
int m_iCoin; // 코인 개수
int m_iBomb; // 폭탄 개수
int m_iKey; // 열쇠 개수
} PickUp;
게임 내 소모품 아이템의 개수를 관리하는 구조체.
주요 멤버 변수
- Item* m_possessedActiveItem: 현재 소지한 액티브 아이템
- vector<Item*> m_possessedPassiveItem: 소지한 패시브 아이템들
- PickUp m_stPickUps: 픽업 아이템 개수 관리
- int m_iPrevActiveCharge: 이전 프레임의 액티브 아이템 충전량
주요 기능 분석
1. 아이템 관리 기능
액티브 아이템 관리
void SetActiveItemCharge(int _value) {
m_possessedActiveItem->m_iCurCharge += _value;
// 범위 제한 (0 ~ 최대 충전량)
m_possessedActiveItem->m_iCurCharge = max(0, min(m_possessedActiveItem->m_iCurCharge,
m_possessedActiveItem->m_iMaxCharge));
}
void UseActiveItem() {
m_possessedActiveItem->m_iCurCharge = 0; // 사용 시 충전량 초기화
}
특징:
- 충전량 기반 액티브 아이템 시스템
- 안전한 범위 제한으로 오버플로우 방지
- 사용 시 충전량 완전 초기화
아이템 할당
void SetPossessedActiveItem(int _iItemNum) {
m_possessedActiveItem = &items[_iItemNum]; // 전역 배열에서 참조
}
2. 초기화 및 업데이트
시스템 초기화
void init() {
// 아이템 태그 설정
for (size_t i = 0; i < items.size(); i++)
items[i].m_sItemTag = items_tags[i];
// 랜덤 액티브 아이템 할당 (32~34번 중)
int randActiveItem = rand() % 3 + 32;
m_possessedActiveItem = &items[randActiveItem];
InitializeGraphics();
}
- 실제 게임과 유사하지 않고, 임의로 설정
- 특정 범위(32-34)에서 랜덤 액티브 아이템 선택
- 그래픽 초기화를 시스템 초기화와 분리
#pragma once
enum class COLLECTIBLES_ITEM_TYPE
{
ACTIVE,
PASSIVE,
FAMILIAR,
};
enum class PICKUP_ITEM_TYPE
{
HEART,
COIN,
KEY,
BOMB,
};
typedef struct _Item
{
wstring m_sItemTag; //아이템 이름
int m_iNumber; //아이템 번호
COLLECTIBLES_ITEM_TYPE m_eItemType; //아이템 타입 ( 액티브 , 패시브 )
float m_fAddMaxHp; //+최대 hp
float m_fAddAttackDmg; //+공격력
float m_fAddAttackDmgCoef; //+공격력 배수
float m_fAddAttackSpd; //+공격속도
float m_fAddMoveSpd; //+이동속도
float m_fAddAttackRange; //+공격사거리
int m_iCurCharge; //액티브 아이템의 경우 현재 차지
int m_iMaxCharge; //액티브 아이템의 경우 최대 차지
}Item;
vector<wstring> items_tags;
vector<Item> items = {
{L"", 1, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.2f, 0.f, 0.f},
{L"", 2, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 3.f, 0.f, 0.f, 0.f},
{L"", 3, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 1.f, 3.f, 0.f, 0.f, 0.f},
{L"", 4, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 1.f, 1.5f, 0.f, 0.f, 0.f},
{L"", 5, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 3.f, 0.f, 0.f, 100.f},
{L"", 6, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.5f, 0.f, -100.f},
{L"", 7, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 3.f, 0.f, 0.f, 0.f, 0.f},
{L"", 8, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 9, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 10, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 11, COLLECTIBLES_ITEM_TYPE::PASSIVE, 3, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 12, COLLECTIBLES_ITEM_TYPE::PASSIVE, 2, 1.f, 1.5f, 0.f, 0.f, 0.f},
{L"", 13, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.2f, 100.f, 0.f},
{L"", 14, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 200.f, 0.f},
{L"", 15, COLLECTIBLES_ITEM_TYPE::PASSIVE, 2, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 16, COLLECTIBLES_ITEM_TYPE::PASSIVE, 3, 0.f, 0.f, 0.f, 50.f, 0.f},
{L"", 17, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 18, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 19, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 20, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 100.f, 0.f},
{L"", 21, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 22, COLLECTIBLES_ITEM_TYPE::PASSIVE, 1, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 23, COLLECTIBLES_ITEM_TYPE::PASSIVE, 1, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 24, COLLECTIBLES_ITEM_TYPE::PASSIVE, 1, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 25, COLLECTIBLES_ITEM_TYPE::PASSIVE, 1, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 26, COLLECTIBLES_ITEM_TYPE::PASSIVE, 1, 0.f, 0.f, 0.f, 0.f, 0.f},
{L"", 27, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 100.f, 0.f},
{L"", 28, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 100.f, 0.f},
{L"", 29, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 0.f, 200.f},
{L"", 30, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 0.f, 200.f},
{L"", 31, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.f, 50.f, 50.f},
{L"", 32, COLLECTIBLES_ITEM_TYPE::PASSIVE, 0, 0.f, 0.f, 0.5f, 0.f, 0.f},
{L"", 33, COLLECTIBLES_ITEM_TYPE::ACTIVE, 1, 0.f, 0.f, 0.f, 0.f, 0.f , 3, 3},
{L"", 34, COLLECTIBLES_ITEM_TYPE::ACTIVE, 0, 2.f, 1.f, 0.f, 0.f, 0.f , 1, 5},
{L"", 35, COLLECTIBLES_ITEM_TYPE::ACTIVE, 0, 0.f, 0.f, 0.f, 0.f, 0.f , 6, 6},
};
items 배열의 이름 부분은 CFileMgr에서 아이템 이미지들을 불러올때 items_tags 배열에 저장된다.
엔티티 매니저 ( CEntityMgr )
개요
- CEntityMgr은 Isaac-like 로그라이크 게임의 모든 엔티티(몬스터, 보스, 투사체, 환경 오브젝트)의 그래픽 리소스를 전담 관리하는 클래스.
- 복잡한 스프라이트 시트 처리와 애니메이션 프레임 관리를 체계적으로 구현.
클래스 구조 분석
핵심 멤버 변수
class CEntityMgr {
private:
Direct2DMgr* pD2DMgr; // Direct2D 매니저 참조
vector<ID2D1Bitmap*> tmp_vector; // 임시 비트맵 저장용
vector<ID2D1Bitmap*> m_vMonTears; // 몬스터 투사체 컬렉션
public:
ID2D1Bitmap* GetTearImage(int _idx) { return m_vMonTears[_idx]; }
};
설계 특징:
- 단일 책임 원칙: 엔티티 그래픽만 전담 처리
- 컬렉션 기반 관리: 동적 투사체 관리를 위한 벡터 활용
- Direct2D 통합: 하드웨어 가속 그래픽 처리
주요 기능 분석
1. 시스템 아키텍처
초기화 및 메인 프로세스
void init() {
pD2DMgr = Direct2DMgr::GetInstance();
EntityImageCutting(); // 모든 이미지 처리 통합 실행
}
void EntityImageCutting() {
ProcessRockImages(); // 환경 오브젝트
ProcessFlyImages(); // 일반 몬스터
ProcessHorfImages(); // 특수 몬스터
ProcessTearImages(); // 투사체 시스템
ProcessDeathAnimations(); // 사망 애니메이션
ProcessBossImages(); // 보스 시스템
}
아키텍처 장점:
- 모듈화된 처리: 각 엔티티 타입별 독립적 처리
- 일괄 초기화: 게임 시작 시 모든 리소스 미리 준비
- 확장 가능성: 새로운 엔티티 타입 쉽게 추가
2. 기본 엔티티 처리
환경 오브젝트 (바위)
void ProcessRockImages() {
pD2DMgr->SplitBitmap(pD2DMgr->GetStoredBitmap(L"rocks_basement"),
L"rock", {0, 0}, {32, 32});
}
일반 몬스터 (파리)
void ProcessFlyImages() {
// 기본 상태별 이미지
pD2DMgr->SplitBitmap(source, L"normal_fly", {0, 0}, {64, 32});
pD2DMgr->SplitBitmap(source, L"attack_fly", {0, 32}, {128, 64});
// 사망 애니메이션 (3프레임)
std::vector<ID2D1Bitmap*> flyDeadFrames = {
pD2DMgr->GetStoredBitmap(L"fly_dead_1"),
pD2DMgr->GetStoredBitmap(L"fly_dead_2"),
pD2DMgr->GetStoredBitmap(L"fly_dead_3")
};
// 프레임 결합을 통한 애니메이션 생성
pD2DMgr->StoreCreateMap(CombineBitmapsX(flyDeadFrames), L"fly_dead");
}
3. 투사체 시스템
동적 투사체 관리
void ProcessTearImages() {
// 투사체 기본 이미지 추출
pD2DMgr->SplitBitmap(bulletAtlas, L"monster_normal_tear", {0, 0}, {64, 64});
// 다양한 투사체 타입 생성
m_vMonTears.clear();
int num = 0;
const int maxFramesPerRow[] = {8, 5}; // 행별 최대 프레임 수
for (int i = 0; i < 2; i++) {
for (int j = 0; j < maxFramesPerRow[i]; j++) {
std::wstring tearName = L"tear_" + std::to_wstring(num);
D2D1_POINT_2F startPoint = {32.f * j, 32.f * i};
D2D1_POINT_2F endPoint = {32.f * (j + 1), 32.f * (i + 1)};
pD2DMgr->SplitBitmap(source, tearName, startPoint, endPoint);
m_vMonTears.push_back(pD2DMgr->GetStoredBitmap(tearName));
num++;
}
}
}
투사체 시스템 특징:
- 13가지 투사체 타입: 8개 + 5개 다양한 형태
- 동적 접근: 인덱스 기반 투사체 선택
- 효율적 메모리 관리: 벡터 기반 컬렉션
투사체 폭발 이펙트
const std::vector<std::pair<std::wstring, std::pair<D2D1_POINT_2F, D2D1_POINT_2F>>> tearExplodeFrames = {
{L"monster_normal_tear_explode_1", {{0, 0}, {256, 64}}},
{L"monster_normal_tear_explode_2", {{0, 64}, {256, 128}}},
{L"monster_normal_tear_explode_3", {{0, 128}, {256, 192}}},
{L"monster_normal_tear_explode_4", {{0, 192}, {256, 256}}}
};
// 구조체 기반 일괄 처리
for (const auto& frame : tearExplodeFrames) {
pD2DMgr->SplitBitmap(source, frame.first, frame.second.first, frame.second.second);
explodeFrameBitmaps.push_back(pD2DMgr->GetStoredBitmap(frame.first));
}
4. 복잡한 보스 시스템
Baby Plum 보스 - 다방향 공격 패턴
void ProcessBabyPlumBackBounceImages() {
// 기본 공격 프레임
pD2DMgr->SplitBitmap(source, L"babyplum_attack_backbounce_start", {64*0, 64*6}, {64*2, 64*7});
// 4방향 공격 패턴 처리
// 좌측 방향
std::vector<ID2D1Bitmap*> frontLeftFrames = {
pD2DMgr->GetStoredBitmap(L"babyplum_attack_backbounce_front_left_1"),
pD2DMgr->GetStoredBitmap(L"babyplum_attack_backbounce_front_left_2")
};
// 우측 방향 (좌측 프레임 플립 활용)
std::vector<ID2D1Bitmap*> frontRightFrames = {
FlipBitmap(leftFrame1, true, false), // 수평 플립
FlipBitmap(leftFrame2, true, false)
};
}
보스 시스템 특징:
- 다양한 공격 패턴: Idle, Takedown, Spin, BackBounce
- 방향별 애니메이션: 4방향 공격을 위한 프레임 처리
- 메모리 최적화: 플립 기능으로 프레임 재사용
- 복잡한 사망 이펙트: 대형 폭발 애니메이션
보스 UI 시스템
void ProcessBossHpBar() {
pD2DMgr->SplitBitmap(source, L"boss_hpbar_frame", {0, 32}, {150, 64});
pD2DMgr->SplitBitmap(source, L"boss_hpbar", {0, 0}, {150, 32});
}
기술적 혁신점
1. 메모리 최적화 전략
- 프레임 재사용: FlipBitmap으로 좌우 대칭 프레임 생성
- 동적 컬렉션: 필요한 만큼만 투사체 이미지 로드
- 비트맵 결합: 개별 프레임을 하나의 애니메이션으로 통합
2. 확장성 설계
- 모듈화된 처리 함수: 각 엔티티별 독립적 처리
- 데이터 기반 접근: 하드코딩 최소화
- 일관된 네이밍: 체계적인 리소스 명명 규칙
3. 성능 최적화
- 사전 로딩: 게임 시작 시 모든 리소스 준비
- Direct2D 활용: 하드웨어 가속 그래픽 처리
- 효율적 스프라이트 시트: 메모리 사용량 최적화
플레이어 매니저 ( CPlayerMgr )
아키텍처 분석
클래스 구조
- CPlayerMgr: 싱글톤 패턴을 사용한 플레이어 전담 관리자
- CPlayer: 실제 플레이어 객체
- Direct2DMgr: Direct2D 렌더링 관리자와 협력
주요 책임 분리
// 플레이어 생성 및 설정
void CreateAndSettingPlayer();
// 이미지 처리 및 애니메이션 생성
void SettingImageAndAnimations(int _characterIdx);
// 플레이어 상태 관리
void PlayerHit(float _dmg);
void PlayerHeal(float _value);
스프라이트 처리 시스템
1. 동적 스프라이트 분할
// 원본 스프라이트에서 필요한 부분만 추출
auto createSlicedBitmap = [this](const std::wstring& source, const std::wstring& targetName,
D2D1_POINT_2F topLeft, D2D1_POINT_2F bottomRight) {
pD2DMgr->SplitBitmap(pD2DMgr->GetStoredBitmap(source), targetName, topLeft, bottomRight);
return pD2DMgr->GetStoredBitmap(targetName);
};
2. 스프라이트 결합 시스템
// 여러 프레임을 하나의 애니메이션 스프라이트로 결합
std::vector<ID2D1Bitmap*> walking = {
pD2DMgr->GetStoredBitmap(L"player_idle_walk_1"),
pD2DMgr->GetStoredBitmap(L"player_idle_walk_2")
};
walking_sprite = CombineBitmapsX(walking);
3. 스프라이트 플리핑
// 좌우 대칭 애니메이션을 위한 비트맵 플리핑
left_walking_sprite = FlipBitmap(right_walking_sprite, true, false);
left_attack_sprite = FlipBitmap(right_attack_sprite, true, false);
다중 캐릭터 시스템
캐릭터별 차별화
namespace PlayerConstants {
const std::vector<std::wstring> CHARACTER_ACCESSORIES_FILES = {
L"",
L"character_002_maggiesbeautifulgoldenlocks",
L"character_003_cainseyepatch",
L"character_004_judasfez"
};
const std::vector<Vec2> CHARACTER_ACCESSORIES_SLICE_SIZES = {
Vec2(64.f, 64.f),
Vec2(32.f, 32.f),
Vec2(32.f, 64.f)
};
}
악세사리 시스템
- 기본 캐릭터(Isaac)는 악세사리 없음
- 다른 캐릭터들은 고유한 악세사리 애니메이션 보유
- 4방향 애니메이션 지원 (앞, 뒤, 좌, 우)
애니메이션 시스템
1. 레이어 기반 애니메이션
// 다중 레이어로 애니메이션 재생
animator->Play(PlayerConstants::IDLE_ANIM, true, 1); // 몸체
animator->Play(PlayerConstants::FRONT_IDLE_ANIM, true, 2); // 머리
animator->Play(PlayerConstants::ACCESSORIES_FRONT_IDLE_ANIM, true, 3); // 악세사리
2. 상태별 애니메이션
- 기본 상태: idle, walking (4방향)
- 공격 상태: attack (4방향)
- 아이템 소지: item_get (4방향)
- 특수 상태: hit, die
3. 동적 애니메이션 속도
const float attackAnimSpeed = m_Player->GetPlayerStat().m_fAttackSpd / 2.f;
발사체 시스템
눈물(Tear) 관리
// 13개 프레임의 눈물 애니메이션
for (int i = 0; i < 2; i++) {
int max = (i == 0) ? 8 : 5;
for (int j = 0; j < max; j++) {
// 각 프레임을 개별 비트맵으로 분할
std::wstring tearName = L"tear_" + std::to_wstring(num);
// ... 스프라이트 분할 로직
m_vTears.push_back(pD2DMgr->GetStoredBitmap(tearName));
}
}
폭발 효과
- 4프레임 폭발 애니메이션
- 동적 스프라이트 결합으로 처리
씬 매니저 ( CSceneMgr )
전체 아키텍처 분석
1. CSceneMgr (씬 관리 시스템)
게임의 전체적인 흐름을 관리하는 씬 매니저.
class CSceneMgr {
CScene* m_arrScene[(UINT)SCENE_TYPE::END]; // 모든 씬 보관
CScene* m_pCurScene; // 현재 활성 씬
void ChangeScene(SCENE_TYPE _eNext); // 씬 전환
friend class CEventMgr; // 이벤트 기반 씬 전환
};
주요 설계 특징
- 싱글톤 패턴: 게임 전체에서 하나의 씬 매니저만 존재
- 배열 기반 관리: 모든 씬을 미리 생성하여 빠른 전환 지원
- 이벤트 기반 전환: 즉시 전환이 아닌 이벤트를 통한 안전한 씬 전환
씬 라이프사이클
void ChangeScene(SCENE_TYPE _eNext) {
m_pCurScene->Exit(); // 현재 씬 종료 처리
m_pCurScene = m_arrScene[(UINT)_eNext]; // 씬 포인터 교체
m_pCurScene->Enter(); // 새 씬 진입 처리
}
씬 관리 시스템 심화 분석
1. 메모리 효율적인 씬 관리
void CSceneMgr::init() {
// 모든 씬을 미리 생성 및 초기화
m_arrScene[(UINT)SCENE_TYPE::TEST] = new CScene_Test;
m_arrScene[(UINT)SCENE_TYPE::MAIN] = new CScene_Main;
m_arrScene[(UINT)SCENE_TYPE::FIGHT] = new CScene_Fight;
// 각 씬에 메타데이터 설정
m_arrScene[(UINT)SCENE_TYPE::MAIN]->SetName(L"CScene_Main");
m_arrScene[(UINT)SCENE_TYPE::MAIN]->SetSceneType(SCENE_TYPE::MAIN);
}
2. 안전한 씬 전환 메커니즘
- 지연 전환: 현재 프레임 완료 후 다음 프레임에서 전환
- 이벤트 기반: CEventMgr와 협력하여 안전한 타이밍에 전환
- 생명주기 관리: Exit() → Enter() 순서로 리소스 정리 및 초기화