WinApi/Brotato 모작

[Win32 API Brotato 모작] 5. Object

Vfly 2025. 3. 12. 04:31

이번에는 이 프로그램에서 그려질 모든 객체의 부모 클래스이자 추상 클래스인 CObject 클래스에 대해 설명하겠다.

 

CObject를 상속받는 자식 클래스들은 다음과 같다.

  • CDamagedUI : 몬스터에게 데미지를 입힐 때 얼만큼의 피해를 입혔는지 숫자로 표시하기 위한 클래스
  • Ground : 전투 맵 상에서 맵 밖으로 빠져나가지 못하게 하기 위해 벽을 구현할 클래스
  • Missile : 총기류 무기들의 탄환을 구현하기 위한 클래스
  • Monster : 몬스터
  • Player : 유저 플레이어
  • UI : 화면상에 Text를 표현하거나, 뒷 배경과 같은 Panel, 버튼을 구현하기 위한 클래스
  • Weapon : 플레이어가 소지할 무기

CObject 멤버 변수

private:
	GROUP_TYPE		m_ObjType;

	wstring			m_ObjName;
	Vec2			m_vPos;
	Vec2			m_vPrevPos;
	Vec2			m_vScale;
	Vec2			m_vRenderScale;

	//Component
	CCollider*		m_pCollider;
	CAnimator*		m_pAnimator;
	CRigidbody*		m_pRigidBody;
	CGravity*		m_pGravity;
	vector<CImage*>	m_pImages;
	CTextUI*		m_pTextUI;

	bool			m_bAlive;			//자기 자신이 활성화 or 비활성화. (삭제 전용)
	bool			m_bEnable;			//일시적인 활성화 or 비활성화. 
	bool			m_bFlipX;


	//Render Wave용 Float
	float			m_fWaveDuration;
	float			m_fWaveElapsed;

 

  • 기본 속성
    • GROUP_TYPE m_ObjType : 오브젝트의 그룹 타입 ( 플레이어, 몬스터, UI 등등 분류 )
    • wstring m_ObjName : 오브젝트 이름
    • Vec2 m_vPos : 현재 위치
    • Vec2 m_PrevPos : 이전 프레임에서의 위치 ( 움직임 감지용 )
    • Vec2 m_vScale : 실제 크기
    • Vec2 m_vRenderScale : 렌더링 시 적용되는 크기 ( 애니메이션 효과 )
  • 컴포넌트 : 모든 객체마다 가질 수도 있고 안가질 수도 있는 특성들은 컴포넌트로 생각하여 필요할때 Create() 해주는 방식이다.
    • CCollider* m_pCollider : 충돌체 컴포넌트
    • CAnimator* m_pAnimator : 애니메이션 처리 컴포넌트
    • CRigidbody* m_pRigidBody : 물리 처리 컴포넌트 ( 중력, 힘, 가속도 적용 )
    • CGravity* m_pGravity : 중력 처리 컴포넌트
    • vector<CImage* > m_pImages : 이미지 렌더링 컴포넌트 벡터
    • CTextUI* m_pTextUI : 텍스트 UI 출력 컴포넌트
  • 상태 관리
    • bool m_bAlive : 객체가 살아있는지 여부 (false일 경우 삭제 대상이라 업데이트 하지 않음)
    • bool m_bEnable : 일시적 활성화 / 비활성화
    • bool m_bFlipX : 좌우 반전 여부

CObject 주요 함수

public:
	void SetPos(Vec2 _vPos) { m_vPos = _vPos; }
	void SetScale(Vec2 _vScale) { 
		m_vScale = _vScale; 
		m_vRenderScale = _vScale;
	}

	GROUP_TYPE GetObjType() { return m_ObjType; }
	Vec2 GetPos() { return m_vPos; }

	virtual Vec2 GetFinalPos() { return m_vPos; }

	Vec2 GetScale() { return m_vScale; }
	Vec2 GetRenderScale() { return m_vRenderScale; }

	void SetObjType(GROUP_TYPE _eType) { m_ObjType = _eType; }
	void SetName(const wstring& _strName) { m_ObjName = _strName; }
	void SetWaveDuration(float _fWaveTime) { m_fWaveDuration = _fWaveTime; }
	void SetRenderScale(Vec2 _vScale) { m_vRenderScale = _vScale; }

	void SetFlipX(bool _bFlipX) { m_bFlipX = _bFlipX; }
	bool GetFlipX() { return m_bFlipX; }

	const wstring& GetName() { return m_ObjName; }

	CCollider* GetCollider() { return m_pCollider; }
	CAnimator* GetAnimator() { return m_pAnimator; }
	CRigidbody* GetRigidbody() { return m_pRigidBody; }
	CGravity* GetGravity() { return m_pGravity; }
	CTextUI* GetTextUI() { return m_pTextUI; }

	vector<CImage*>& GetImages() { return m_pImages; }
	size_t GetImageCount() { return m_pImages.size(); }
	CImage* GetImage(int _Idx);
	void DeleteImage() { Safe_Delete_Vec(m_pImages); }
	void SetImage(CImage* _image, int _idx) { m_pImages[_idx] = _image; }
	void AddImage(ID2D1Bitmap* _bitmap);


	void CreateCollider();
	void CreateAnimator();
	void CreateRigidBody();
	void CreateGravity();
	void CreateImage();
	void CreateTextUI(const wstring& _text, Vec2 _offsetLT, Vec2 _offsetRB		//text , 좌상단 offset, 우하단 offset
						, int _fontSize, D2D1::ColorF _colorText				//글자크기, 글자색상(ColoF)
						, bool _bDrawOutline									//글자외곽선 여부(false라면 다 0으로 작성할것)
						, float _fOutlineThickness, D2D1::ColorF _colorOutline	//글자외곽선 두께, 글자외곽선 색상
						, FONT_TYPE _eType										//폰트 타입(DEFAULT, KR)
						, TextUIMode _eMode, int _iStartNum);					//(TEXT,NUMBER)모드 , NUMBER모드일 경우 감소시작할 숫자(타이머)

	virtual void OnCollision(CCollider* _pOther) {};
	virtual void OnCollisionEnter(CCollider* _pOther) {};
	virtual void OnCollisionExit(CCollider* _pOther) {};

	bool IsDead() {
		return !m_bAlive;
	}

	bool IsMove() {
		if (m_vPos.x != m_vPrevPos.x || m_vPos.y != m_vPrevPos.y) return true;
		return false;
	}

	void ShakeScale();

private:
	void SetDead() { m_bAlive = false; }

public:
	virtual void start() {};
	virtual void update();
	virtual void finalupdate();
	virtual void render(ID2D1HwndRenderTarget* _pRender);
	void component_render(ID2D1HwndRenderTarget* _pRender);

	virtual CObject* Clone() = 0;
public:
	CObject();
	CObject(const CObject& _origin);
	virtual ~CObject();
  • virtual void update()
    • 매 프레임 호출되는 업데이트 함수
    • 흔들림 효과(ShakeScale)를 적용하고 이전 위치를 저장하여 이동 여부 체크 준비.
  • virtual void finalupdate()
    • 모든 객체가 일반 업데이트 후 마지막으로 호출되는 함수
    • 각종 컴포넌트의 최종 업데이트 수행( 애니메이션, 충돌 박스 등 위치 조정 )
  • virtual void render(ID2D1HwndRenderTarget*)
    • Direct2D를 이용하여 렌더링
    • 각 클래스들은 오버라이딩 하여 사용하지만 하지 않을 경우 사각형을 그려주는 작업 수행
  • component_render( ID2D1HwndRenderTarget*)
    • 각 컴포넌트들 렌더링
  • 충돌 처리 이벤트 함수 ( OnCollision, OnCollisionEnter, OnCollisionExit )
    • 충돌이 일어날 때 호출되는 가상함수로 자식 클래스에서 재정의하여 각 클래스에 맞는 행동 구현

CObject.cpp , CObject.h

#pragma once
#include "global.h"
#include "CCamera.h"

class CCollider;
class CAnimator;
class CRigidbody;
class CGravity;
class CImage;
class CTextUI;

class CObject
{
private:
	GROUP_TYPE		m_ObjType;

	wstring			m_ObjName;
	Vec2			m_vPos;
	Vec2			m_vPrevPos;
	Vec2			m_vScale;
	Vec2			m_vRenderScale;

	//Component
	CCollider*		m_pCollider;
	CAnimator*		m_pAnimator;
	CRigidbody*		m_pRigidBody;
	CGravity*		m_pGravity;
	vector<CImage*>	m_pImages;
	CTextUI*		m_pTextUI;

	bool			m_bAlive;			//자기 자신이 활성화 or 비활성화. (삭제 전용)
	bool			m_bEnable;			//일시적인 활성화 or 비활성화. 
	bool			m_bFlipX;

	//Render Wave용 Float
	float			m_fWaveDuration;
	float			m_fWaveElapsed;

public:
	void SetPos(Vec2 _vPos) { m_vPos = _vPos; }
	void SetScale(Vec2 _vScale) { 
		m_vScale = _vScale; 
		m_vRenderScale = _vScale;
	}

	GROUP_TYPE GetObjType() { return m_ObjType; }
	Vec2 GetPos() { return m_vPos; }

	virtual Vec2 GetFinalPos() { return m_vPos; }

	Vec2 GetScale() { return m_vScale; }
	Vec2 GetRenderScale() { return m_vRenderScale; }

	void SetObjType(GROUP_TYPE _eType) { m_ObjType = _eType; }
	void SetName(const wstring& _strName) { m_ObjName = _strName; }
	void SetWaveDuration(float _fWaveTime) { m_fWaveDuration = _fWaveTime; }
	void SetRenderScale(Vec2 _vScale) { m_vRenderScale = _vScale; }

	void SetFlipX(bool _bFlipX) { m_bFlipX = _bFlipX; }
	bool GetFlipX() { return m_bFlipX; }

	const wstring& GetName() { return m_ObjName; }

	CCollider* GetCollider() { return m_pCollider; }
	CAnimator* GetAnimator() { return m_pAnimator; }
	CRigidbody* GetRigidbody() { return m_pRigidBody; }
	CGravity* GetGravity() { return m_pGravity; }
	CTextUI* GetTextUI() { return m_pTextUI; }

	vector<CImage*>& GetImages() { return m_pImages; }
	size_t GetImageCount() { return m_pImages.size(); }
	CImage* GetImage(int _Idx);
	void DeleteImage() { Safe_Delete_Vec(m_pImages); }
	void SetImage(CImage* _image, int _idx) { m_pImages[_idx] = _image; }
	//void AddImage(const wstring& tag);
	//void AddImage(const wstring& tag, ID2D1Bitmap* _bitmap);
	void AddImage(ID2D1Bitmap* _bitmap);

	CTextUI* GetTextUI() { return m_pTextUI; }

	void CreateCollider();
	void CreateAnimator();
	void CreateRigidBody();
	void CreateGravity();
	void CreateImage();
	void CreateTextUI(const wstring& _text, Vec2 _offsetLT, Vec2 _offsetRB		//text , 좌상단 offset, 우하단 offset
						, int _fontSize, D2D1::ColorF _colorText				//글자크기, 글자색상(ColoF)
						, bool _bDrawOutline									//글자외곽선 여부(false라면 다 0으로 작성할것)
						, float _fOutlineThickness, D2D1::ColorF _colorOutline	//글자외곽선 두께, 글자외곽선 색상
						, FONT_TYPE _eType										//폰트 타입(DEFAULT, KR)
						, TextUIMode _eMode, int _iStartNum);					//(TEXT,NUMBER)모드 , NUMBER모드일 경우 감소시작할 숫자(타이머)

	virtual void OnCollision(CCollider* _pOther) {};
	virtual void OnCollisionEnter(CCollider* _pOther) {};
	virtual void OnCollisionExit(CCollider* _pOther) {};

	bool IsDead() {
		return !m_bAlive;
	}

	bool IsMove() {
		if (m_vPos.x != m_vPrevPos.x || m_vPos.y != m_vPrevPos.y) return true;
		return false;
	}

	void ShakeScale();

private:
	void SetDead() { m_bAlive = false; }

public:
	virtual void start() {};
	virtual void update();
	virtual void finalupdate();
	virtual void render(ID2D1HwndRenderTarget* _pRender);
	void component_render(ID2D1HwndRenderTarget* _pRender);

	virtual CObject* Clone() = 0;
public:
	CObject();
	CObject(const CObject& _origin);
	virtual ~CObject();

	friend class CEventMgr;
};

#include "pch.h"
#include "CObject.h"

#include "CTimeMgr.h"

#include "CCollider.h"
#include "CAnimator.h"
#include "CResMgr.h"
#include "CTexture.h"
#include "CRigidbody.h"
#include "CGravity.h"
#include "CImage.h"
#include "Direct2DMgr.h"
#include "CTextUI.h"
#include "CPathMgr.h"


CObject::CObject()
	: m_vPos{}
	, m_ObjType(GROUP_TYPE::END)
	, m_vScale{} 
	, m_pCollider(nullptr)
	, m_pAnimator(nullptr)
	, m_pRigidBody(nullptr)
	, m_pGravity(nullptr)
	, m_pImages{}
	, m_pTextUI(nullptr)
	, m_bAlive(true)
	, m_bEnable(true)
	, m_fWaveDuration(1.f)
	, m_fWaveElapsed(0.f)
	, m_bFlipX(false)
{
	m_vRenderScale = m_vScale;
}

CObject::CObject(const CObject& _origin)
	: m_ObjName(_origin.m_ObjName)
	, m_ObjType(_origin.m_ObjType)
	, m_vPos(_origin.m_vPos)
	, m_vScale(_origin.m_vScale)
	, m_pCollider(nullptr)
	, m_bAlive(true)
	, m_bEnable(true)
	, m_pAnimator(nullptr)
	, m_pRigidBody(nullptr)
	, m_pGravity(nullptr)
	, m_pImages{}
	, m_pTextUI(nullptr)
	, m_bFlipX(false)
	, m_fWaveDuration(_origin.m_fWaveDuration)
	, m_fWaveElapsed(0.f)
{
	m_vRenderScale = m_vScale;
	if (_origin.m_pCollider != nullptr) {
		m_pCollider = new CCollider(*_origin.m_pCollider);
		m_pCollider->m_pOwner = this;
	}

	if (_origin.m_pAnimator != nullptr) {
		m_pAnimator = new CAnimator(*_origin.m_pAnimator);
		m_pAnimator->m_pOwner = this;
	}

	if (_origin.m_pGravity != nullptr) {
		m_pGravity = new CGravity(*_origin.m_pGravity);
		m_pGravity->m_pOwner = this;
	}

	if (_origin.m_pRigidBody != nullptr) {
		m_pRigidBody = new CRigidbody(*_origin.m_pRigidBody);
		m_pRigidBody->m_pOwner = this;
	}

	if (_origin.m_pTextUI != nullptr) {
		m_pTextUI = new CTextUI(*_origin.m_pTextUI);
		m_pTextUI->m_pOwner = this;
	}

}

CObject::~CObject() {
	if (m_pCollider != nullptr) delete m_pCollider;
	if (m_pAnimator != nullptr) delete m_pAnimator;
	if (m_pGravity != nullptr)  delete m_pGravity;
	if (m_pRigidBody != nullptr)delete m_pRigidBody;

	Safe_Delete_Vec(m_pImages);
	m_pImages.clear();

	if (m_pTextUI != nullptr) delete m_pTextUI;
}


void CObject::update()
{
	ShakeScale();
	m_vPrevPos = m_vPos;
}

void CObject::finalupdate()
{
	if (m_pAnimator) m_pAnimator->finalupdate();
	if (m_pGravity)  m_pGravity->finalupdate();
	if (m_pRigidBody)m_pRigidBody->finalupdate();
	if (m_pCollider) m_pCollider->finalupdate();
	
	if (!m_pImages.empty())
	{
		for (size_t i = 0; i < m_pImages.size(); ++i)
			m_pImages[i]->finalupdate();
	}
}

void CObject::render(ID2D1HwndRenderTarget* _pRender)
{
	//진짜 좌표.

	if (GetImages().empty() && nullptr == GetAnimator()) {
		Vec2 vRenderPos = CCamera::GetInstance()->GetRenderPos(m_vPos);

		float left = vRenderPos.x - m_vRenderScale.x / 2.f;
		float top = vRenderPos.y - m_vRenderScale.y / 2.f;
		float right = vRenderPos.x + m_vRenderScale.x / 2.f;
		float bottom = vRenderPos.y + m_vRenderScale.y / 2.f;

		D2D1_RECT_F rect = D2D1::RectF(left, top, right, bottom);

		ID2D1SolidColorBrush* pBrush = nullptr;
		HRESULT hr = _pRender->CreateSolidColorBrush(
			D2D1::ColorF(D2D1::ColorF::Black), &pBrush);

		if (SUCCEEDED(hr))
		{
			_pRender->DrawRectangle(rect, pBrush);
			pBrush->Release();
		}
	}

	component_render(_pRender);
}

void CObject::component_render(ID2D1HwndRenderTarget* _pRender)
{
	if (m_pAnimator != nullptr) m_pAnimator->render(_pRender);

	if (m_pCollider != nullptr)	m_pCollider->render(_pRender);

	if (!m_pImages.empty())
	{
		for (size_t i = 0; i < m_pImages.size(); ++i)
			m_pImages[i]->render(_pRender);
	}

	if (m_pTextUI != nullptr)	m_pTextUI->render(_pRender);
}

void CObject::CreateCollider()
{
	m_pCollider = new CCollider;
	m_pCollider->m_pOwner = this;
}

void CObject::CreateAnimator()
{
	m_pAnimator = new CAnimator;
	m_pAnimator->m_pOwner = this;
}

void CObject::CreateRigidBody()
{
	m_pRigidBody = new CRigidbody;
	m_pRigidBody->m_pOwner = this;
}

void CObject::CreateGravity()
{
	m_pGravity = new CGravity;
	m_pGravity->m_pOwner = this;
}
CImage* CObject::GetImage(int _Idx)
{
	size_t tmp = m_pImages.size() - 1;
	if (tmp < _Idx) return nullptr;
	else return m_pImages[_Idx];
}

void CObject::AddImage(ID2D1Bitmap* _bitmap)
{
	Direct2DMgr* pD2DMgr = Direct2DMgr::GetInstance();

	CImage* tmp = new CImage;
	tmp->SetBitmap(_bitmap);
	tmp->m_pOwner = this;
	m_pImages.push_back(tmp);
}

void CObject::CreateTextUI(const wstring& _text, Vec2 _offsetLT, Vec2 _offsetRB		//text , 좌상단 offset, 우하단 offset
	, int _fontSize, D2D1::ColorF _colorText										//글자크기, 글자색상(ColoF)
	, bool _bDrawOutline															//글자외곽선 여부(false라면 다 0으로 작성할것)
	, float _fOutlineThickness, D2D1::ColorF _colorOutline							//글자외곽선 두께, 글자외곽선 색상
	, FONT_TYPE _eType																//폰트 타입(DEFAULT, KR)
	, TextUIMode _eMode, int _iStartNum)											//(TEXT,NUMBER)모드 , NUMBER모드일 경우 감소시작할 숫자(타이머))
{
	m_pTextUI = new CTextUI;
	m_pTextUI->m_pOwner = this;

	m_pTextUI->SetMode(_eMode);
	m_pTextUI->SetText(_text);

	m_pTextUI->SetFontSize(_fontSize);
	m_pTextUI->SetTextColor(_colorText);

	m_pTextUI->SetDrawOutline(_bDrawOutline);
	m_pTextUI->SetOutlineThickness(_fOutlineThickness);
	m_pTextUI->SetOutlineColor(_colorOutline);

	m_pTextUI->SetFontType(_eType);

	m_pTextUI->SetOffsetLT(_offsetLT);
	m_pTextUI->SetOffsetRB(_offsetRB);

	m_pTextUI->SetTime(_iStartNum);
}


void CObject::ShakeScale()
{
	Vec2 OriginalScale = GetScale();
	Vec2 ScaleWave = OriginalScale * 0.3f;
	float waveScale = 1.f;
	if (IsMove()) {
		waveScale = 1.5f;
	}
	m_fWaveElapsed += (fDT * waveScale);

	float angle = 2 * PI * m_fWaveElapsed / m_fWaveDuration;
	float delta = 0.2f * sin(angle);

	m_vRenderScale.x = m_vScale.x + ((1.f + delta) * ScaleWave.x);
	m_vRenderScale.y = m_vScale.y + ((1.f - delta) * ScaleWave.y);

	if (m_fWaveElapsed > m_fWaveDuration) {
		m_fWaveElapsed = 0.f;
	}
}

 

 

예시

// 플레이어 객체 생성 예시
CPlayer* pPlayer = new CPlayer();
pPlayer->SetName(L"Hero");
pPlayer->SetPos(Vec2(100.f,200.f));
pPlayer->SetScale(Vec2(50.f,50.f));
pPlayer->CreateCollider();
pPlayer->CreateAnimator();

 


 

이렇게 생성된 객체들은 Scene에서 다음과 같은 코드에서 update()와 finalupdate() 그리고 render()가 호출된다.

void CScene::update()
{
	for (UINT typeIDX = 0; typeIDX < (UINT)GROUP_TYPE::END; typeIDX++) {
		for (size_t objIDX = 0; objIDX < m_arrObj[typeIDX].size(); objIDX++) {
			if (!m_arrObj[typeIDX][objIDX]->IsDead()) {
				m_arrObj[typeIDX][objIDX]->update();
			}
		}
	}
}
//움직이고 했던 걸, 마지막으로 업데이트 함. 
//충돌체가 플레이어 따라가게 함, 충돌 처리. 
void CScene::finalupdate()
{
	for (UINT typeIDX = 0; typeIDX < (UINT)GROUP_TYPE::END; typeIDX++) {
		for (size_t objIDX = 0; objIDX < m_arrObj[typeIDX].size(); objIDX++) {

			//Final Update는 돌려줌. 내부적으로 Component들의 마무리 단계 업데이트(충돌처리나, 참조관계등)
			m_arrObj[typeIDX][objIDX]->finalupdate();
		}
	}
}

void CScene::render(ID2D1HwndRenderTarget* _pRender)
{
	for (UINT typeIDX = 0; typeIDX < (UINT)GROUP_TYPE::END; typeIDX++) {
		auto ObjVecIter = m_arrObj[typeIDX].begin();

		for (; ObjVecIter != m_arrObj[typeIDX].end();) {
			if (!(*ObjVecIter)->IsDead()) {
				(*ObjVecIter)->render(_pRender);
				ObjVecIter++;
			}
			else {
				//Dead상태일 경우엔 렌더링에서 삭제하기. 
				ObjVecIter = m_arrObj[typeIDX].erase(ObjVecIter);
			}

		}
	}
}