본문 바로가기

WinAPI

[WinAPI] 게임 프레임워크 - 8. Animation

게임 프레임워크에서 애니메이션을 ANIMATIONCLIP이라는 구조체에 저장한다.

 

typedef struct _tagAnimationClip
{
	ANIMATION_TYPE eType;
	ANIMATION_OPTION eOption;
	vector<class CTexture*> vecTexture;
	float fAnimationTime;
	float fAnimationLimitTime;
	float fAnimationFrameTime;
	int iFrameX;
	int iFrameY;
	int iFrameMaxX;
	int iFrameMaxY;
	int iStartX;
	int iStartY;
	int iLengthX;
	int iLengthY;
	float fOptionTime;
	float fOptionLimitTime;
	_SIZE tFrameSize;
}ANIMATIONCLIP, *PANIMATIONCLIP;

 

Animation Type은 ATLAS, FRAME이 있는데 ATLAS는 애니메이션이 이미지 한장에 담겨있을 때, FRAME은 애니메이션이 이미지 여러장에 한장씩 그려져 있는 것이다. eOption은 애니메이션이 끝나고 다시 디폴트 애니메이션으로 돌아갈지 객체 자체를 없앨지 정한다. fAnimationTime은 애니메이션 재생시간, fAnimationFrameTime은 한 프레임에 해당하는 시간이다. iFrameX, iFrameY는 현재 애니메이션의 프레임이다. 

 

bool CAnimation::AddClip(const string& strName, ANIMATION_TYPE eType,	
	ANIMATION_OPTION eOption, float fAnimationLimitTime, 
	int iFrameMaxX, int iFrameMaxY, 
	int iStartX, int iStartY, int iLengthX, int iLengthY, 
	float fOptionLimitTime, const string& strTexKey, 
	const wchar_t* pFileName, const string& strPathKey)
{
	PANIMATIONCLIP pClip = new ANIMATIONCLIP;

	pClip->eType = eType;
	pClip->eOption = eOption;
	pClip->fAnimationLimitTime = fAnimationLimitTime;
	pClip->iFrameMaxX = iFrameMaxX;
	pClip->iFrameMaxY = iFrameMaxY;
	pClip->iStartX = iStartX;
	pClip->iStartY = iStartY;
	pClip->iLengthX = iLengthX;
	pClip->iLengthY = iLengthY;
	pClip->fOptionLimitTime = fOptionLimitTime;
	pClip->fAnimationFrameTime = fAnimationLimitTime / iLengthX * iLengthY; 

	CTexture* pTex = GET_SINGLE(CResourcesManager)->LoadTexture(strTexKey,
		pFileName, strPathKey);

	pClip->tFrameSize.x = pTex->GetWidth() / iFrameMaxX;
	pClip->tFrameSize.y = pTex->GetHeight() / iFrameMaxY;

	pClip->vecTexture.push_back(pTex);

	pClip->fAnimationTime = 0.f;
	pClip->iFrameX = iStartX;
	pClip->iFrameY = iStartY;
	pClip->fOptionTime = 0.f;

	m_mapClip.insert(make_pair(strName, pClip));

	if (m_strDefaultClip.empty())	
		SetDefaultClip(strName);
	if (m_strCurClip.empty())
		SetCurrentClip(strName);

	return true;
}

 

AddClip은 ANIMATIONCLIP의 정보를 입력해 m_mapClip에 삽입한다. 그리고 DefaultClip과 CurClip이 비어있는 상태라면 클립을 디폴트 클립과 현재 클립으로 지정한다.

 

void CAnimation::Update(float fTime)
{
	m_bMotionEnd = false;
	m_pCurClip->fAnimationTime += fTime;

	while (m_pCurClip->fAnimationTime >= m_pCurClip->fAnimationFrameTime)
	{
		m_pCurClip->fAnimationTime -= m_pCurClip->fAnimationFrameTime;
		++m_pCurClip->iFrameX;	

		if (m_pCurClip->iFrameX - m_pCurClip->iStartX == m_pCurClip->iLengthX)
		{
			m_pCurClip->iFrameX = m_pCurClip->iStartX;
			++m_pCurClip->iFrameY;

			if (m_pCurClip->eType == AT_FRAME)
				m_pObj->SetTexture(m_pCurClip->vecTexture[m_pCurClip->iFrameX]);

			if (m_pCurClip->iFrameY - m_pCurClip->iStartY == m_pCurClip->iLengthY)
			{
				m_pCurClip->iFrameY = m_pCurClip->iStartY;
				m_bMotionEnd = true;
				switch (m_pCurClip->eOption)
				{
					case AO_ONCE_RETURN:
						ChangeClip(m_strDefaultClip);
						break;
					case AO_ONCE_DESTROY:
						m_pObj->Die();
						break;
					case AO_TIME_RETURN:
						break;
					case AO_TIME_DESTROY:
						break;
				}
			}
		}

		else 
		{
			if (m_pCurClip->eType == AT_FRAME)
				m_pObj->SetTexture(m_pCurClip->vecTexture[m_pCurClip->iFrameX]);
		}
	}
}

 

Animation::Update 에서 시간에 따라 fAnimationTime이 증가한다. fAnimationTime이 fAnimationFrameTime을 초과하면 FrameX가 증가한다. FrameX - StartX가 LengthX와 같아지면 FrameX를 StartX로 초기화하고 FrameY가 증가한다. FrameY - StartY가 LengthY와 같아지면 MotionEnd = true가 된다. ANIMATIONCLIP의 타입 중 AO_ONCE_RETURN은 애니메이션이 끝나고 디폴트 클립으로 돌아간다. AO_ONCE_DESTROY는 객체가 지워진다.

 

void CAnimation::ChangeClip(const string& strClip)
{
	if (m_strCurClip == strClip)
		return;

	m_strCurClip = strClip;

	if (m_pCurClip)
	{
		m_pCurClip->iFrameX = m_pCurClip->iStartX;
		m_pCurClip->iFrameY = m_pCurClip->iStartY;
		m_pCurClip->fAnimationTime = 0.f;
		m_pCurClip->fOptionTime  = 0.f;
	}
	m_pCurClip = FindClip(strClip);

	if (m_pCurClip->eType == AT_ATLAS)
		m_pObj->SetTexture(m_pCurClip->vecTexture[0]);

	else if (m_pCurClip->eType == AT_FRAME)
		m_pObj->SetTexture(m_pCurClip->vecTexture[m_pCurClip->iFrameX]);
}

 

ChangeClip은 인수로 받아온 문자열에 맞는 Clip을 CurClip으로 지정하고 화면에 애니메이션이 보여질 수 있도록 SetTexture해준다. 

 

		POSITION tImagePos;

		if (m_pAnimation)
		{
			PANIMATIONCLIP pClip = m_pAnimation->GetCurrentClip();
					
			if (pClip->eType == AT_ATLAS)
			{
				tImagePos.x = pClip->iFrameX * m_tSize.x;
				tImagePos.y = pClip->iFrameY * m_tSize.y;
			}			
		}

		tImagePos += m_tImageOffset;

 

Object::Render에서 애니메이션클립이 AT_ATLAS 타입일 때 frameX, frameY에 따라 이미지의 위치를 달리 해서 출력한다. 한장의 이미지에 위치만 다르게 해서 출력하지만 마치 애니메이션이 움직이는 것처럼 표현된다. 

 

	CAnimation*  pAni = CreateAnimation("PlayerAnimation");
	AddAnimationClip("IdleLeft", AT_ATLAS, AO_LOOP, 0.3f, 6, 5,
		0, 0, 5, 1, 0.f, "PlayerIdleLeft", L"Player/PlayerSpriteLR.bmp");
	SetAnimationClipColorKey("IdleLeft", 255, 255, 255);
	AddAnimationClip("RunLeft", AT_ATLAS, AO_ONCE_RETURN, 0.3f, 6, 5,
		0, 1, 4, 1, 0.f, "PlayerRunLeft", L"Player/PlayerSpriteLR.bmp");
	SetAnimationClipColorKey("RunLeft", 255, 255, 255);
	AddAnimationClip("AttackLeft", AT_ATLAS, AO_ONCE_RETURN, 0.3f, 6, 5,
		0, 3, 6, 1, 0.f, "PlayerAttackLeft", L"Player/PlayerSpriteLR.bmp");
	SetAnimationClipColorKey("AttackLeft", 255, 255, 255);

 

Player에서 애니메이션과 클립을 만드는 예시이다. 가만히 있을 때 , 뛸 때 , 공격할 때 애니메이션클립을 추가한다. 애니메이션 타입이 ATLAS이기 때문에 같은 파일을 참조한다. Idle 클립은 AO_LOOP 타입인데 Animation::Update의 switch 문의 AO_LOOP case 가 없기 때문에 계속 반복해서 애니메이션을 그린다. Idle 클립을 처음 추가했기 때문에 자동으로 

defaultClip, curClip으로 지정한다. 그리고 방향키를 눌렀을 때 ChangeClip을 통해 걷는 애니메이션으로 변경한다. Run클립은 AO_ONCE_RETURN이므로 걷는 모션이 끝나면 Idle클립으로 돌아온다. 그리고 SetAnimationClipColorKey로 클립 텍스쳐의 배경색을 지운다.