응애맘마조

230705 강의 본문

공부/3D강의

230705 강의

TH.Wert 2023. 7. 5. 21:42

어제일자에 이어서 애니메이션 효과의 자연스러운 움직임에 대해서 강의했습니다. 애니메이션 블렌드를 했는데 애니메이션에서 다른 애니메이션으로 전환 시 끝 부분과 다른 애니메이션의 첫 부분을 섞어서 자연스럽게 표현한다고 보면 이해하기 쉽습니다.

#pragma once

class Animation
{
public:
	UINT					frameMax;
	UINT					boneMax;
	Matrix** arrFrameBone;//	프레임갯수* 본갯수 ( local Inverse * ani)
	float					tickPerSecond;
	string					file;

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

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.5f);
	void	RenderDetail();
	float   GetPlayTime();// 0처음 ~ 1 끝
};
#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];
	for (UINT i = 0; i < frameMax; i++)
	{
		arrFrameBone[i] = new Matrix[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_LAST)
	{
		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)
			{
				if (Animator.animState == AnimationState::ONCE_LAST)
				{
					Animator.currentFrame--;
					Animator.nextFrame--;
				}
				else
				{
					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]);
	}
}

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)
{
	/*currentAnimator.animState = state;
	currentAnimator.animIdx = idx;
	currentAnimator.currentFrame = 0;
	currentAnimator.nextFrame = 1;*/

	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::Text("PlayTime : %f", GetPlayTime());
	ImGui::SliderFloat("AniScale", &aniScale, 0.001f, 10.0f);
	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);
		}
		button = name + "OnceLast";
		if (ImGui::Button(button.c_str()))
		{
			PlayAnimation(AnimationState::ONCE_LAST, i);
		}
		button = name + "OnceFirst";
		if (ImGui::Button(button.c_str()))
		{
			PlayAnimation(AnimationState::ONCE_FIRST, i);
		}
		button = name + "Loop";
		if (ImGui::Button(button.c_str()))
		{
			PlayAnimation(AnimationState::LOOP, i);
		}
	}
}

float Animations::GetPlayTime()
{
	if (isChanging)
	{
		return (float)nextAnimator.nextFrame /
			(float)(playList[nextAnimator.animIdx]->frameMax - 1);
	}
	return (float)currentAnimator.nextFrame /
		(float)(playList[currentAnimator.animIdx]->frameMax - 1);
}

애니메이션 클래스의 헤더와 cpp 파일입니다. 이렇게 실행을 하게 되면 실행은 되나 만약 도중에 애니메이션 효과를 전환할 때 부드럽지 않고 프레임이 바로 바뀌게 됩니다. 

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);
	}
Changedtime = 0.0f;

isChanging = true;

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

PlayAnimation 함수에 있는 주석으로 된 코드를 풀고 위 2칸으로 나눈 코드를 주석 처리 하면 애니메이션에서 다른 애니메이션으로 바꿔도 이어지는 느낌이 있는 것처럼 전환이 가능합니다.

 

읽어주셔서 감사합니다.

'공부 > 3D강의' 카테고리의 다른 글

230707 강의  (0) 2023.07.07
230706 강의  (0) 2023.07.06
230704 강의  (0) 2023.07.04
230703 강의  (0) 2023.07.03
230630 강의  (0) 2023.06.30
Comments