WIN32 Animation

Date:     Updated:

카테고리:

태그:

16일차

- Default
    - Client.h
    - framework.h
    - Resouce.h
    - targetver.h
- Engine
    1. Header
        1. define.h
        **2. struct.h
        3. func.h**
        4. func.cpp
    2. Core
        1. CCore.cpp
 ****       2. CCore.h
    3. Manager
        1. KeyMgr
            1. CkeyMgr.cpp
            2. CkeyMgr.h
        2. TimeMgr
            1. CTimeMgr.cpp
            2. CTimeMgr.h
        3. SceneMgr
            1. CSceneMgr.cpp
            2. CSceneMgr.h
    		4. PathMgr
						1. CPathMgr.cpp
						2. CPathMgr.h
				**5. ResMgr
						1. CResMgr.cpp
						2. CResMgr.h**
	****			6. CollisionMgr
						1. CCollisionMgr.cpp
						2. CCollisionMgr.h
				7. EventMgr
						1. CEventMgr.cpp
****						2. CEventMgr.h
    4. Object
		    1. Monser
			    1. CMonster.cpp
			    2. CMonster.h
			  2. Missle
				  1. Missile.cpp
				  2. Missile.h
				**3. Player
					1. CPlayer.cpp
					2. Cplayer.h
        1. CObject.cpp**
        **2. CObject.h**
    5. Scene
        1. Scene_Start
            1. CScene_Start.cpp
            2. CScene_Start.h
        2. Scene_Tool
		        1. CScene_Tool.cpp
		        2. CScene_Tool.h
        3. CScene.cpp
        4. CScene.h
    6. Resource
		    1. Sound
		    2. Texture
			    1. CTexture.cpp
				  2. CTexture.h
		    3. CRes.cpp
		    **4. CRes.h**
		7. Component
				1. Collider
					1. CCollider.cpp
					2. CCollider.h
				**2. Animator
					1. Animation
						1. CAnimation.cpp
						2. CAnimation.h
					1. CAnimator.cpp
					2. CAnimation.h**
		8. Module
				1. SelectGDI.cpp
				2. SelectGDI.h
- 리소스 파일
- main.cpp
- pch.h.
  • 오늘은 주로 애니메이션이다!
  1. struct.h
	Vec2 operator * (int _value) {
		return Vec2(x * (float)_value, y * (float)_value);
	}
  • INT로 곱하기 할 수 있도록 연산자를 추가해줬다.
  1. func.h
template<typename T1, typename T2>
void Safe_Delete_Map(map<T1, T2>& _map) {
	typename map<T1, T2>::iterator iter = _map.begin();

	for (; iter != _map.end(); ++iter) {
		if (iter->second != nullptr) {
			delete iter->second;
			iter->second = nullptr;
		}
	}
	_map.clear();
}

template<typename T1, typename T2>
void Safe_Delete_UnordedMap(unordered_map<T1, T2>& _map) {
	typename unordered_map<T1, T2>::iterator iter = _map.begin();

	for (; iter != _map.end(); ++iter) {
		if (iter->second != nullptr) {
			delete iter->second;
			iter->second = nullptr;
		}
	}
	_map.clear();
}
  • 리소스, 애니메이션들을 map이나 unordered_map으로 관리한다. 그걸 전부 다 삭제할 때에 대한 템플릿 함수를 만들어서 삭제하기로 했다. 사실 auto&를 쓰면 되는데, 강의에서 이렇게 해서…
  1. CResMgr.h
#pragma once

class CTexture;
class CRes;
class CResMgr
{
	SINGLE(CResMgr)
private:
	map<wstring, CRes*> m_mapTex;

public:
	CTexture* LoadTexture(const wstring& _strKey, const wstring& _strRelativePath);
	CTexture* FindTexture(const wstring& _strKey);
};

  1. CResMgr.cpp
#include "pch.h"
#include "CResMgr.h"

#include "CPathMgr.h"
#include "CTexture.h"

CResMgr::CResMgr() {

}

CResMgr::~CResMgr() {

	Safe_Delete_Map(m_mapTex);
}

CTexture* CResMgr::LoadTexture(const wstring& _strKey, const wstring& _strRelativePath)
{
	//이미 텍스쳐가 map에 있는지 없는지.
	CTexture* pTex = FindTexture(_strKey);

	//이미 있다면 그냥 리턴해주기. 
	if (pTex != nullptr) return pTex;

	wstring strFilePath = CPathMgr::GetInstance()->GetContentPath();
	strFilePath += _strRelativePath;

	pTex = new CTexture;
	pTex->Load(strFilePath);

	//키랑 상대경로를 리소스가 스스로 알도록 하자. 
	//스스로가 어떤 이름인지, 어떤 경로인지 알도록 하자. 
	pTex->SetKey(_strKey);
	pTex->SetRelativePath(_strRelativePath);

	m_mapTex.insert(make_pair(_strKey, pTex));
	return pTex;
}

CTexture* CResMgr::FindTexture(const wstring& _strKey)
{
	auto TextureIter = m_mapTex.find(_strKey);

	if (TextureIter == m_mapTex.end()) return nullptr;
	else return (CTexture*)TextureIter->second;
}

  • CRes로 형태를 바꿔주었다. 다만, 어차피 내부적으로 포인터 값은 CTexture의 포인터 값이므로 상관은 없다.
  1. CAnimator.h
#pragma once

class CObject;
class CAnimation;
class CTexture;

class CAnimator
{
	//여러 종류의 Animation을 들고 있어야함. 
	//그 중에서도 현재 재생중인 애니메이션이 있겠지. 

private:
	unordered_map<wstring, CAnimation*> m_mapAnim;		//모든 Anim
	CAnimation*							m_pCurAnim;		//현재 재생중인 Anim
	CObject*							m_pOwner;		//Animator 소유 오브젝트
	bool								m_bRepeat;		//반복 재생 여부.

public:
	CObject* GetObj() { return m_pOwner; }

public:
	void CreateAnimation(const wstring& _strName, CTexture* _pTex, Vec2 _vLT, Vec2 _vSliceSize, Vec2 _vStep, float _fDuration, UINT _iFrameCount);
	CAnimation* FindAnimation(const wstring& _strName);
	void Play(const wstring& _strName, bool _bRepeat);

	void update();
	void render(HDC _dc);

public:
	CAnimator();
	~CAnimator();

	friend class CObject;
};

  • 애니메이터는 “애니메이션”을 실행한다. 따라서 한 애니메이터에 다양한 애니메이션이 등록될 수 있으며, 애니메이터는 한 오브젝트 안에 속해있다.
  1. CAnimator.cpp
#include "pch.h"
#include "CAnimator.h"
#include "CAnimation.h"

CAnimator::CAnimator()
	: m_pOwner(nullptr)
	, m_pCurAnim(nullptr)
	, m_mapAnim{}
	, m_bRepeat(false)
{
}

CAnimator::~CAnimator()
{
	Safe_Delete_UnordedMap(m_mapAnim);
}

void CAnimator::update()
{
	if (nullptr != m_pCurAnim) {
		m_pCurAnim->update();

		if (m_bRepeat && m_pCurAnim->IsFinish()) {
			m_pCurAnim->SetFrame(0);
		}
	}

}

void CAnimator::render(HDC _dc)
{
	if (nullptr != m_pCurAnim) {
		m_pCurAnim->render(_dc);
	}
}

void CAnimator::CreateAnimation(const wstring& _strName, CTexture* _pTex, Vec2 _vLT, Vec2 _vSliceSize, Vec2 _vStep, float _fDuration, UINT _iFrameCount)
{
	CAnimation* pAnim = FindAnimation(_strName);
	assert(nullptr == pAnim);

	pAnim = new CAnimation;

	pAnim->SetName(_strName);
	pAnim->m_pAnimator = this;
	pAnim->Create(_pTex, _vLT, _vSliceSize, _vStep, _fDuration, _iFrameCount);

	m_mapAnim.insert(make_pair(_strName, pAnim));
}

CAnimation* CAnimator::FindAnimation(const wstring& _strName)
{
	auto animIter = m_mapAnim.find(_strName);

	if (animIter == m_mapAnim.end()) return nullptr;
	
	return animIter->second;
}

void CAnimator::Play(const wstring& _strName, bool _bRepeat)
{
	m_pCurAnim = FindAnimation(_strName);
	m_bRepeat = _bRepeat;
}

  • 애니메이션을 만들고, 업데이트 하고 랜더한다. 중요한 CreateAnimation함수는
  • 애니메이션 이름, 텍스쳐, 텍스쳐의 좌측 상단(어디서부터 읽어올지), 한 슬라이스의 사이즈, 한 슬라이스마다 얼마나 이동해야 하는가, 한 프레임의 시간, 프레임의 갯수를 매개변수로 받는다.
  1. CAnimation.h
#pragma once

class CAnimator;
class CTexture;

//프레임마다 가지고 있을 정보.

//vLT : 좌상단. vSlice : 자를 영역. fDuration : 각 프레임에서 머무는 시간. 
//각 프레임에서 머무는 시간. 
struct tAnimFrm {
	Vec2	vLT;
	Vec2	vSlice;
	float	fDuration;
};

class CAnimation
{
private:
	wstring					m_strName;
	CAnimator*				m_pAnimator;

	CTexture*				m_pTex;			//Animation이 사용하는 텍스쳐
	vector<tAnimFrm>		m_vecFrm;		//모든 프레임 정보
	int						m_iCurFrm;		//현재 프레임. 
	float					m_fAccTime;		//시간 누적
	int						m_iFrameCount;

	bool					m_bFinish;		//애니메이션이 끝났음을 알림. 

public:
	void update();
	void render(HDC _dc);
	void Create(CTexture* _pTex, Vec2 _vLT, Vec2 _vSliceSize, Vec2 _vStep, float _fDuration, UINT iFrameCount);

public:
	const wstring& GetName() { return m_strName; }
	bool	IsFinish() { return m_bFinish; }

private:
	void SetName(const wstring& _strName) { m_strName = _strName; }
	void SetFrame(int _iframeIDX) {
		m_bFinish = false;
		m_iCurFrm = _iframeIDX;
		m_fAccTime = 0.f;
	}
	
public:
	CAnimation();
	~CAnimation();

	friend class CAnimator;
};

  • 구조체를 만들어서 한 프레임마다의 정보를 담는다. 나머지는 전부 주석에 적혀있다.
  1. CAnimation.cpp
#include "pch.h"
#include "CAnimation.h"
#include "CAnimator.h"
#include "CTimeMgr.h"
#include "CTexture.h"
#include "CObject.h"

CAnimation::CAnimation()
	:m_pAnimator(nullptr)
	,m_pTex(nullptr)
	,m_iCurFrm(0)
	,m_fAccTime(0.f)
	,m_bFinish(false)
{

}

CAnimation::~CAnimation()
{

}

void CAnimation::update()
{
	if (m_bFinish) return;
	m_fAccTime += fDT;
	//프레임이 계속 돌아가는데, 만약 내가 한 번 재생만 원한다면?
	if (m_vecFrm[m_iCurFrm].fDuration < m_fAccTime) {

		//일시 정지해서 fDT가 2.344면 프레임 Duration 뺀 값을 넣는게 맞지 않나?
		m_fAccTime -= m_vecFrm[m_iCurFrm].fDuration;
		m_iCurFrm++;
		if (m_vecFrm.size() <= m_iCurFrm) {

			//끝났다는 의미의 -1
			m_iCurFrm = -1;
			m_bFinish = true;
		}
	}
}

void CAnimation::render(HDC _dc)
{

	if (m_bFinish) return;
	CObject* pObj = m_pAnimator->GetObj();
	Vec2 vPos = pObj->GetPos();

	TransparentBlt(_dc,
			(int)(vPos.x - m_vecFrm[m_iCurFrm].vSlice.x / 2.f),
			(int)(vPos.y - m_vecFrm[m_iCurFrm].vSlice.x / 2.f),
			(int)(m_vecFrm[m_iCurFrm].vSlice.x),
			(int)(m_vecFrm[m_iCurFrm].vSlice.y),
			m_pTex->GetDC(),
			(int)(m_vecFrm[m_iCurFrm].vLT.x),
			(int)(m_vecFrm[m_iCurFrm].vLT.y),
			(int)(m_vecFrm[m_iCurFrm].vSlice.x),
			(int)(m_vecFrm[m_iCurFrm].vSlice.y),
			RGB(255,0,255)
		);
}

void CAnimation::Create(CTexture* _pTex, Vec2 _vLT, Vec2 _vSliceSize, Vec2 _vStep, float _fDuration, UINT iFrameCount)
{
	m_pTex = _pTex;
	tAnimFrm frm = {};
	for (int frameIDX = 0; frameIDX < (int)iFrameCount; frameIDX++) {
		frm.fDuration = _fDuration;
		frm.vLT = _vLT + _vStep * frameIDX;
		frm.vSlice = _vSliceSize;

		m_vecFrm.push_back(frm);
	}

}

  • update에서는 시간을 세어서 프레임을 넘긴다.
  • render에서는 그 텍스쳐를 기반으로 TransparentBlt로 렌더링한다.
  • Create에서는 주어진 Create함수를 기반으로 각 프레임을 생성하여 벡터에 넣어준다.
  1. CRes.h
#pragma once
class CRes
{

private:
	wstring		m_strKey;			//리소스의 고유한 키값
	wstring		m_strRelativePath;	//리소스 상대경로

public:
	void SetKey(const wstring& _strKey) { m_strKey = _strKey; }
	void SetRelativePath(const wstring& _strPath) { m_strRelativePath = _strPath; }

	const wstring& GetKey() { return m_strKey; }
	const wstring& GetRelativePath() { return m_strRelativePath; }

public:
	CRes();
	virtual ~CRes();
};
  • 부모격이 되는 이 클래스의 소멸자에 가상함수 표시를 안해놔서…
  1. CObject.h
#pragma once

//오브젝트라는 건, 가장 부모격인 녀석. 오브젝트마다 성향이 있음 
//어떤 건 UI, 어떤 건 캐릭터... 

//오브젝트를 일괄적으로 관리할 녀석이 필요함. -> Scene

//이 오브젝트는 부모 클래스. -> 상속받은 오브젝트도 Scene의 업데이트 시점에
//부모쪽에 구현되어있는 update뿐만 아니라, 각 오브젝트(자식)들 마다의 update를 하고 싶음

//충돌할 오브젝트, 충돌하지 않을 오브젝트(UI, 배경등등...) <- 이런 식으로 계속 트리처럼 나누는 건 예외가 있을수도 있음. 
// 따라서 부품기반의 구조가 필요함(컴포넌트 구조)
//구조의 

class CCollider;
class CAnimator;

class CObject
{

private:
	wstring			m_ObjName;
	Vec2			m_vPos;
	Vec2			m_vScale;

	//Component
	CCollider*		m_pCollider;
	CAnimator*		m_pAnimator;

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

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

	Vec2 GetPos() { return m_vPos; }
	Vec2 GetScale() { return m_vScale; }

	void SetName(const wstring& _strName) { m_ObjName = _strName; }
	const wstring& GetName() { return m_ObjName; }

	CCollider* GetCollider() { return m_pCollider; }
	CAnimator* GetAnimator() { return m_pAnimator; }

	void CreateCollider();
	void CreateAnimator();

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

	bool IsDead() {
		return !m_bAlive;
	}

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

public:
	virtual void update() = 0;

	//부모에서 함수의 구현이 끝나게, 자식에서 오버라이딩 못하게 막기. 
	virtual void finalupdate() final;
	virtual void render(HDC _dc);

	void component_render(HDC _dc);
	virtual CObject* Clone() = 0;

public:
	CObject();
	CObject(const CObject& _origin);
	virtual ~CObject();

	//복사 생성자의 문제. -> 단순히 복사만 하면 m_pCollider의 주소가 그대로 복사됨.
	//따라서 m_pCollider을 따로 만들어줘야 함. 
	//m_bAlive도 가져올 필요는 없음. 

	friend class CEventMgr;
};
  • GetAnimator, m_pAnimator이 추가되었다.
  1. CObject.cpp
#include "pch.h"
#include "CObject.h"

#include "CCollider.h"
#include "CAnimator.h"

CObject::CObject()
	: m_vPos{}
	, m_vScale{} 
	, m_pCollider(nullptr)
	, m_pAnimator(nullptr)
	, m_bAlive(true)
	, m_bEnable(true)
{

}

CObject::CObject(const CObject& _origin)
	: m_ObjName(_origin.m_ObjName)
	, m_vPos(_origin.m_vPos)
	, m_vScale(_origin.m_vScale)
	, m_pCollider(nullptr)
	, m_bAlive(true)
	, m_bEnable(true)
	, m_pAnimator(nullptr)
{
	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;
	}
}

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

	if (m_pAnimator != nullptr) delete m_pAnimator;
}

void CObject::finalupdate()
{
	if (m_pCollider) m_pCollider->finalupdate();
}

void CObject::render(HDC _dc)
{

	Rectangle(_dc, (int)(m_vPos.x - m_vScale.x / 2.f), (int)(m_vPos.y - m_vScale.y / 2.f),
		(int)(m_vPos.x + m_vScale.x / 2.f), (int)(m_vPos.y + m_vScale.y / 2.f));

	component_render(_dc);

}

void CObject::component_render(HDC _dc)
{
	if (m_pCollider != nullptr)	m_pCollider->render(_dc);

	if (m_pAnimator != nullptr) m_pAnimator->render(_dc);
}

void CObject::CreateCollider()
{
	m_pCollider = new CCollider;

	m_pCollider->m_pOwner = this;
}

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

  • 애니메이터 만들고, 애니메이터 렌더링하고…
  1. CPlayer.cpp
#include "pch.h"
#include "CPlayer.h"

#include "CSceneMgr.h"
#include "CScene.h"
#include "CkeyMgr.h"
#include "CTimeMgr.h"
#include "CResMgr.h"

#include "CMissile.h"
#include "CTexture.h"
#include "CCollider.h"
#include "CAnimator.h"

CPlayer::CPlayer()
{
	//m_pTex = CResMgr::GetInstance()->LoadTexture(L"PlayerTex", L"texture\\Tenshi.bmp");

	CreateCollider();
	GetCollider()->SetOffsetPos(Vec2(0.f, 12.f));
	GetCollider()->SetScale(Vec2(25.f, 25.f));

	CTexture* m_pTex = CResMgr::GetInstance()->LoadTexture(L"PlayerTex", L"texture\\link_0.bmp");

	//우리 텍스쳐가 완벽하게 편집이 되어 있어서 저렇게 수월하게 할 수 있었음...
	//근데 그렇지 않은 경우가 정말 많음. 
	CreateAnimator();
	GetAnimator()->CreateAnimation(L"WALK_DOWN", m_pTex, Vec2(0.f, 260.f), Vec2(60.f, 65.f), Vec2(60.f, 0.f), 0.1f, 10);
	GetAnimator()->Play(L"WALK_DOWN", true);

}

CPlayer::~CPlayer()
{

}

void CPlayer::update()
{
	Vec2 vPos = GetPos();
	if (KEY_HOLD(KEY::W)) {
		vPos.y -= 200.f * fDT;
	}
	if (KEY_HOLD(KEY::S)) {
		vPos.y += 200.f * fDT;
	}
	if (KEY_HOLD(KEY::A)) {
		vPos.x -= 200.f * fDT;
	}
	if (KEY_HOLD(KEY::D)) {
		vPos.x += 200.f * fDT;
	}

	if (KEY_TAP(KEY::Z)) {
		CreateMissile();
	}
	SetPos(vPos);

	GetAnimator()->update();
}

void CPlayer::render(HDC _dc)
{
	//컴포넌트(충돌체, etc...) 가 있는 경우 렌더. 
	component_render(_dc);
}

void CPlayer::CreateMissile()
{
	Vec2 vMissilePos = GetPos();
	vMissilePos.y -= GetScale().y / 2.f;

	CMissile* pMissile = new CMissile;

	pMissile->SetName(L"Missile_Player");
	pMissile->SetPos(vMissilePos);
	pMissile->SetScale(Vec2(25.f, 25.f));
	pMissile->SetDir(Vec2(0.f, -1.f));

	//이걸 바로 추가하는 게 아니라, 이벤트를 등록하는 것. 
	CreateObject(pMissile, GROUP_TYPE::PROJ_PLAYER);
}

  • 애니메이션만이 있는 개체는 텍스쳐가 필요 없다. 그래서 지워줬고.
  • 애니메이터를 생성자에서 만들고, Create하고, Play시켰다. 나중에 캐릭터의 상태(IDLE, WALK, ATTACK…등)에 따라 달라지겠지만, 일단은 걷는 형태만.

WINAPI 카테고리 내 다른 글 보러가기

댓글 남기기