WIN32 Animation
카테고리: WINAPI
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.
- 오늘은 주로 애니메이션이다!
- struct.h
Vec2 operator * (int _value) {
return Vec2(x * (float)_value, y * (float)_value);
}
- INT로 곱하기 할 수 있도록 연산자를 추가해줬다.
- 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&를 쓰면 되는데, 강의에서 이렇게 해서…
- 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);
};
- 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의 포인터 값이므로 상관은 없다.
- 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;
};
- 애니메이터는 “애니메이션”을 실행한다. 따라서 한 애니메이터에 다양한 애니메이션이 등록될 수 있으며, 애니메이터는 한 오브젝트 안에 속해있다.
- 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함수는
- 애니메이션 이름, 텍스쳐, 텍스쳐의 좌측 상단(어디서부터 읽어올지), 한 슬라이스의 사이즈, 한 슬라이스마다 얼마나 이동해야 하는가, 한 프레임의 시간, 프레임의 갯수를 매개변수로 받는다.
- 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;
};
- 구조체를 만들어서 한 프레임마다의 정보를 담는다. 나머지는 전부 주석에 적혀있다.
- 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함수를 기반으로 각 프레임을 생성하여 벡터에 넣어준다.
- 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();
};
- 부모격이 되는 이 클래스의 소멸자에 가상함수 표시를 안해놔서…
- 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이 추가되었다.
- 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;
}
- 애니메이터 만들고, 애니메이터 렌더링하고…
- 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…등)에 따라 달라지겠지만, 일단은 걷는 형태만.
댓글 남기기