Git
레포지토리 주소 : [https://github.com/kjinwoo12/UE5Game_LeagueOfWatch]
기준 태그 : 16_LyraCharacter
완성 영상
완성된 캐릭터의 관찰을 위해서 PlayableCharacter
의 카메라를 3인칭으로 옮겼다.
요구사항 확인
- Lyra Starter Game에서 제공하는 기본적인 캐릭터 애니메이션을 League Of Watch에 이식
Lyra Sample Game은 언리얼 엔진5가 출시된 후 개발자들의 적응을 돕기 위해 마켓 플레이스에서 무료로 배포하는 샘플 프로젝트다. 캐릭터, AI, 이펙트, 게임모드 등 하나의 게임을 완성할 때 어떤 방법으로 만들어야 효과적인지 알 수 있다.
Lyra Sample Game의 캐릭터 애니메이션 블루프린트에는 게임에서 일반적으로 만들어야 하는 캐릭터 애니메이션이 구현되어 있다. Lyra Starter Game에 있는 애니메이션 블루프린트를 내 프로젝트에 이주시켜서 League of watch 개발에 사용한다면 큰 노력 없이 적당한 퀄리티의 캐릭터를 만들 수 있을 것이라고 생각한다.
또, 애니메이션 블루프린트가 어떤 방식으로 만들어졌는지 확인하면서 내 프로젝트에 성공적으로 이주시킨다면 에픽 게임즈에서 권장하는 캐릭터 애니메이션을 만드는 노하우를 얻을 수 있을 것이다.
ABP_Mannequin_Base
분석
이벤트 그래프
이벤트 그래프를 살펴보면 간단한 튜토리얼과 함께 예제 코드와 설명이 있는 것을 확인할 수 있다.
요약하자면 성능 향상을 위해 애니메이션 블루프린트에서는 이벤트 그래프는 사용하지 않고, BlueprintThreadSafeUpdateAnimation
함수를 사용한다는 뜻이다. 이벤트 그래프를 사용하던 UE4와는 달라졌다.
BlueprintThreadSafeUpdateAnimation
BlueprintThreadSafeUpdateAnimation
을 들어가면 다시 AnimBP Tour를 확인할 수 있다. 요야갛자면 BlueprintThreadSafeUpdateAnimation
에서 게임 개체의 데이터를 가져오는 방법으로 Property access
를 사용하는 방법을 알려준다.
코드를 살펴보면 Property Access
를 사용해서 게임 개체의 데이터를 애니메이션 재생에 필요한 데이터로 처리하는 모습을 볼 수 있다. - Update Location Data : 개체의 위치 데이터 처리 - Update Rotation Data : 개체의 회전 데이터 처리 - Update Velocity Data : 개체의 속도 데이터 처리 - Update Acceleration Data : 개체의 속도 변화 데이터 처리 - Update Wall Detection Heuristic : 개체가 벽에 부딪치는 상태인지 확인 - Update Character State Data : 개체의 상태 데이터(앉아있는지, 공중에 떠있는지, 총으로 조준하는 중인지 등등) 처리 - Update Blend Weight Data : 애니메이션 블랜딩 비중 처리 - Update Root Yaw Offset : [Turn In Place](https://www.youtube.com/watch?v=3zVh1-4zmy4&ab\_channel=MattAspland)(제자리 회전)을 위한 Yaw 데이터 처리 - Update Aiming Data : 조준 방향 관련 데이터 처리 - Update Jump Fall Data : 점프/낙하 관련 데이터 처리
각각의 함수는 모두 애니메이션 그래프에서 데이터를 사용할 수 있도록 데이터 처리 후 변수에 값을 저장하는 형태로 되어있다. 어떻게 사용하는지는 애니메이션 그래프를 확인해볼 필요가 있다.
AnimGraph
애니메이션 그래프에 들어가면 확인할 수 있는 튜토리얼이다. 요약하면 이 애니메이션 그래프에서는 애니메이션을 직접 참조하지 않고, 재사용할 수 있도록 특점 지점에 애니메이션을 재생할 수 있는 진입점을 제공하며 동시에 여러 진입점에서 받아온 애니메이션을 블랜딩 한다는 뜻이다.
https://docs.unrealengine.com/5.0/ko/animation-blueprint-linking-in-unreal-engine/
애니메이션 블루프린트 링크하기
애니메이션 블루프린트 링크 및 템플릿을 사용하여 애니메이션 블루프린트 로직을 모듈화합니다.
docs.unrealengine.com
애니메이션 블루프린트를 모듈화 하기 위한 장치로 AnimLayerInterface
를 사용한다.
Locomotion
캐릭터의 기본적인 이동 관련 움직임을 담당하는 부분이다. LeftHandPose_OverrideState의 경우 ABP_ItemAnimLayersBase에서 구현되었지만 자식 애니메이션 블루프린트에서 애님시퀀스 LeftHands_Override가 전부 값 할당이 되지 않았기 때문에 확인할 수 없었지만 총 굵기에 따라 달라지는 왼손 그립 애니메이션을 위한 것으로 보인다.
스테이트 머신에서 Start(뛰기시작),Cycle(뛰는중), Pivot(방향전환)는 블렌드스페이스가 Additive animation으로 적용되었지만 나머지는 전부 업데이트 시 함수를 바인딩하고 애니메이션 레이어 인터페이스의 레이어를 재생하도록 되어있다. 실제 애니메이션과 연결된 부분은 무기 애니메이션을 담당하는 ABP_ItemAnimLayerBase
를 확인하면 된다. 애니메이션 시퀀스는 ABP_ItemAnimLayerBase
의 자식 애니메이션 블루프린트에서 클래스 디폴드 값으로 변수에 들어간다. 애니메이션 그래프 로직이 있는 ABP_Mannequin_Base
와 ABP_ItemAnimLayersBase
는 다른 애니메이션 블루프린트임에도 ABP_Mannequin_Base
의 애님 그래프 로직이 잘 작동하는 이유는 애님 레이어 재생 방법을 확인하면 알 수 있다. 아래 링크 참고.
https://docs.unrealengine.com/5.0/ko/animation-blueprint-linking-in-unreal-engine/
블루프린트 이주
ABP_Mannequin_Base
먼저 애니메이션 블루프린트를 이주시키기 전에 ABP_Mannequin_Base
의 부모 클래스인 LyraAnimInstance
를 확인할 필요가 있다. C++ 클래스이기 때문에 단순히 ABP_Mannequin_Base
를 액션->이주로 LeagueOfWatch에 옮길 수 없기 때문이다. C++ 클래스를 블루프린트로 바꾸거나 C++ 코드를 가져와 빌드해야 한다.ABP_Mannequin_Base
가 ULyraAnimInstance
에서 사용하는 것은 GroundDistance
변수다. ULyraCharcterMovementComponent
에서 라인트레이스로 바닥과 액터 사이의 거리를 측정한다.
void ULyraAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
const ALyraCharacter* Character = Cast<ALyraCharacter>(GetOwningActor());
if (!Character)
{
return;
}
ULyraCharacterMovementComponent* CharMoveComp = CastChecked<ULyraCharacterMovementComponent>(Character->GetCharacterMovement());
const FLyraCharacterGroundInfo& GroundInfo = CharMoveComp->GetGroundInfo();
GroundDistance = GroundInfo.GroundDistance;
}
애니메이션 업데이트마다 호출되는 NativeUpdateAnimation(float)
에서 오너 액터의 ULyraCharacterMovementComponent
로 바닥과의 거리를 측정한다. 바닥과 거리를 측정하는 기능은 블루프린트로 빼두거나 UAdvanceCharacterMovementComponent
클래스를 만들어 기능을 추가하는 방법으로 LeagueOfWatch 프로젝트에 옮길 수 있다.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "AdvCharacterMovementComponent.generated.h"
USTRUCT(BlueprintType)
struct FAdvCharacterGroundInfo
{
GENERATED_BODY()
FAdvCharacterGroundInfo() :
LastUpdateFrame(0),
GroundDistance(0.0f)
{
}
uint64 LastUpdateFrame;
UPROPERTY(BlueprintReadOnly)
FHitResult GroundHitResult;
UPROPERTY(BlueprintReadOnly)
float GroundDistance;
};
/**
*
*/
UCLASS()
class LEAGUEOFWATCH_API UAdvCharacterMovementComponent : public UCharacterMovementComponent
{
GENERATED_BODY()
public:
const FAdvCharacterGroundInfo& GetGroundInfo();
protected:
FAdvCharacterGroundInfo CachedGroundInfo;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Characters/AdvCharacterMovementComponent.h"
#include "GameFramework/Character.h"
#include "Components/CapsuleComponent.h"
namespace AdvancedCharacter
{
static float GroundTraceDistance = 100000.0f;
FAutoConsoleVariableRef CVar_GroundTraceDistance(TEXT("LyraCharacter.GroundTraceDistance"), GroundTraceDistance, TEXT("Distance to trace down when generating ground information."), ECVF_Cheat);
};
const FAdvCharacterGroundInfo& UAdvCharacterMovementComponent::GetGroundInfo()
{
if(!CharacterOwner || (GFrameCounter == CachedGroundInfo.LastUpdateFrame))
{
return CachedGroundInfo;
}
if(MovementMode == MOVE_Walking)
{
CachedGroundInfo.GroundHitResult = CurrentFloor.HitResult;
CachedGroundInfo.GroundDistance = 0.0f;
}
else
{
const UCapsuleComponent* CapsuleComponent = CharacterOwner->GetCapsuleComponent();
check(CapsuleComponent);
const float CapsuleHalfHeight = CapsuleComponent->GetUnscaledCapsuleHalfHeight();
const ECollisionChannel CollisionChannel = (UpdatedComponent ? UpdatedComponent->GetCollisionObjectType() : ECC_Pawn);
const FVector TraceStart(GetActorLocation());
const FVector TraceEnd(TraceStart.X, TraceStart.Y, (TraceStart.Z - AdvancedCharacter::GroundTraceDistance - CapsuleHalfHeight));
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(LyraCharacterMovementComponent_GetGroundInfo), false, CharacterOwner);
FCollisionResponseParams ResponseParam;
InitCollisionParams(QueryParams, ResponseParam);
FHitResult HitResult;
GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, CollisionChannel, QueryParams, ResponseParam);
CachedGroundInfo.GroundHitResult = HitResult;
CachedGroundInfo.GroundDistance = AdvancedCharacter::GroundTraceDistance;
if(MovementMode == MOVE_NavWalking)
{
CachedGroundInfo.GroundDistance = 0.0f;
}
else if(HitResult.bBlockingHit)
{
CachedGroundInfo.GroundDistance = FMath::Max((HitResult.Distance - CapsuleHalfHeight), 0.0f);
}
}
CachedGroundInfo.LastUpdateFrame = GFrameCounter;
return CachedGroundInfo;
}
이후 ABP_Mannequin_Base
의 부모 클래스를 AnimInstance
로 변경한 다음에 BlueprintAnimationUpdate
를 오버라이드하고 아래처럼 블루프린트 코드를 추가한다. ULyraAnimInstance::NativeUpdateAnimation
를 대체하는 코드다.
ABP_UnarmedAnimLayers
실제 애니메이션 재생을 위해 빈 손으로 움직이는 애니메이션 블루프린트인 ABP_UnarmedAnimLayers
를 이주한다. 이주 전 반드시 Animation Warping
플러그인과 Animation Locomotion Library
플러그인을 활성화하자.
실행하면 FootstepEffectTagModifier
에서 많은 오류가 나타나는 것을 확인할 수 있다. 이주되지 못한 C++ 코드 때문으로 보인다.AnimNotify_LyraContextEffects
를 추가하면 해결된다. AnimNotify_LyraContextEffect
도 필요한 다른 C++ 코드가 많아 글에 다 적지 못하니 Git Repo에서 Source/*/Feedback/ContextEffects/*
를 확인하면 된다.
'게임개발일지 > 리그오브워치' 카테고리의 다른 글
개발 일시중단 (0) | 2024.06.18 |
---|---|
[리그오브워치개발일지] #17 캐릭터 카메라 관련 기능 (0) | 2024.02.04 |
[리그오브워치개발일지] #15 기본 게임 규칙 : 인게임 접속 후 시작 (1) | 2024.01.16 |
[리그오브워치개발일지] #14 MultiplayLibrary 플러그인 추가 (0) | 2024.01.10 |
[리그오브워치개발일지] #13 유저 인터페이스 : 캐릭터 선택 (3) 선택 규칙 (0) | 2023.12.11 |