캐릭터가 어떤 무기를 들고있는지를 나타내는 ECharacterState에 더해, 현재 어떤 액션을 취하고 있는지를 나타내는 EActionState 열거형을 만들었다. 현재 EActionState 값에 따라 플레이어가 할 수 있는 행동을 정해줄 수 있다.
예를 들어, 캐릭터가 구르는 중에는 조준을 할 수 없고, 근접공격 중에는 조준을 할 수 없도록 해야 한다.
EActionState: 현재 캐릭터의 액션 상태를 나타냄
#pragma once
UENUM(BlueprintType)
enum class ECharacterState : uint8
{
ECS_Unarmed UMETA(DisplayName = "Unarmed"),
ECS_ArmedWithPistol UMETA(DisplayName = "Armed With Pistol"),
ECS_ArmedWithRifle UMETA(DisplayName = "Armed With Rifle")
};
UENUM(BlueprintType)
enum class EActionState : uint8
{
EAS_Neutral UMETA(DisplayName = "Neutral"),
EAS_Attacking UMETA(DisplayName = "Attacking"),
EAS_Rolling UMETA(DisplayName = "Rolling"),
EAS_Aiming UMETA(DisplayName = "Aiming"),
};
UENUM(BlueprintType)은 이 enum을 리플렉션 시스템에 포함시키며, 블루프린트에서 열거형의 형태로 사용할 수 있게 한다는 의미이다.
우선 중립, 공격(근접 공격), 구르기, 조준의 4가지 상태를 만들었다. 개발이 진행됨에 따라 더 추가될 수 있다.
그리고 캐릭터의 조준, 중립 상태를 IsAiming 플래그가 아닌 EActionState를 토대로 전환하도록 하였다.
void AKannaCharacter::Aim()
{
if(CharacterState == ECharacterState::ECS_Unarmed) return; //비무장 상태일 시 Action State 바꾸지 않음, 카메라만 줌 인
ActionState = EActionState::EAS_Aiming;
}
void AKannaCharacter::ReleaseAim()
{
ActionState = EActionState::EAS_Neutral;
}
Aim과 ReleaseAim은 각각 마우스 오른쪽을 홀드할 때, 뗄 때의 콜백 함수이다. 함수는 실행되면서 캐릭터의 액션 상태를 전환한다.
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/KannaAnimInstance.h"
#include "Character/KannaCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"
void UKannaAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
KannaCharacter = Cast<AKannaCharacter>(TryGetPawnOwner());
if (KannaCharacter)
{
KannaCharacterMovement = KannaCharacter->GetCharacterMovement();
}
}
void UKannaAnimInstance::NativeUpdateAnimation(float DeltaTime)
{
Super::NativeUpdateAnimation(DeltaTime);
if (KannaCharacterMovement)
{
GroundSpeed = UKismetMathLibrary::VSizeXY(KannaCharacterMovement->Velocity);
IsFalling = KannaCharacterMovement->IsFalling();
CharacterState = KannaCharacter->GetCharacterState();
ActionState = KannaCharacter->GetActionState();
}
}
애니메이션 블루프린트의 부모 C++ 클래스. 매 틱마다 CharacterState, ActionState를 불러오고 있다.
이제 근접공격 기능을 구현할 차례이다. 현재 공격 모션은 두 가지가 있다.
이 두 애니메이션 시퀀스를 합쳐, AM_Attack이라는 애니메이션 몽타주를 만들었다.
애니메이션 몽타주를 이용하면 여러 애니메이션 시퀀스를 하나로 합친 에셋을 만들어, C++ 코드나 블루프린트 노드를 통해 재생을 컨트롤할 수 있다.
자세한 내용은 https://docs.unrealengine.com/5.3/ko/animation-montage-in-unreal-engine/
위의 이미지에서 AttackEnd라는 하얀 라벨은 Notify로, 애니메이션 블루프린트의 이벤트 그래프에서 Event 노드로서 사용할 수 있다.
언리얼의 Notify는 유니티의 Animation Event와 동일하다.
다음은 이 애니메이션 몽타주를 재생하는 코드이다.
void AKannaCharacter::PlayAttackMontage()
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage) //null check
{
AnimInstance->Montage_Play(AttackMontage);
const int32 RandNum = FMath::RandRange(1, 2);
FName SectionName = FName();
switch (RandNum)
{
case 1:
SectionName = FName("Attack1");
break;
case 2:
SectionName = FName("Attack2");
break;
default:
break;
}
AnimInstance->Montage_JumpToSection(SectionName, AttackMontage);
}
}
먼저 Mesh를 통해 애니메이션 인스턴스를 참조해온다.
애니메이션 인스턴스와 AttackMontage의 널 체크 후, AnimInstance->Montage_Play를 통해 몽타주를 재생한다.
유니티의 Animation.Play랑 같은 기능이지만, 언리얼에서는 자동으로 전후 애니메이션과 블렌딩이 되어 더 자연스럽다.
RandNum는 1~2 범위 난수이다. switch문을 통해 재생할 섹션의 이름을 정한다.
AnimInstance->Montage_JumpToSection(SectionName, AttackMontage)은 함수 이름 그대로 애니메이션 몽타주 안에서 파라미터로 받은 섹션으로 점프한다.
결과적으로 펀치와 킥 중 하나가 랜덤으로 재생된다.
다음은 PlayAttackMontage()를 호출하는 코드이다.
void AKannaCharacter::Attack()
{
if (ActionState != EActionState::EAS_Neutral || CharacterState == ECharacterState::ECS_Unarmed) return;
//GetCharacterMovement()->DisableMovement(); - 수정(2023.12.20)
Controller->SetIgnoreMoveInput(true); //공격 중에는 움직이지 않도록
PlayAttackMontage();
ActionState = EActionState::EAS_Attacking;
}
void AKannaCharacter::AttackEnd()
{
ActionState = EActionState::EAS_Neutral;
//GetCharacterMovement()->MovementMode = EMovementMode::MOVE_Walking; - 수정(2023.12.20)
Controller->SetIgnoreMoveInput(false); //공격 완료 후 Movement Mode 초기화
}
Attack(), AttackEnd() 함수이다. Attack()은 근접공격 인풋의 콜백 함수로 바인딩되어있고, AttackEnd()는 위의 애니메이션 몽타주의 Notify에 바인딩되어있다.
공격은 중립상태일 때에만 가능하며, 함수 안에서 자동으로 캐릭터의 EActionState를 전환한다.
공격을 시작하면 공격이 끝날 때 까지 EActionState가 EAS_Attacking으로 유지되어, 빠르게 또 다시 근접공격 키를 눌러도 Attack()의 if문에서 걸러진다.
만약 이렇게 하지 않으면, 근접공격 키를 연타하면 계속 근접공격이 시작되어 칸나가 부자연스럽게 덜덜거리는 것을 볼 수 있다.
또한, 근접공격중에는 캐릭터의 움직임을 없애주어야 한다. 그렇지 않으면 공격하면서 미끄러지는 것 처럼 보이게 된다.
GetCharacterMovement()의 멤버함수로 DisableMovement()는 있는데, EnableMovement()는 없어 의아했었다.
검색 결과 실제로 해당하는 함수는 따로 없는 것이 맞고, MovementMode를 다시 할당해주면 된다고 한다.
따라서, 사실상의 기본값인 EMovementMode::MOVE_Walking을 할당해주었다.
(나머지는 Flying, Falling, Swimming, ... 이런거다.)
2023.12.20 수정)
EMovement를 한번 None으로 했다가 바꿀 경우 근접 공격 후 구르기 시 캐릭터가 안움직이는 버그가 있었다.
따라서 MovementMode를 만지는 대신 Controller->SetIgnoreMoveInput 함수를 사용하는 것으로 바꿨다.
결과:
https://www.youtube.com/watch?v=ATc_mRC9rP8
'언리얼 엔진 5 > 개발 일지' 카테고리의 다른 글
[UE5] 블루아카이브 TPS게임 개발일지 (6) - TPS 애니메이션 (Layered Blend, Blend Space) (0) | 2023.12.15 |
---|---|
[UE5] 블루아카이브 TPS게임 개발일지 (5) - Mixamo Converter 사용, 구르기 구현 (0) | 2023.12.14 |
[UE5] 블루아카이브 TPS게임 개발일지 (3) - 툰 쉐이딩 (0) | 2023.12.03 |
[UE5] 블루아카이브 TPS게임 개발일지 (2) - 캐릭터 state 추가, 애니메이션 개선 (0) | 2023.12.02 |
[UE5] 블루아카이브 TPS게임 개발일지 (1) - 조준 기능 구현 (0) | 2023.12.01 |