728x90
반응형
- 정리
- 애니메이션 몽타주와 콤보 공격의 구현
- 애니메이션 몽타주 활용 방법의 이해
- 데이터 에셋을 활용한 콤보 로직의 구현
- 언리얼 델리게이트를 사용한 이벤트 콜백의 구현
- 애니메이션 몽타주와 콤보 공격의 구현
- 애니메이션 몽타주
- 몽타주(Montage) : 이미지 일부를 잘라내 한 화면에서 합성하는 회화 기법
- 애니메이션 클립을 잘라내고 합성한 후 이를 재생하는 애니메이션 기능
- 애니메이션 클립을 모아둔 다수의 섹션으로 구성되어 있음
- 섹션끼리 연동할 수 있으며, 스크립트를 통해 원하는 섹션으로 건너뛸 수 있음
- 애니메이션 몽타주를 통해 세션을 만들고 위 그림 오른쪽 아래를 보면 Montage Sections에 화살표로 연결이 되어 있다.
- 이는 연결이 되어 있다는 뜻이고, 연결을 끊으려면 화살표를 클릭하여 Remove link를 해주면 된다.
// Combo Action Section
protected:
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = Animation)
TObjectPtr<class UAnimMontage> ComboActionMontage;
void ProcessComboCommand();
- ABCharacterBase.h
#include "Animation/AnimMontage.h"
// ...
void AABCharacterBase::ProcessComboCommand()
{
// 스켈레탈 메쉬의 GetAnimInstance 함수를 호출하여 AnimInstance에 대한 포인터 가져오기
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
AnimInstance->Montage_Play(ComboActionMontage);
}
- ABCharacterBase.cpp
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UInputAction> AttackAction;
// Attack
void Attack();
- ABCharacterPlayer.h
// Attack 액션에 대해서 (생성자)
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionAttackRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_Attack.IA_Attack'"));
if (nullptr != InputActionAttackRef.Object)
{
AttackAction = InputActionAttackRef.Object;
}
void AABCharacterPlayer::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// ...
EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::Attack); // 입력 Attack액션이 발동되었을 때, Attack함수 바인딩
}
// Attack
void AABCharacterPlayer::Attack()
{
ProcessComboCommand();
}
- ABCharacterPlayer.cpp
// ...
static ConstructorHelpers::FClassFinder<APawn> DefaultPawnClassRef(TEXT("/Script/Engine.Blueprint'/Game/ArenaBattle/Blueprint/BP_ABCharacterPlayer.BP_ABCharacterPlayer_C'"));
// ...
- ABGameMode.cpp
- 플레이 캐릭터도 BP로 변경해준다.
- ABCharacterBase.h에서 Montage를 멤버변수로 설정해 BP상에서 적용할 수 있도록 해준다.
- ABCharacterBase.cpp에서 스켈레탈 메쉬의 GetAnimInstance()함수를 호출하여 AnimInstance에 대한 포인터 가져와 준다.
- UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
- 콤보 공격의 기획
- 콤보 정보를 저장한 데이터 에셋의 생성 (ArenaBattleCombo)
- 각 콤보마다 입력을 테스트하는 프레임을 지정 (마지막 프레임 제외)
- 테스트 프레임 전에 입력이 들어오면 다음 몽타주 섹션으로 이어서 재생
- 테스트 프레임보다 입력이 늦으면 해당 섹션을 마저 플레이하고 종료
protected:
virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData); // Data세팅 함수
UPROPERTY(EditAnywhere,Category = CharacterControl, Meta = (AllowPrivateAccess = "true"))
TMap<ECharacterControlType, class UABCharacterControlData*> CharacterControlManager;
// Combo Action Section
protected:
UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = Animation)
TObjectPtr<class UAnimMontage> ComboActionMontage;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = "true"))
TObjectPtr<class UABComboActionData> ComboActionData;
void ProcessComboCommand();
void ComboActionBegin(); // 몽타주가 시작될 때 호출
void ComboActionEnd(class UAnimMontage* TargetMontage, bool IsProperlyEnded); // 몽타주가 모두 종료가 됐을 때 호출 / 몽타주에 설정된 델리게이트를 통해서 바로 호출될 수 있도록 파라미터 설정
void SetComboCheckTimer(); // 타이머를 발동 시킴
void ComboCheck(); // 타이머가 발동했을 때 입력이 들어왔는지 체크하는 함수
int32 CurrentCombo = 0; // 현재 몇번째 콤보가 시작되고 있는지 체크를 위한 (0 = 시작안함, 1이상 = 시작)
FTimerHandle ComboTimerHandle;
bool HasNextComboCommand;
- ABCharacterBase.h
- 애니메이션 몽타주를 관리할 DataAsset을 만들고, Combo Action에 필요한 멤버 변수와 함수들을 만들어 준다.
- void ComboActionBegin();
- 몽타주가 시작될 때 호출되는 함수
- void ComboActionEnd(class UAnimMontage* TargetMontage, bool IsProperlyEnded);
- 몽타주가 모두 종료가 됐을 때 호출 / 몽타주에 설정된 델리게이트를 통해서 바로 호출될 수 있도록 파라미터 설정
- void SetComboCheckTimer();
- 타이머를 발동 시킴
- void ComboCheck();
- 타이머가 발동했을 때 콤보 어택에 맞는 입력이 들어왔는지 체크하는 함수
- void ComboActionBegin();
#include "ABComboActionData.h" // 에셋에 저장되어 있는 각 프레임 정보, 인덱스가 유효한지 검사를 위한
void AABCharacterBase::ProcessComboCommand()
{
if (CurrentCombo == 0)
{
// 콤보가 시작되면 리턴
ComboActionBegin();
return;
}
// ComboTimerHandle가 유효한가? 실행중인가?
if (!ComboTimerHandle.IsValid())
{
HasNextComboCommand = false;
}
else
{
HasNextComboCommand = true;
}
}
// 콤보 시작
void AABCharacterBase::ComboActionBegin()
{
// Combo Status
CurrentCombo = 1;
// Movement Setting
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None); // Move_None으로 설정하면 움직임이 없도록 설정할 수 있음
// Animation Setting
const float AttackSpeedRate = 1.0f;
// 스켈레탈 메쉬의 GetAnimInstance 함수를 호출하여 AnimInstance에 대한 포인터 가져오기
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
AnimInstance->Montage_Play(ComboActionMontage, AttackSpeedRate); // 몽타주 재생
FOnMontageEnded EndDelegate;
EndDelegate.BindUObject(this, &AABCharacterBase::ComboActionEnd);
AnimInstance->Montage_SetEndDelegate(EndDelegate, ComboActionMontage); // 바인딩된 함수 EndDelegate와 ComboActionMontage를 넘겨주어 바인딩 된 AABCharacterBase::ComboActionEnd 함수가 실행되도록
// 콤보 타이머 무효화후, 콤보 타이머 시작
ComboTimerHandle.Invalidate();
SetComboCheckTimer();
}
// 콤보 끝
void AABCharacterBase::ComboActionEnd(UAnimMontage* TargetMontage, bool IsProperlyEnded)
{
// Assertion 함수를 사용해서 몽타주가 끝날땐 CurrentCombo가 절때 0이 될 수 없으니, 검증 + 0이 나오면 에러
ensure(CurrentCombo != 0);
CurrentCombo = 0;
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking); // 다시 움직임 가능하게 변경
}
// 콤보 타이머를 발동시키는 함수
void AABCharacterBase::SetComboCheckTimer()
{
int32 ComboIndex = CurrentCombo - 1;
ensure(ComboActionData->EffectiveFrameCount.IsValidIndex(ComboIndex));
// 콤보가 가능한 타이밍의 시간 정의 로직
const float AttackSpeedRate = 1.0f;
float ComboEffectiveTime = (ComboActionData->EffectiveFrameCount[ComboIndex] / ComboActionData->FrameRate) / AttackSpeedRate;
if (ComboEffectiveTime > 0.0f)
{
GetWorld()->GetTimerManager().SetTimer(ComboTimerHandle, this, &AABCharacterBase::ComboCheck, ComboEffectiveTime, false);
}
}
// 타이머가 발동했을 때 입력이 들어왔는지 체크하는 함수
void AABCharacterBase::ComboCheck()
{
// 타이머 초기화
ComboTimerHandle.Invalidate();
// 입력이 들어왔다면
if (HasNextComboCommand)
{
// AnimInstance 가져오기
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
CurrentCombo = FMath::Clamp(CurrentCombo + 1, 1, ComboActionData->MaxComboCount);
// 재생할 세션 이름 정의후 해당 애니메이션 몽타주로 점프
FName NextSection = *FString::Printf(TEXT("%s%d"), *ComboActionData->MontageSectionPrefix, CurrentCombo);
AnimInstance->Montage_JumpToSection(NextSection, ComboActionMontage);
// 다시 콤보 타이머 발동
SetComboCheckTimer();
HasNextComboCommand = false;
}
}
- void AABCharacterBase::ComboActionBegin() 를 통해 콤보가 시작이 되면, 캐릭터의 움직임을 멈추고, 플레이할 애니메이션 몽타주를 가져와 플레이해주며, 콤보가 끝날때의 이벤트를 받는 델리게이트를 콤보가 끝나면 발동할 함수를 바인딩해주면서 설정한다.
- 설정을 해주고 나면, ComboTimerHandle.Invalidate();를 통해 타이머를 무효화시켜준 뒤, void AABCharacterBase::SetComboCheckTimer()를 통해 콤보에 대한 타이머를 발동시켜준다.
- 해당 과정에서 타이머가 끝나기전에 입력이 들어왔는지는 void AABCharacterBase::ComboCheck()에서 처리해 주며, 콤보 액션이 될 수 있도록 시간안에 입력이 들어왔다면 다음 콤보 애니메이션으로 몽타주로 점프하고 void AABCharacterBase::SetComboCheckTimer()로 다시 콤보 타이머를 발동시켜준다.
해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part2 -언리얼 게임 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 | 이득우 - 인프런
청강문화산업대학교에서 언리얼 엔진, 게임 수학, UEFN 게임제작을 가르치고 있습니다. - 이득우의 언리얼 C++ 프로그래밍, 넥슨 코리아 공식 교육 교재 선정 2023 - 스마일게이트 언리얼 프로그래
www.inflearn.com
728x90
'공부 > 이득우의 언리얼 프로그래밍' 카테고리의 다른 글
[Study] Part 2 - 캐릭터 스탯과 위젯 (7/15) (0) | 2024.06.04 |
---|---|
[Study] Part 2 - 캐릭터 공격 판정 (6/15) (0) | 2024.06.03 |
[Study] Part 2 - 캐릭터 애니메이션 설정 (4/15) (2) | 2024.06.01 |
[Study] Part 2 - 캐릭터 컨트롤 설정 (3/15) (0) | 2024.05.28 |
[Study] Part 2 - 캐릭터와 입력 시스템 (2/15) (0) | 2024.05.20 |