WinApi/Brotato 모작

[Win32 API Brotato 모작] 2. Main & Core & Manager (2)

Vfly 2025. 3. 11. 00:39

이전 포스트에 이어서 CFontMgr, CEventMgr, CCollisionMgr, CSceneMgr, CUIMgr를 살펴보겠다.


<CFontMgr.cpp>

#include "pch.h"
#include "CFontMgr.h"
#include "CPathMgr.h"
#include "CFontCollectionLoader.h"


CFontMgr::CFontMgr()
    : m_pDWriteFactory(nullptr)
{
    DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&m_pDWriteFactory));
}

CFontMgr::~CFontMgr()
{
    delete[] m_fontFamilies;

    for (auto fontCollection : m_fontCollections) {
        if (fontCollection) fontCollection->Release();
    }
    if (m_pDWriteFactory) m_pDWriteFactory->Release();
}

FontFamily* CFontMgr::GetFont(FONT_TYPE _eType)
{
    if ((UINT)_eType < m_iFontCount) {
        return &m_fontFamilies[(UINT)_eType];
    }
    return nullptr; // 잘못된 요청에 대해 nullptr 반환
}

void CFontMgr::init()
{
    wstring strContentPath = CPathMgr::GetInstance()->GetContentPath();

    // 폰트 경로 설정
    m_fontPaths = {
        strContentPath + L"font\\Anybody-Medium.ttf",
        strContentPath + L"font\\NotoSansKR-Medium.ttf"
    };

    // Font Collection Loader 등록
    auto loader = new CFontCollectionLoader(m_fontPaths);

    HRESULT hr = m_pDWriteFactory->RegisterFontCollectionLoader(loader);

    if (SUCCEEDED(hr)) {
        for (UINT32 i = 0; i < m_fontPaths.size(); ++i) {
            IDWriteFontCollection* pCollection = nullptr;

            hr = m_pDWriteFactory->CreateCustomFontCollection(
                loader, &i, sizeof(i), &pCollection
            );

            if (SUCCEEDED(hr)) {
                m_fontCollections.push_back(pCollection);
            }
        }

        loader->Release();
        m_pDWriteFactory->UnregisterFontCollectionLoader(loader);
    }
}

IDWriteTextFormat* CFontMgr::GetTextFormat(FONT_TYPE type, float fontSize, const std::wstring& locale)
{
    if ((size_t)type >= m_fontCollections.size()) return nullptr;

    IDWriteTextFormat* pTextFormat = nullptr;
    HRESULT hr = m_pDWriteFactory->CreateTextFormat(
        m_fontPaths[(size_t)type].c_str(), // 폰트 이름
        m_fontCollections[(size_t)type],  // 폰트 컬렉션
        DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL,
        fontSize,
        locale.c_str(),
        &pTextFormat
    );
   
    return SUCCEEDED(hr) ? pTextFormat : nullptr;
}

 

<CFontCollectionLoader.cpp>

#include "pch.h"
#include "CFontCollectionLoader.h"
#include "CFontFileEnumerator.h"

HRESULT CFontCollectionLoader::CreateEnumeratorFromKey(
    IDWriteFactory* factory,
    const void* collectionKey,
    UINT32 collectionKeySize,
    IDWriteFontFileEnumerator** fontFileEnumerator
) {
    if (!factory || !fontFileEnumerator || !collectionKey || collectionKeySize != sizeof(UINT32)) {
        return E_INVALIDARG;
    }

    UINT32 index = *reinterpret_cast<const UINT32*>(collectionKey);
    if (index >= m_fontPaths.size()) {
        return E_FAIL;
    }

    *fontFileEnumerator = new CFontFileEnumerator(factory, m_fontPaths[index]);
    (*fontFileEnumerator)->AddRef();

    return S_OK;
}

 

<CFontFileEnumerator.cpp, CFontFileEnumerator.h>

#pragma once

#include <dwrite.h>
#include <string>

class CFontFileEnumerator : public IDWriteFontFileEnumerator {
private:
    ULONG m_refCount;
    IDWriteFactory* m_factory;
    std::wstring m_fontPath;
    IDWriteFontFile* m_currentFile;
    bool m_hasEnumerated; // 폰트 파일이 이미 열거되었는지 여부

public:
    CFontFileEnumerator(IDWriteFactory* factory, const std::wstring& fontPath)
        : m_refCount(1), m_factory(factory), m_fontPath(fontPath), m_currentFile(nullptr), m_hasEnumerated(false) {}

    ~CFontFileEnumerator() {
        if (m_currentFile) {
            m_currentFile->Release();
        }
    }

    HRESULT STDMETHODCALLTYPE MoveNext(BOOL* hasCurrentFile) override;

    HRESULT STDMETHODCALLTYPE GetCurrentFontFile(IDWriteFontFile** fontFile) override;

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override {
        if (riid == __uuidof(IDWriteFontFileEnumerator) || riid == __uuidof(IUnknown)) {
            *ppvObject = this;
            AddRef();
            return S_OK;
        }
        *ppvObject = nullptr;
        return E_NOINTERFACE;
    }

    ULONG STDMETHODCALLTYPE AddRef() override { return ++m_refCount; }
    ULONG STDMETHODCALLTYPE Release() override {
        ULONG newCount = --m_refCount;
        if (newCount == 0) delete this;
        return newCount;
    }
};


#include "CFontFileEnumerator.h"

HRESULT CFontFileEnumerator::MoveNext(BOOL* hasCurrentFile) {
    if (!hasCurrentFile) return E_INVALIDARG;

    // 이미 열거가 완료되었으면 FALSE 반환
    if (m_hasEnumerated) {
        *hasCurrentFile = FALSE;
        return S_OK;
    }

    // 폰트 파일 참조 생성
    HRESULT hr = m_factory->CreateFontFileReference(
        m_fontPath.c_str(),
        nullptr, // 마지막 수정 시간 (nullptr 사용)
        &m_currentFile
    );

    if (SUCCEEDED(hr)) {
        *hasCurrentFile = TRUE;
        m_hasEnumerated = true; // 열거 완료 상태로 설정
    }
    else {
        *hasCurrentFile = FALSE;
    }

    return hr;
}

HRESULT CFontFileEnumerator::GetCurrentFontFile(IDWriteFontFile** fontFile) {
    if (!fontFile || !m_currentFile) return E_FAIL;

    *fontFile = m_currentFile;
    (*fontFile)->AddRef(); // 참조 카운트 증가

    return S_OK;
}

 

이 3개의 클래스는 DirectWrite를 사용해 .ttf(TrueTypeFont) 폰트 파일을 불러오고, 커스텀 폰트 컬렉션을 관리하는 클래스들이다.

 

전체 구조 요약

  1. CFontMgr : 폰트 관리 클래스. 폰트를 로드하고 , 텍스트 형식을 생성한다.
  2. CFontCollectionLoader : DirectWrite에서 커스텀 폰트 컬렉션을 로드하는 역할을 한다.
  3. CFontFileEnumerator : 커스텀 폰트 파일을 열거(Enumerator)하는 클래스이다. 

CFontMgr

  • 폰트 경로 설정 :
    • CPathMgr를 사용하여 콘텐츠 경로를 가져온다.
    • 두개의 .ttf 파일 경로를 m_fontPaths 벡터 저장.
  • 커스텀 폰트 컬렉션 로더 등록 :
    • CFontCollectionLoader 객체를 생성하고 , DirectWrite 팩토리에 등록한다.
    • 이 로더는 커스텀 폰트 컬렉션을 처리할 수 있도록 한다.
  • 폰트 컬렉션 생성:
    • 각 폰트 파일에 대해 CreateCustomFontCollection() 을 호출하여 커스텀 폰트 컬렉션을 생성한다.
    • 생성된 컬렉션은 m_fontCollection 벡터에 저장된다.
  • GetTextFormat() 함수 (텍스트 형식 가져오기)
IDWriteTextFormat* CFontMgr::GetTextFormat(FONT_TYPE type, float fontSize, const std::wstring& locale)
{
    if ((size_t)type >= m_fontCollections.size()) return nullptr;

    IDWriteTextFormat* pTextFormat = nullptr;
    HRESULT hr = m_pDWriteFactory->CreateTextFormat(
        m_fontPaths[(size_t)type].c_str(), // 폰트 이름
        m_fontCollections[(size_t)type],  // 폰트 컬렉션
        DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL,
        fontSize,
        locale.c_str(),
        &pTextFormat
    );
   
    return SUCCEEDED(hr) ? pTextFormat : nullptr;
}
  • 입력 매개변수 :
    • FONT_TYPE type : 사용할 폰트를 지정한다.
    • float fontSize : 텍스트 크기를 지정한다.
    • std::wstring locale : 지역화 정보(ex : "en-us" , "ko-kr") 를 지정한다.
  • 텍스트 형식 생성
    • CreateTextFormat() 함수를 호출하여 텍스트 형식을 생성한다.
// 폰트 생성
IDWriteTextFormat* pTextFormat = CFontMgr::GetInstance()->GetTextFormat(fontType, fontSize);
if (!pTextFormat) return;

다음과 같은 방식으로 사용한다.


<CCollisionMgr.h>

#pragma once

class CCollider;

union COOLIDER_ID {
	struct {
		UINT Left_id;
		UINT Right_id;
	};
	ULONGLONG ID;
};

class CCollisionMgr
{
	SINGLE(CCollisionMgr)
private:
	unordered_map<ULONGLONG, bool>	m_mapColInfo;	//충돌체 간의 이전 프레임 충돌 정보. 
	UINT m_arrCheck[(UINT)GROUP_TYPE::END];			//그룹간의 충돌 체크 매트릭스
public:
	void update();
	void CheckGroup(GROUP_TYPE eLeft, GROUP_TYPE e_Right);
	void Reset() {
		memset(m_arrCheck, 0, sizeof(UINT) * (UINT)GROUP_TYPE::END);
		m_mapColInfo.clear();
	}
private:
	void CollisionGroupUpdate(GROUP_TYPE _eLeft, GROUP_TYPE _eRight);
	bool IsCollision(CCollider* _pLeftCol, CCollider* _pRightCol);
};

 

 

<CCollisionMgr.h>

#include "pch.h"
#include "CCollisionMgr.h"
#include "CSceneMgr.h"
#include "CScene.h"
#include "CObject.h"
#include "CCollider.h"

CCollisionMgr::CCollisionMgr() 
	:m_arrCheck{}
{
	

}

CCollisionMgr::~CCollisionMgr() 
{

}

void CCollisionMgr::update()
{
	if (CScene::GetPause()) return;
	//표를 확인하면서, 비트값이 ON되어 있다면 충돌하고,
	//OFF라면 충돌하지 않음.

	for (UINT iRow = 0; iRow < (UINT)GROUP_TYPE::END; iRow++) {
		for (UINT iCol = iRow; iCol < (UINT)GROUP_TYPE::END; iCol++) {
			if (m_arrCheck[iRow] & (1 << iCol)) {
				//해당 그룹간의 충돌을 진행한다. 
				//따라서 현재 Scene에서 충돌체를 검사한다. 

				CollisionGroupUpdate((GROUP_TYPE)iRow, (GROUP_TYPE)iCol);
			}
		}
	}
}

void CCollisionMgr::CollisionGroupUpdate(GROUP_TYPE _eLeft, GROUP_TYPE _eRight)
{
	CScene* pCurScene = CSceneMgr::GetInstance()->GetCurScene();

	const vector<CObject*>& vecLeft = pCurScene->GetGroupObject(_eLeft);
	const vector<CObject*>& vecRight = pCurScene->GetGroupObject(_eRight);

	unordered_map<ULONGLONG, bool>::iterator colliderMapIter;

	for (auto leftIDX = 0; leftIDX < vecLeft.size(); leftIDX++) {
		//충돌체를 보유하지 않는 경우 
		if (nullptr == vecLeft[leftIDX]->GetCollider()) continue;

		for (auto rightIDX = 0; rightIDX < vecRight.size(); rightIDX++) { 
			//충돌체를 보유하지 않는 경우. or 자기 자신과의 충돌인 경우. 
			if (nullptr == vecRight[rightIDX]->GetCollider() || vecLeft[leftIDX] == vecRight[rightIDX]) continue;


			//둘 중 하나라도 충돌 판정이 false일 경우 continue
			if (false == vecRight[rightIDX]->GetCollider()->GetActive() || false == vecLeft[leftIDX]->GetCollider()->GetActive()) continue;


			CCollider* pLeftCollider = vecLeft[leftIDX]->GetCollider();
			CCollider* pRightCollider = vecRight[rightIDX]->GetCollider();


			//충돌체들 간의 조합 ID
			COOLIDER_ID ID;

			ID.Left_id = pLeftCollider->GetID();
			ID.Right_id = pRightCollider->GetID();

			colliderMapIter = m_mapColInfo.find(ID.ID);

			//충돌 정보가 미 등록 상태인 경우 등록(충돌하지 않았다. 로)
			if (colliderMapIter == m_mapColInfo.end()) {
				//두 조합의 검사를 최초로 했을 경우. 
				m_mapColInfo.insert(make_pair(ID.ID, false));
				colliderMapIter = m_mapColInfo.find(ID.ID);
			}

			//현재 충돌한 경우. 
			if (IsCollision(pLeftCollider, pRightCollider)) {
				if (colliderMapIter->second == false) {
					//이번 프레임에 막 충돌한 경우.
					
					//하필 이제 삭제될 때, 충돌할 때. 그 충돌은 없었던 것이 됨.
					if (!vecLeft[leftIDX]->IsDead() && !vecRight[rightIDX]->IsDead()) {
						pLeftCollider->OnCollisionEnter(vecRight[rightIDX]->GetCollider());
						pRightCollider->OnCollisionEnter(vecLeft[leftIDX]->GetCollider());
						colliderMapIter->second = true;
					}
				}
				else if (colliderMapIter->second == true) {
					//이전 프레임에도 충돌하고 있었을 경우. 

					//이제 죽는 애는 충돌에서 벗어났다고 명시적으로 해줌. 
					if (vecLeft[leftIDX]->IsDead() || vecRight[rightIDX]->IsDead()) {
						pLeftCollider->OnCollisionExit(vecRight[rightIDX]->GetCollider());
						pRightCollider->OnCollisionExit(vecLeft[leftIDX]->GetCollider());
						colliderMapIter->second = false;
					}
					else {
						pLeftCollider->OnCollision(vecRight[rightIDX]->GetCollider());
						pRightCollider->OnCollision(vecLeft[leftIDX]->GetCollider());
					}

				}

			}
			else {
				//충돌하지 않은 경우. 
				if (colliderMapIter->second == true) {
					//막 지금 충돌에서 벗어난 경우.
					pLeftCollider->OnCollisionExit(vecRight[rightIDX]->GetCollider());
					pRightCollider->OnCollisionExit(vecLeft[leftIDX]->GetCollider());
					colliderMapIter->second = false;
				}

			}
		}
	}
}

bool CCollisionMgr::IsCollision(CCollider* _pLeftCol, CCollider* _pRightCol)
{
	Vec2 vLeftPos = _pLeftCol->GetFinalPos();
	Vec2 vLeftScale = _pLeftCol->GetScale();

	Vec2 vRightPos = _pRightCol->GetFinalPos();
	Vec2 vRightScale = _pRightCol->GetScale();

	if ((abs(vRightPos.x - vLeftPos.x) <= (vLeftScale.x + vRightScale.x) / 2.f) &&
		(abs(vRightPos.y - vLeftPos.y) <= (vLeftScale.y + vRightScale.y) / 2.f)) {

		return true;
	}

	return false;
}

void CCollisionMgr::CheckGroup(GROUP_TYPE _eLeft, GROUP_TYPE _eRight)
{
	//그룹값 중에서 더 작은 타입을 행으로, 더 큰 값을 열(비트)로 사용. 
	UINT iRow = (UINT)_eLeft;
	UINT iCol = (UINT)_eRight;

	if (iCol < iRow) {
		iRow = (UINT)_eRight;
		iCol = (UINT)_eLeft;
	}

	if (m_arrCheck[iRow] & (1 << iCol)) {
		m_arrCheck[iRow] &= ~(1 << iCol);
	}
	else {
		m_arrCheck[iRow] |= (1 << iCol);
	}
}

 

이 클래스는 여러 객체들 간의 충돌을 감지하고 처리하는 역할을 한다.

 

update()함수에서는 게임이 Pause상태가 아니라면 충돌 검사를 진행한다.

 

이때 객체 그룹들 간의 충돌체크는 비트마스킹으로 구현하였다. 멤버 변수 m_arrCheckUINT 타입으로 만들었기 때문에 그룹의 총 갯수는 32개가 된다.

 

CheckGroup()에서 들어온 두 인자는 "두 그룹의 충돌체크를 진행해주세요" 라는 의미를 가진다.

 

충돌 체크 및 처리

  • 각 그룹의 객체 간의 충돌의 체크하고, 충돌이 발생하면 IsCollision() 함수를 호출하여 충돌 여부를 확인한다.
  • 충돌이 발생한 경우, 이전 프레임의 충돌 상태에 따라 OnCollisionEnter(), OnCollision(), OnCollisionExit() 함수를 호출해준다.

CScene에서 다음과 같은 코드를 작성해주면 충돌검사를 진행하게 된다.

//이제 새로운 충돌이 발생할수도 있음. 
CCollisionMgr::GetInstance()->CheckGroup(GROUP_TYPE::PLAYER, GROUP_TYPE::MONSTER);
CCollisionMgr::GetInstance()->CheckGroup(GROUP_TYPE::WEAPON, GROUP_TYPE::MONSTER);
CCollisionMgr::GetInstance()->CheckGroup(GROUP_TYPE::PROJ_PLAYER, GROUP_TYPE::MONSTER);
CCollisionMgr::GetInstance()->CheckGroup(GROUP_TYPE::GROUND, GROUP_TYPE::PLAYER);
CCollisionMgr::GetInstance()->CheckGroup(GROUP_TYPE::DROP_ITEM, GROUP_TYPE::PLAYER);

<CEventMgr.cpp, CEventMgr.h>

#pragma once


struct tEvent {
	EVENT_TYPE		eEven;
	DWORD_PTR		lParam;
	DWORD_PTR		wParam;
};

class CEventMgr
{
	SINGLE(CEventMgr)

private:
	vector<tEvent> m_vecEvent;
	unordered_set<CObject*> m_setDeadScheduled;		//삭제 예정인 오브젝트들. 
public:
	void update();

private:
	void Excute(const tEvent& _eve);

public:
	void AddEvent(const tEvent& _eve) {
		m_vecEvent.push_back(_eve);
	}
};



#include "pch.h"
#include "CEventMgr.h"

#include "CObject.h"
#include "CSceneMgr.h"
#include "CUIMgr.h"
#include "CScene.h"
#include "AI.h"
#include "CState.h"

CEventMgr::CEventMgr() 
	: m_vecEvent{}
{

}

CEventMgr::~CEventMgr() {


}

void CEventMgr::update()
{
	//==================================================
	//이전 프레임에서 등록해준 Dead Object들을 삭제한다.
	//==================================================
	
	for (auto deadObjectPtr  : m_setDeadScheduled) {
		delete deadObjectPtr;
	}
	
	m_setDeadScheduled.clear();
	///////
	//  ===============
	//    Event 처리.
	//  ===============
	for (const auto& event_node : m_vecEvent) {
		Excute(event_node);
	}

	m_vecEvent.clear();
}

void CEventMgr::Excute(const tEvent& _eve)
{
	switch (_eve.eEven) {
	case EVENT_TYPE::CREATE_OBJECT:
	{
		// lParam : Object Address
		// wParam : Group Type
		CObject* pNewObj = (CObject*)_eve.lParam;
		GROUP_TYPE eType = (GROUP_TYPE)_eve.wParam;
		
		if (eType == GROUP_TYPE::WEAPON) {
			int a = 0;
		}

		CSceneMgr::GetInstance()->GetCurScene()->AddObject(pNewObj, eType);
	}
		break;
	case EVENT_TYPE::DELETE_OBJECT:
	{
		// lParam : Object Address
		// wParam : NULL
		// Object를 Dead 상태로 변경하고 -> 삭제 예정 오브젝트들을 모아둔다.
		CObject* pDeleteObj = (CObject*)_eve.lParam;
		pDeleteObj->SetDead();
		m_setDeadScheduled.insert(pDeleteObj);
	}
		break;
	case EVENT_TYPE::SCENE_CHANGE:
	{
		// lParam : Next Scene Type
		// wParam : NULL
		CSceneMgr::GetInstance()->ChangeScene((SCENE_TYPE)_eve.lParam);

		//이전 Scene에 대한 UI를 가리키고 있었기에 그거 해제. 
		CUIMgr::GetInstance()->SetFocusedUI(nullptr);
	}
		break;

	case EVENT_TYPE::CHANGE_AI_STATE:
	{
		// lParam : AI PTR
		// wParam : Next Type
		AI* pAI = (AI*)_eve.lParam;
		MON_STATE eNextState = (MON_STATE)_eve.wParam;
		pAI->ChangeState(eNextState);
	}
		break;
	}
}

 

이 클래스는 한 프레임동안 일어난 여러 Event들을 처리해주는 클래스이다.

 

EventMgr를 사용하는 이유는 다음과 같다.

 

한 프레임내에서 객체의 삭제나, 객체의 상태가 변하거나, 객체가 추가되거나 와 같은 여러 Event들이 발생할 것이다. 만약 이런 Event들이 Event가 발생한 그 순간에 객체를 삭제하거나, 상태를 변경하거나, 추가하는 행위가 일어나면 이후에 있을 상황에 매우 큰 영향을 끼칠 수 있다.

 

예를들어 씬(화면)을 전환하다고 생각해보자, 화면 전환이 발생하면 새로운 객체들을 다시 그려야하고 기존 씬에 있던 객체들을 삭제를 진행시켜야한다. 하지만 씬을 바로 바꿔버리면 기존에 있던 객체들은 화면상에 그대로 남아있고 새로운 씬에 객체가 그려지는 상황이 발생할 수 있다.

 

따라서 특정 Event들은 한 프레임 내에서 발생은 했지만 적용은 다음 프레임에 해주거나 마무리를 할 수 있는 시간을 주고 Event를 적용시켜주어야 일관성이 있게 될 것이다.

 

따라서 작성한 코드는 객체 생성, 객체 삭제, 화면(씬) 변경, 객체 상태 변경을 프레임이 끝난뒤에 처리해주고, 프레임내에서 발생한 객체들도 한꺼번에 delete해주는 방식으로 작성하였다.

 

이때 m_setDeadScheduled변수에 저장되는 객체들이 같은 객체지만 여러번 들어오는 경우는 방지하기 위해 

unordered_set<CObject*> m_setDeadScheduled 자료구조를 사용하였다.


<CSceneMgr.cpp , CSceneMgr.h>

#pragma once

class CScene;
class CObject;

class CSceneMgr
{
	SINGLE(CSceneMgr)

private:
	CObject* m_pPlayer;		//Player

private:
	CScene* m_arrScene[(UINT)SCENE_TYPE::END];	//모든 씬에 대한 정보를 다 가짐
	CScene* m_pCurScene;						//현재 씬 

public:
	void RegisterPlayer(CObject* _player) { m_pPlayer = _player; }
	CObject* GetPlayer() { return m_pPlayer; }

public:
	void init();
	void update();
	void render(ID2D1HwndRenderTarget* _pRender);
	bool IsWaveScene() {
		if (m_pCurScene == m_arrScene[(UINT)SCENE_TYPE::START]) return true;
		return false;
	}
public:
	CScene* GetCurScene() { return m_pCurScene; }
	CScene* GetScene(SCENE_TYPE _eType) { return m_arrScene[(UINT)_eType]; }

private:
	void ChangeScene(SCENE_TYPE _eNext);

	friend class CEventMgr;
	friend class CScene_Select_Character;
};


#include "pch.h"
#include "CSceneMgr.h"
#include "CScene.h"
#include "CScene_Main.h"
#include "CScene_Start.h"
#include "CScene_Tool.h"
#include "CScene_Test.h"
#include "CScene_Select_Character.h"
#include "CScene_Select_Weapon.h"
#include "CScene_Run_End.h"
#include "CScene_Shop.h"
#include "CEventMgr.h"
#include "CWaveMgr.h"

CSceneMgr::CSceneMgr()
	: m_pCurScene(nullptr)
	, m_arrScene{}
{

}

CSceneMgr::~CSceneMgr() {
	for (UINT sceneIDX = 0; sceneIDX < (UINT)SCENE_TYPE::END; sceneIDX++) {
		if (m_arrScene[sceneIDX] != nullptr) {
			delete m_arrScene[sceneIDX];
		}
	}
}

void CSceneMgr::init()
{
	//Scene 생성
	m_arrScene[(UINT)SCENE_TYPE::MAIN] = new CScene_Main;
	m_arrScene[(UINT)SCENE_TYPE::MAIN]->SetName(L"Main Title Scene");
	m_arrScene[(UINT)SCENE_TYPE::MAIN]->SetSceneType(SCENE_TYPE::MAIN);

	m_arrScene[(UINT)SCENE_TYPE::START] = new CScene_Start;
	m_arrScene[(UINT)SCENE_TYPE::START]->SetName(L"Start Scene");
	m_arrScene[(UINT)SCENE_TYPE::START]->SetSceneType(SCENE_TYPE::START);

	m_arrScene[(UINT)SCENE_TYPE::TOOL] = new CScene_Tool;
	m_arrScene[(UINT)SCENE_TYPE::TOOL]->SetName(L"Tool Scene");
	m_arrScene[(UINT)SCENE_TYPE::TOOL]->SetSceneType(SCENE_TYPE::TOOL);

	m_arrScene[(UINT)SCENE_TYPE::TEST] = new CScene_Test;
	m_arrScene[(UINT)SCENE_TYPE::TEST]->SetName(L"Test Scene");
	m_arrScene[(UINT)SCENE_TYPE::TEST]->SetSceneType(SCENE_TYPE::TEST);

	m_arrScene[(UINT)SCENE_TYPE::SELECT_CHARACTER] = new CScene_Select_Character;
	m_arrScene[(UINT)SCENE_TYPE::SELECT_CHARACTER]->SetName(L"Select Character Scene");
	m_arrScene[(UINT)SCENE_TYPE::SELECT_CHARACTER]->SetSceneType(SCENE_TYPE::SELECT_CHARACTER);

	m_arrScene[(UINT)SCENE_TYPE::SELECT_WEAPON] = new CScene_Select_Weapon;
	m_arrScene[(UINT)SCENE_TYPE::SELECT_WEAPON]->SetName(L"Select Weapon Scene");
	m_arrScene[(UINT)SCENE_TYPE::SELECT_WEAPON]->SetSceneType(SCENE_TYPE::SELECT_WEAPON);

	m_arrScene[(UINT)SCENE_TYPE::SHOP] = new CScene_Shop;
	m_arrScene[(UINT)SCENE_TYPE::SHOP]->SetName(L"Shop Scene");
	m_arrScene[(UINT)SCENE_TYPE::SHOP]->SetSceneType(SCENE_TYPE::SHOP);

	m_arrScene[(UINT)SCENE_TYPE::RUN_END] = new CScene_Run_End;
	m_arrScene[(UINT)SCENE_TYPE::RUN_END]->SetName(L"Run End");
	m_arrScene[(UINT)SCENE_TYPE::RUN_END]->SetSceneType(SCENE_TYPE::RUN_END);

	//m_arrScene[(UINT)SCENE_TYPE::STAGE_01] = new CScene_Stage01;
	//m_arrScene[(UINT)SCENE_TYPE::STAGE_02] = new CScene_Stage02;

	//웨이브 세팅
	CWaveMgr::GetInstance()->WaveInit();

	//현재 씬 설정
	m_pCurScene = m_arrScene[(UINT)SCENE_TYPE::MAIN];

	m_pCurScene->Enter();
}

void CSceneMgr::update()
{
	m_pCurScene->update();
	m_pCurScene->finalupdate();
	CWaveMgr::GetInstance()->update();
}
void CSceneMgr::render(ID2D1HwndRenderTarget* _pRender)
{
	m_pCurScene->render(_pRender);
}

//씬을 바꿀 때, 기존 씬에 있던 포인터나 그런 거. 
//한 프레임은 작업을 마무리하고, Scene을 업데이트 해야만 함. 
//여기서 해줄 건 이벤트 등록. 다음 프레임부터 다음 씬으로 가도록 이벤트를 넣어준다. 
void CSceneMgr::ChangeScene(SCENE_TYPE _eNext)
{
	m_pCurScene->Exit();
	m_pCurScene = m_arrScene[(UINT)_eNext];
	m_pCurScene->Enter();
}

 

CSceneMgr에서는 다음과 같은 작업들을 수행한다.

  1. 씬(Scene) 생성 : 게임에서 필요한 여러씬들을 생성해 두고, ChangeScene()함수를 이용해 씬들을 변경하여 화면에 보여준다.
  2. update(), final_update(), render() : 현재 씬의 update(), final_update(), render() 함수들을 실행시켜준다.
  3. 씬(Scene) 변경 : 씬 변경이 일어나면 기존 씬의 탈출함수(객체의 삭제 등등)을 실행해주고 새로운 씬의 진입함수(객체 생성)을 실행시켜준다.
  4. Player객체 저장 : 캐릭터 선택 화면 (CScene_Select_Character)에서 생성된 Player객체를 CSceneMgr가 가지고 있게 만들어준다. 왜냐하면 Player객체는 이 게임내에서 무조건 한개밖에 없기 때문에 어떤 씬에서든 접근이 가능하도록 CSceneMgr쪽에서 객체에 대한 포인터를 가지고 있게 한다.

<CUIMgr.cpp , CUIMgr.h>

#pragma once

class CUI;
class CUIMgr
{
	SINGLE(CUIMgr)

private:
	CUI*		m_pFocusedUI;

public:
	void update();

	//해당 ui는 활성화 되면서 자신을 보라고 쏴줌. 
	void SetFocusedUI(CUI* _pUI);

private:
	CUI* GetFocusedUI();						//현재 포커싱 된 ui
	CUI* GetTargetedUI(CUI* _pParentUI);		//부모 UI내에서 실제로 타겟팅 된 ui를 찾아서 반환한다. 
};

#include "pch.h"
#include "CUIMgr.h"
#include "CCore.h"
#include "CObject.h"
#include "CPanelUI.h"
#include "CSceneMgr.h"
#include "CScene.h"
#include "CUI.h"

#include "CkeyMgr.h"

CUIMgr::CUIMgr() 
	: m_pFocusedUI(nullptr)
{

}

CUIMgr::~CUIMgr() {

}

void CUIMgr::update()
{
	bool bLbtnTap = KEY_TAP(KEY::LBTN);
	bool bLbtnAway = KEY_AWAY(KEY::LBTN);
	// 1. FocusedUI 확인
	m_pFocusedUI = GetFocusedUI();

	if (!m_pFocusedUI) return;

	// 2. FocusedUI 내에서 부모 UI 포함, 자식 UI들 중 실제 타겟팅 된 UI를 가져온다. 
	CUI* pTargetUI = GetTargetedUI(m_pFocusedUI);

	if (nullptr != pTargetUI) {
		pTargetUI->MouseOn();

		if (bLbtnTap) {//눌리기까지 함. 
			pTargetUI->MouseLbtnDown();
			pTargetUI->m_bLbtnDown = true;
		}
		else if (bLbtnAway)
		{
			//이번에 안 눌렀는데, 해당 위에 있다.
			pTargetUI->MouseLbtnUp();
			if (pTargetUI->m_bLbtnDown) {
				pTargetUI->MouseLbtnClicked();
			}
			//왼쪽 버튼 떼면 눌렸던 체크를 다시 해제한다. 
			pTargetUI->m_bLbtnDown = false;
		}
	}	
}


//특정 키 눌렸을 때 어떤 UI를 보여줘야 한다. 
void CUIMgr::SetFocusedUI(CUI* _pUI)
{
	//이미 포커스 된 거나, nullptr값이 들어올 경우에. 
	if (m_pFocusedUI == _pUI || nullptr == _pUI) {
		m_pFocusedUI = _pUI;
		return;
	}

	m_pFocusedUI = _pUI;

	CScene* pCurScene = CSceneMgr::GetInstance()->GetCurScene();
	vector<CObject*>& vecUI = pCurScene->GetUIGroup();

	vector<CObject*>::iterator CUIIter = vecUI.begin();
	//왼쪽 버튼 TAP이 발생했다는 전제가 있는 UI들. 
	for (; CUIIter != vecUI.end(); CUIIter++) {
		if (m_pFocusedUI == *CUIIter) {
			break;
		}
	}

	vecUI.erase(CUIIter);
	vecUI.push_back(m_pFocusedUI);
}

CUI* CUIMgr::GetFocusedUI()
{
	CScene* pCurScene = CSceneMgr::GetInstance()->GetCurScene();
	vector<CObject*>& vecUI = pCurScene->GetUIGroup();

	bool bLbtnTap = KEY_TAP(KEY::LBTN);

	//기존 포커싱 UI를 받아두고 변경되었는지 확인한다. 
	CUI* pFocusedUI = m_pFocusedUI;

	//왼쪽 클릭이 없다면, Focus가 전환될 이유도 없기에 그냥 리턴. 
	if (!bLbtnTap) return pFocusedUI;

	vector<CObject*>::iterator targetIter = vecUI.end();
	vector<CObject*>::iterator CUIIter = vecUI.begin();
	//왼쪽 버튼 TAP이 발생했다는 전제가 있는 UI들. 
	for (; CUIIter != vecUI.end(); CUIIter++) {
		if (((CUI*)*CUIIter)->IsMouseOn()) {
			targetIter = CUIIter;
		}
	}

	//마우스가 눌리긴 했는데 허공을 눌렀을 경우, 받아갈 놈이 없음.
	if (vecUI.end() == targetIter) {
		return nullptr;
	}

	pFocusedUI = (CUI*)*targetIter;

	//계속 위에서 교체되다가 보면, 최종적인 Focus를 가져가는 UI가 된다. 
	//그리고 벡터 내부에서 순서를 바꿔줘야함. 
	vecUI.erase(targetIter);
	vecUI.push_back(pFocusedUI);

	return pFocusedUI;
}

CUI* CUIMgr::GetTargetedUI(CUI* _pParentUI)
{
	//1. 부모 UI포함, 모든 자식 UI를 검사. 그 안에서 가장 우선순위 높은 건 가장 아래 자식.(자식 아래까지 내려가야함)

	//2. 여러 UI가 타겟중일때, 우선 순위가 제일 높은 건 아래 자식. 
	//3. UI가 겹쳐있다면? <- UI 배치부터가 잘못되었다. 

	//이런 트리 구조 순회는 Level 순회라고 한다. -> 재귀함수 방식으로 하면 부모 -> 자식 -> 더 자식 순서대로. 
	//우리가 원하는 것은 더 깊게가 아닌, 같은 계층의 UI는 같이 검사. 
	//따라서 Queue를 활용한다. 
	//0(부모)에서 1,2(자식)넣고, 1에서 3,4,5.. 넣고.. 이러면 된다. 
	bool bLbtnAway = KEY_AWAY(KEY::LBTN);

	CUI* pTargetUI = nullptr;

	//계속 함수 올 때마다 할당하는 건 낭비.
	static list<CUI*> levelQueue;
	static vector<CUI*> vecNoneTarget;

	//그냥 비워준다.
	levelQueue.clear();
	vecNoneTarget.clear();

	levelQueue.push_back(_pParentUI);

	while (!levelQueue.empty()) {
		CUI* pUI = levelQueue.front();

		levelQueue.pop_front();

		// 큐에서 꺼내온 UI가 TargetUI인지 확인.
		if (pUI->IsMouseOn()) {
			if (nullptr != pTargetUI) {
				vecNoneTarget.push_back(pTargetUI);
			}

			pTargetUI = pUI;
		}
		else {
			vecNoneTarget.push_back(pUI);
		}

		const vector<CUI*>& vecChild = pUI->GetChildsUI();
		for (auto& child : vecChild) {
			levelQueue.push_back(child);
		}
	}
	if (bLbtnAway) {
		for (auto& noneChild : vecNoneTarget) {
			noneChild->m_bLbtnDown = false;
		}
	}
	return pTargetUI;
}

 

CUIMgr에서는 크게 2가지 작업을 수행한다.

  1. 현재 포커싱된 UI를 찾아준다.
  2. 포커싱된 UI , 포커싱이 안되어있는 UI의 마우스 상태값들을 처리해준다.

포커싱된 UI라는 뜻은 현재 그 객체위에 최소한 Mouse가 올려져있는 상태이거나 마우스 클릭이 발생한 UI라는 뜻이다.

 

그리고 추가적으로 포커싱된 UI는 화면상에서 가장 앞에 그려져야 할것이다.

따라서 GetFocusedUI() 함수에서는 객체들이 저장된 벡터에서 객체들의 순서를 바꾸어 포커싱된 UI가 가장 나중에 그려지게 만들어주는 작업을 수행한다.

 

또한 계층구조를 가지는 UI의 경우, 예를들어 일반적인 게임에서 아이템창이 열렸다고 가정해보면 뒷배경과 축소,확대,닫기 버튼 등 여러 다양한 UI들이 있을 것이다, 이런경우 계층 구조 내에서도 포커싱된 UI가 따로 존재할 것이다. 그 경우 Level순회를 돌아서 최종적으로 포커싱된 UI를 찾는 함수가 GetTargetedUI()함수이다.

 

 

 

다음 포스트에서 마지막으로 Direct2DMgr에 대해서 설명하고 SingleTon 패턴으로 구현된 Manager들에 대한 설명을 마치도록 하겠다.