함수포인터를 사용해서 애니메이션의 자동 프레임전환을 구현해보자.
언리얼에서 사용하는 애니메이션의 자동 전환은. 일정 시간 뒤에 애니메이션이 자동으로 전환되는 형식으로 구성되어있는데,
https://docs.unrealengine.com/4.27/ko/AnimatingObjects/SkeletalMeshAnimation/Sequences/
애니메이션 시퀀스
Animation Sequence, 애니메이션 시퀀스 애셋은 한 애니메이션을 이루는 모든 트랜스폼 데이터가 들어있는 개별 애셋입니다.
docs.unrealengine.com
이러한 애니메이션간 선형전환방식을 사용할것 이다.
class ModelClip
{
private:
friend class ModelAnimator;
ModelClip() = default;
~ModelClip();
void Init();
void Excute();
KeyFrame* GetKeyFrame(string boneName) { return keyFrames[boneName]; }
public:
void SetEvent(Event event, float timeRatio) { events[timeRatio] = event; }
private:
string name;
UINT frameCount;
float tickPerSecond, duration;
float playTime = 0.0f;
// 노드 이름을 키로 하고 프레임 데이터를 데이터로 한다.
unordered_map<string, KeyFrame*> keyFrames;
map<float, Event> events;
map<float, Event>::iterator eventIter;
};
우선 애니메이션 클립 저장 객체에 map형태의 event 변수를 생성한다.
map은 진입된 Key값의 순서대로 저장되는 성질이 있음으로. 더 빠른 위치의 애니메이션이 입력되었을 때, 그 애니메이션이 출력될것이다.
0초에 애니메이션을 지정하고, 0.5초 뒤에 Run이라는 애니메이션을 집어넣었다면,
float의 시간대로 Map을 검색하고, count시, 1이상이 검색되었을 경우, 그 애니메이션의 전환 event를 실행시켜주면 된다.
void ModelClip::Init()
{
duration = frameCount / tickPerSecond;
eventIter = events.begin();
playTime = 0.0f;
}
void ModelClip::Excute()
{
if (events.empty()) return;
if (eventIter == events.end()) return;
float ratio = playTime / duration;
if (eventIter->first > ratio) return;
eventIter->second();
eventIter++;
}
현재 eventIter에서 검색되어지지 않았을 경우, 또는 마지막일경우, 반환시켜준다. 실행을 무시한다.
만약, 전부다 해당되면서, 현재 애니메이션 재생위치가 처음보다 클경우,(음수진입이 안되었을경우)
Iter에 지정되어있는 Event를 실행시키고, Iter를 다음으로 넘겨준다.
void PlayClip(int clip, float scale = 1.0f, float takeTime = 0.2f);
// 텍스처 생성 함수: 텍스처를 생성하여 저장
void CreateTexture();
Matrix GetTransformByNode(int nodeIndex);
void SetRunAnimation(bool input) { IsPlay = input; }
ModelClip* GetClip(UINT clip) { return clips[clip]; }
protected:
// 클립 변환 생성 함수: 지정된 인덱스에 대한 클립 변환 매트릭스 생성
void CreateClipTransform(UINT index);
void UpdateFrame();
ModelAnimator에 GetTransformByNode과 PlayClip을 생성해준다.
Matrix ModelAnimator::GetTransformByNode(int nodeIndex)
{
Matrix curAnim;
{
Frame& frame = frameBuffer->GetData()->cur;
Matrix cur = nodeTransforms[frame.clip].transform[frame.curFrame][nodeIndex];
Matrix next = nodeTransforms[frame.clip].transform[frame.curFrame + 1][nodeIndex];
curAnim = MATH->Lerp(cur, next, frame.time) * world;
}
{
Frame& frame = frameBuffer->GetData()->next;
if (frame.clip < 0)
return curAnim;
Matrix cur = nodeTransforms[frame.clip].transform[frame.curFrame][nodeIndex];
Matrix next = nodeTransforms[frame.clip].transform[frame.curFrame + 1][nodeIndex];
Matrix nextAnim = MATH->Lerp(cur, next, frame.time) * world;
return MATH->Lerp(curAnim, nextAnim, frameBuffer->GetData()->tweenTime);
}
}
ModelAnimator은, 지정된 AnimClip객체에서 현재 재생되고있는 애니메이션의 클립데이터의 보간정보값을 받아와서.
Node와 Node사이의 전환데이터의 중간 Matrix를 받아온다.
이 Matrix는, 객체에 상속된 Obejct가 존재할경우, 해당 Object의 위치정보 변환에 사용된다.
void ModelAnimator::PlayClip(int clip, float scale, float takeTime)
{
IsPlay = true;
frameBuffer->GetData()->next.clip = clip;
frameBuffer->GetData()->next.scale = scale;
frameBuffer->GetData()->takeTime = takeTime;
clips[clip]->Init();
}
playClip에서는, 지정된 클립과 속도, 변환시간을 넣은다음. clip을 초기화시켜준다.
시작초기화를 하는것.
그리고,
void ModelAnimator::UpdateFrame()
{
if (!IsPlay) return;
Motion* motion = frameBuffer->GetData();
{
Frame* frame = &motion->cur;
ModelClip* clip = clips[frame->clip];
frame->time += clip->tickPerSecond * frame->scale * DELTA;
clip->playTime += frame->scale * DELTA;
if (frame->time >= 1.0f)
{
frame->curFrame = (frame->curFrame + 1) % (clip->frameCount - 1);
frame->time -= 1.0f;
}
clip->Excute();
}
{
Frame* frame = &motion->next;
if (frame->clip < 0) return;
ModelClip* clip = clips[frame->clip];
motion->tweenTime += DELTA / motion->takeTime;
if (motion->tweenTime >= 1.0f)
{
motion->cur = motion->next;
motion->tweenTime = 0.0f;
motion->next.clip = -1;
motion->next.curFrame = 0;
motion->next.time = 0.0f;
return;
}
frame->time += clip->tickPerSecond * frame->scale * DELTA;
if (frame->time >= 1.0f)
{
frame->curFrame = (frame->curFrame + 1) % (clip->frameCount - 1);
frame->time -= 1.0f;
}
}
}
여기에
clip->playTime += frame->scale * DELTA;
이러한 코드를 추가하여, 현재 play되고있는 시간에 DELTA초에다가 scale값을 곱한만큼 충첩연산을 시켜준다.
이것으로 현재 지속되고있는 시간정보를 알 수 있다.
class Traveler : public CapsuleCollider
{
private:
enum ActionState
{
IDLE,
ATTACK
};
public:
Traveler();
~Traveler();
void Update();
void Render();
void GUIRender();
private:
void Attack();
void EndAttack();
void SetState(ActionState state);
void ReadClips();
private:
ModelAnimator* bodyMesh;
Model* sword;
Transform* rightHand;
ActionState curState;
};
#include "Framework.h"
#include "Traveler.h"
Traveler::Traveler()
{
bodyMesh = new ModelAnimator("Traveler");
bodyMesh->SetShader(L"Model/2DTextureModel.hlsl");
bodyMesh->Load();
bodyMesh->SetParent(this);
ReadClips();
rightHand = new Transform();
sword = new Model("weapon0");
sword->SetParent(rightHand);
sword->Load();
}
Traveler::~Traveler()
{
delete bodyMesh;
delete sword;
delete rightHand;
}
void Traveler::Update()
{
Attack();
rightHand->SetWorld(bodyMesh->GetTransformByNode(36));
UpdateWorld();
bodyMesh->Update();
sword->UpdateWorld();
}
void Traveler::Render()
{
bodyMesh->Render();
sword->Render();
}
void Traveler::GUIRender()
{
bodyMesh->GUIRender();
sword->GUIRender();
}
void Traveler::Attack()
{
if (KEY->Down(VK_LBUTTON))
{
SetState(ATTACK);
}
}
void Traveler::EndAttack()
{
SetState(IDLE);
}
void Traveler::SetState(ActionState state)
{
if (curState == state) return;
curState = state;
bodyMesh->PlayClip(state);
}
void Traveler::ReadClips()
{
bodyMesh->ReadClip("Idle");
bodyMesh->ReadClip("Walk");
bodyMesh->ReadClip("Flair");
bodyMesh->CreateTexture();
bodyMesh->GetClip(ATTACK)->SetEvent(bind(&Traveler::EndAttack, this), 0.8f);
}
애니메이션을 직접 실행해주고 변경해주는 객체를 생성한다.
애니메이션은 Idle과 Walk와 Flair로 지정하였으며,
마우스 왼쪽 버튼을 누르면, Walk가 실행되고, 0.8초 후에 Idle상태로 돌아온다.
시연영상. 마우스 왼쪽클릭을 하면, 걷는동작이 나오고 0.8초뒤에 정지한다.
'서울게임아카데미 교육과정 6개월 국비과정' 카테고리의 다른 글
20231124 37일차 ModelMatarialReImport (0) | 2023.11.24 |
---|---|
20231123 36일차 3인칭 카메라 구현 (0) | 2023.11.24 |
20231121 34일차 모델의 애니메이션 선형보간 (0) | 2023.11.21 |
20231117 33일차 모델의 애니메이션 리깅출력하기 (0) | 2023.11.17 |
20231116 32일차 모델의 애니메이션 리깅저장하기1 (0) | 2023.11.16 |