본문 바로가기
공부/Animation

[Animation] Animation 클래스 정리

by MY블로그 2023. 7. 4.
FrameWork / GameObject / Member / Animation.h
#pragma once
class Animation
{
public:
	UINT					frameMax;
	UINT					boneMax;
	Matrix** arrFrameBone;//	프레임갯수* 본갯수 (차후 Rvalue && 로 사용할 것)
	float					tickPerSecond;
	string					file;


	Animation();
	~Animation();
	void LoadFile(string file);
	void SaveFile(string file);
};
enum class AnimationState
{
	LOOP,
	ONCE,
	STOP//Pause
};

class Animations
{
	struct Animator
	{
		float frameWeight = 0.0f;
		UINT  currentFrame = 0;
		UINT  nextFrame = 1;
		UINT  animIdx = 0;
		AnimationState animState = AnimationState::STOP;
	};
	void AnimatorUpdate(Animator& Animator);
public:
	Animations();
	~Animations();
	void Update();
	Animator							currentAnimator;
	Animator							nextAnimator;
	bool								isChanging;
	float								blendtime;
	float								Changedtime;
	float								aniScale = 1.0f;
	vector<shared_ptr<Animation>>		playList;
	Matrix	GetFrameBone(int boneIndex);
	void	PlayAnimation(AnimationState state, UINT idx, float blendtime = 0.2f);
	void	RenderDetail();
	float   GetPlayTime();// 0처음 ~ 1 끝
};
FrameWork / GameObject / Member / Animation.cpp
#include "framework.h"

Animation::Animation() // 생성자( 초기화 )
{
	frameMax = 0;
	boneMax = 0;
	tickPerSecond = 0;
	arrFrameBone = nullptr;
	file = "";
}

Animation::~Animation() // 소멸자
{
	for (UINT i = 0; i < frameMax; i++)
	{
		delete[] arrFrameBone[i];
	}
	delete[] arrFrameBone;
}


void Animation::LoadFile(string file) // 파일 불러오기
{
	this->file = file;
	BinaryReader in;
	wstring path = L"../Contents/Animation/" + Util::ToWString(file);
	in.Open(path);

	frameMax = in.Int();
	boneMax = in.Int();
	tickPerSecond = in.Float();

	arrFrameBone = new Matrix * [frameMax]; // 읽어온 frameMax 만큼 배열로 생성 
	for (UINT i = 0; i < frameMax; i++)
	{
		arrFrameBone[i] = new Matrix[boneMax];  // 생성된 배열에 읽어온 boneMax 만큼 배열로 생성
	}

	for (UINT i = 0; i < frameMax; i++)
	{
		for (UINT j = 0; j < boneMax; j++)
		{
			arrFrameBone[i][j] = in.matrix();
		}
	}
	in.Close();
}

void Animation::SaveFile(string file) // 읽어온 파일 저장
{
	this->file = file;
	BinaryWriter out;
	wstring path = L"../Contents/Animation/" + Util::ToWString(file);
	out.Open(path);

	out.Int(frameMax);
	out.Int(boneMax);
	out.Float(tickPerSecond);

	for (UINT i = 0; i < frameMax; i++)
	{
		for (UINT j = 0; j < boneMax; j++)
		{
			out.matrix(arrFrameBone[i][j]);
		}
	}
	out.Close();
}

void Animations::AnimatorUpdate(Animator& Animator) // 애니메이터 업데이트
{
	if (Animator.animState == AnimationState::LOOP) // 무한반복
	{
		Animator.frameWeight += DELTA * playList[Animator.animIdx]->tickPerSecond * aniScale;
		if (Animator.frameWeight >= 1.0f)
		{
			Animator.frameWeight = 0.0f;
			Animator.currentFrame++;
			Animator.nextFrame++;
			if (Animator.nextFrame >= playList[Animator.animIdx]->frameMax)
			{
				Animator.currentFrame = 0;
				Animator.nextFrame = 1;
			}
		}
	}
	else if (Animator.animState == AnimationState::ONCE) // 애니메이션 프레임0~끝까지 1회만 재생
	{
		Animator.frameWeight += DELTA * playList[Animator.animIdx]->tickPerSecond * aniScale;
		if (Animator.frameWeight >= 1.0f)
		{
			Animator.frameWeight = 0.0f;
			Animator.currentFrame++;
			Animator.nextFrame++;
			if (Animator.nextFrame >= playList[Animator.animIdx]->frameMax)
			{
				Animator.currentFrame--;
				Animator.nextFrame--;
				/*Animator.currentFrame = 0;
				Animator.nextFrame = 1;*/
				Animator.animState = AnimationState::STOP;
			}
		}
	}
}

Animations::Animations()
{
	isChanging = false;
}

Animations::~Animations()
{
	for (int i = 0; i < playList.size(); i++)
	{
		SafeReset(playList[i]); // vector<shared_ptr<Animation>>playList 스마트포인터 해제
	}
}

void Animations::Update()
{
	if (isChanging) // 전환시
	{
		AnimatorUpdate(nextAnimator);
		Changedtime += DELTA;
		if (Changedtime > blendtime)
		{
			Changedtime = 0.0f;
			//다음애니메이션을 현재애니메이션으로 바꾼다.
			currentAnimator = nextAnimator;
			isChanging = false;
		}
	}
	AnimatorUpdate(currentAnimator);
}

Matrix Animations::GetFrameBone(int boneIndex)
{
	if (isChanging)
	{
		return
			playList[currentAnimator.animIdx]->arrFrameBone[currentAnimator.nextFrame][boneIndex]
			* (1.0f - Changedtime / blendtime)
			+
			(playList[nextAnimator.animIdx]->arrFrameBone[nextAnimator.nextFrame][boneIndex]
				* nextAnimator.frameWeight +
				playList[nextAnimator.animIdx]->arrFrameBone[nextAnimator.currentFrame][boneIndex]
				* (1.0f - nextAnimator.frameWeight)) * (Changedtime / blendtime);
	}

	return playList[currentAnimator.animIdx]->arrFrameBone[currentAnimator.nextFrame][boneIndex]
		* currentAnimator.frameWeight +
		playList[currentAnimator.animIdx]->arrFrameBone[currentAnimator.currentFrame][boneIndex]
		* (1.0f - currentAnimator.frameWeight);
}

void Animations::PlayAnimation(AnimationState state, UINT idx, float blendtime)
{
	Changedtime = 0.0f;

	isChanging = true;

	currentAnimator.animState = AnimationState::STOP;
	nextAnimator.animState = state;
	this->blendtime = blendtime;
	nextAnimator.animIdx = idx;
	nextAnimator.currentFrame = 0;
	nextAnimator.nextFrame = 1;
}

void Animations::RenderDetail() // ImGui 사용을 위한 함수
{
	ImGui::Text("PlayTime : %f", GetPlayTime());
	ImGui::InputFloat("AniScale", &aniScale, 0.1f, 1.0f); // SlideFloat 에서 편의상 InputFloat 으로 변경
	for (UINT i = 0; i < playList.size(); i++)
	{
		string name = to_string(i) + playList[i]->file;
		string button = name + "Stop";


		if (ImGui::Button(button.c_str()))
		{
			PlayAnimation(AnimationState::STOP, i);
		}
		//ImGui::SameLine(); // 편의상 해제
		button = name + "Once";
		if (ImGui::Button(button.c_str()))
		{
			PlayAnimation(AnimationState::ONCE, i);
		}
		//ImGui::SameLine(); // 편의상 해제
		button = name + "Loop";
		if (ImGui::Button(button.c_str()))
		{
			PlayAnimation(AnimationState::LOOP, i);
		}
	}
}

float Animations::GetPlayTime() // 플레이타임은 전체프레임이 비율로 바뀐다. 0~1 사이값
{
	if (isChanging)
	{
		return (float)nextAnimator.nextFrame /
			(float)(playList[nextAnimator.animIdx]->frameMax - 1);
	}
	return (float)currentAnimator.nextFrame /
		(float)(playList[currentAnimator.animIdx]->frameMax - 1);
}

복습필요!

 

Ps.특이사항

현재 Animation 적용후 재생시 각각의 bonse(관절)을 임의 조절 할 수 있도록 코드가 변경 되었다,

단, 런타임중 바뀌게되므로 연산량이 증가하기때문에 주의 해야한다!

 

기존 공식

OffSet * NodeTransform * Parent 

 

변경 후

Tpose * (Tpose * Animation) * Parent

댓글