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() 순서로 리소스 정리 및 초기화