언리얼5 - 캐릭터 콤보 액션
애니메이션 몽타주 시스템의 활용
데이터 애셋과 델리게이트를 활용한 콤보 공격의 구현
애니메이션 몽타주
몽타주(Montage) : 이미지 일부를 잘라내 한 화면에서 합성하는 회화 기법
애니메이션 클립을 잘라내고 합성한 후 이를 재생하는 애니메이션 기능
애니메이션 클립을 모아둔 다수의 섹션으로 구성되어 있음
섹션끼리 연동할 수 있으며, 스크립트를 통해 원하는 섹션으로 건너뛸 수 있음
폴더에서 우클릭 Animation - Animation Montage
스켈레톤 선택 후 생성. AM_ComboAttack. (AM: 애니메이션 몽타주)
애니메이션 몽타주를 열고 Default 섹션을 누르고 애니메이션을 끌어서 배치
Default 섹션을 재생하면 모션이 연속적으로 재생됨.
Montage 칸에 우클릭하면 섹션을 추가할 수 있음
4가지 섹션을 만들고 위치 조절
Montage Sections 창에 만든 섹션들이 어떻게 연동되는지 지정되어 있음.
화살표는 두 가지 섹션이 서로 연결되어 있다는 것을 의미.
각 섹션을 개별적으로 관리하고 싶다면 화살표로 링크되어 있는 것을 클릭해서 지울 수 있음.
연결을 제거하면 재생했을 때 섹션이 끝나는 곳에서 끊김.
각 섹션을 몽타주 섹션을 통해 제어할 수 있게 됨. (특정 키를 눌렀을 때 섹션 1이 실행되는 식.)
각 섹션을 수동으로 이어서 연결하고 싶을 때는 AnimInstance 함수를 이용해 원하는대로 섹션들을 옮겨다닐 수 있음.
Attack InputAction을 만어주고 각 InputMappingContext에서 공격키 추가
Player 코드에 공격 키 맵핑과 함수 추가
UCLASS()
class ARENABATTLE_API AABCharacterPlayer : public AABCharacterBase
{
GENERATED_BODY()
...
protected:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivate = "true"))
TObjectPtr<class UInputAction> AttackAction;
...
void Attack();
}
ABCharacterPlayer.h
AABCharacterPlayer::AABCharacterPlayer()
{
...
static ConstructorHelpers::FObjectFinder<UInputAction> InputActionAttackRef(TEXT("/Script/EnhancedInput.InputAction'/Game/ArenaBattle/Input/Actions/IA_Attack.IA_Attack'"));
if (InputActionQuaterMoveRef.Object)
{
AttackAction = InputActionAttackRef.Object;
}
...
}
void AABCharacterPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
...
EnhancedInputComponent->BindAction(AttackAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::Attack);
}
void AABCharacterPlayer::Attack()
{
}
ABCharacterPlayer.cpp
왼쪽 클릭을 하면 Attack함수가 호출됨.
실제로 몽타주 애니메이션을 재생하는 것은 기본 클래스에서 구현해 차후에 NPC와 플레이어가 몽타주 애니메이션을 같이 재생하도록 설계.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "ABCharacterBase.generated.h"
// 컨트롤 데이터 ENUM
UENUM()
enum class ECharacterControlType : uint8
{
Shoulder,
Quater
};
UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter
{
GENERATED_BODY()
public:
AABCharacterBase();
protected:
// 캐릭터 컨트롤 데이터 애셋을 입력으로 받음
// Pawn과 Movement 데이터 설정
virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData);
// 두 가지 애셋 오브젝트를 얻어올 Map
UPROPERTY(EditAnywhere, Category = CharacterControl, Meta = (AllowPrivateAccess = "true"))
TMap<ECharacterControlType, class UABCharacterControlData*> CharacterControlManager;
// Combo Action Section (몽타주)
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Categore = Animation)
TObjectPtr<class UAnimMontage> ComboActionMontage;
void ProcessComboCommand();
};
ABCharacterBase.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimMontage.h"
...
void AABCharacterBase::ProcessComboCommand()
{
// 몽타주를 재생하기 위해서는 AnimInstance의 포인터를 가져와야함.
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
// Montage_Play()에 몽타주 애셋을 지정해서 특정한 몽타주를 재생하도록 설정할 수 있음.
AnimInstance->Montage_Play(ComboActionMontage, 1.0);
}
ABCharacterBase.cpp
이후 생성자 코드에서 몽타주 애셋을 지정해줘야하는데, ConstructerHelpers를 사용할 수 있지만 매번 몽타주 애셋을 변경할 때마다 코드를 고치는 것은 비효율적이기 때문에 블루프린트로 ABCharacter 클래스를 확장해서 몽타주 에셋은 블루프린트로 지정.
BP_ABCharacter를 열면 ComboAttackMontage가 있다.
ComboAttackMontage를 AM_ComboAttack으로 지정
ABP_ABCharacter 애니메이션 블루프린트에 몽타주를 재생할 수 있는 Slot 노드를 추가해서 끼워주면 몽타주 애니메이션이 기존에 있던 모든 애니메이션을 덮어써서 나가게 됨. 만약 몽타주가 재생되지 않는다면 기존에 작업했던 애니메이션 모션들의 재생됨.
void AABCharacterPlayer::Attack()
{
ProcessComboCommand();
}
ABCharacterPlayer.cpp
ABCharacterBase의 ProcessComboCommand함수와 CharacterPlayer의 Attack함수가 연동되도록 기능 추가.
static ConstructorHelpers::FClassFinder<APawn> DefaultPawnClassRef(TEXT("/Script/Engine.Blueprint'/Game/ArenaBattle/Blueprint/BP_ABCharacterPlayer.BP_ABCharacterPlayer_C'"));
if (DefaultPawnClassRef.Class)
{
DefaultPawnClass = DefaultPawnClassRef.Class;
}
ABGameMode.cpp
ABGameMode 클래스에도 디폴트 폰을 C++ 캐릭터가 아닌 BP_ABCharacterPlayer로 변경.
변경 후 빌드를 해도 적용되지 않으면 에디터를 껐다가 다시 빌드 후 재실행.
게임을 실행하고 왼쪽 클릭을 하면 공격이 잘 나가는 것을 볼 수 있다.
콤보 공격의 기획
콤보 정보를 저장한 데이터 애셋의 생성 (ArenaBattleCombo)
각 콤보마다 입력을 테스트하는 프레임을 지정 (마지막 프레임 제외)
테스트 프레임 전에 입력이 들어오면 다음 몽타주 섹션으로 이어서 재생
테스트 프레임보다 입력이 늦으면 해당 섹션을 마저 플레이하고 종료
콤보 공격 순서도
델리게이트를 사용해서 이벤트 등록
PrimaryDataAsset을 상속받은 ABComboActionData 클래스 생성
새 폴더를 추가하고 ABCharacterActionData로 DataAsset 생성
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ABComboActionData.generated.h"
/**
*
*/
UCLASS()
class ARENABATTLE_API UABComboActionData : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UABComboActionData();
UPROPERTY(EditAnywhere, Category = Name)
FString MontageSectionNameProfix;
UPROPERTY(EditAnywhere, Category = Name)
uint32 MaxComboCount;
// 프레임의 기준 재생 속도를 지정해
// 정확한 타이밍에 체크가 발생하도록 설정
UPROPERTY(EditAnywhere, Category = Name)
float FrameRate;
// 입력이 사전에 입력됐는지를 감지하는 프레임 지정
UPROPERTY(EditAnywhere, Category = Name)
TArray<float> EffectiveFrameCount;
};
ABComboActionData.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABComboActionData.h"
UABComboActionData::UABComboActionData()
{
}
UABComboActionData.cpp
몽타주 섹션 이름을 ComboAttack 1, 2, 3, 4로 정했기 때문에 때문에 이름은 ComboAttack,
카운트는 4, 기본 프레임 레이트인 30프레임으로 설정. 각 프레임마다 입력을 체크할 프레임 지정. 각각 17,17,20,-1 프레임. 마지막 섹션은 조사할 필요가 없기 때문에 -1.
ComboAttack1 섹션의 17프레임. 이 때에 입력이 들어왔는지 안들어왔는지 체크하고 바로 ComboAttack2 섹션으로 건너뛰어서 다음 액션을 진행, 마찬가지로 17프레임에 입력 검사 후 ComboAttaco3로 전환되도록 코드 구현.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "ABCharacterBase.generated.h"
// 컨트롤 데이터 ENUM
UENUM()
enum class ECharacterControlType : uint8
{
Shoulder,
Quater
};
UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter
{
GENERATED_BODY()
public:
AABCharacterBase();
protected:
// 캐릭터 컨트롤 데이터 애셋을 입력으로 받음
// Pawn과 Movement 데이터 설정
virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData);
// 두 가지 애셋 오브젝트를 얻어올 Map
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();
// 몽타주가 모두 종료됐을 때 호출
// 몽타주에 설정된 델리게이트를 통해 바로 호출될 수 있도록
// 파라미터 맞추기
// 두 가지의 타입으로 되어 있는데,
// UAnimMotage에 선언되어 있는 델리게이트의 파라미터와 맞춘 것
// (FOnMontageEnded, class UAnimMontage* bool/*bInterrupted*/)
void ComboActionEnd(class UAnimMontage* TargetMontage, bool IsProperlyEnded);
// 현재 콤보가 어디까지 진행됐는지를 저장하기 위한 변수
// 내부에서만 사용할 것이기 때문에 UPROPERTY는 붙이지 않았음
// 0 일때는 콤보가 시작되지 않은 것, 1보다 크거나 같으면 콤보가 시작된 것
int32 CurrentCombo = 0;
};
ABCharacterBase.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimMontage.h"
// Sets default values
AABCharacterBase::AABCharacterBase()
{
// Pawn 기본 설정
// 폰의 회전을 지정하기 위한 값
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// 루트 컴포넌트, 캡슐(Capsule) 설정
// GetCapsuleComponent를 사용해 가져올 수 있음, 헤더 추가
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
GetCapsuleComponent()->SetCollisionProfileName(TEXT(""));
// 움직임(Movement) 설정
// 점프의 크기, 이동 속도 등
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
GetCharacterMovement()->JumpZVelocity = 700.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->MaxWalkSpeed = 500.f;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
// 메쉬(Mesh) 설정
// 애니메이션 모드와 캐릭터들의 모델링 파일을 진행방향에 맞춰 상대 위치와 회전을 지정해주어야 함.
GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -100.0f), FRotator(0.0f, -90.0f, 0.0f));
GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
GetMesh()->SetCollisionProfileName(TEXT("CharacterMesh"));
// 스켈레탈 메쉬 컴포넌트의 실제 애셋 부착
// ThirdPersonCharacter에서 제공하는 메쉬와 클래스 사용
static ConstructorHelpers::FObjectFinder<USkeletalMesh> CharacterMeshRef(TEXT("/Script/Engine.SkeletalMesh'/Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Cardboard.SK_CharM_Cardboard'"));
if (CharacterMeshRef.Object)
{
// 스켈레탈에는 메쉬를 가져와서 등록
GetMesh()->SetSkeletalMesh(CharacterMeshRef.Object);
}
static ConstructorHelpers::FClassFinder<UAnimInstance> AnimInstanceClassRef(TEXT("/Game/ArenaBattle/Animation/ABP_ABCharacter.ABP_ABCharacter_C"));
if (AnimInstanceClassRef.Class)
{
// 애니메이션에는 메쉬가 아니라 클래스 등록
GetMesh()->SetAnimInstanceClass(AnimInstanceClassRef.Class);
}
// CharacterControlManager(Map)에 두 가지 컨트롤 데이터 추가
static ConstructorHelpers::FObjectFinder<UABCharacterControlData> ShoulderDataRef(TEXT("/Script/ArenaBattle.ABCharacterControlData'/Game/ArenaBattle/CharacterControl/ABC_Shoulder.ABC_Shoulder'"));
if (ShoulderDataRef.Object)
{
CharacterControlManager.Add(ECharacterControlType::Shoulder, ShoulderDataRef.Object);
}
static ConstructorHelpers::FObjectFinder<UABCharacterControlData> QuaterDataRef(TEXT("/Script/ArenaBattle.ABCharacterControlData'/Game/ArenaBattle/CharacterControl/ABC_Quarter.ABC_Quarter'"));
if (QuaterDataRef.Object)
{
CharacterControlManager.Add(ECharacterControlType::Quater, QuaterDataRef.Object);
}
}
// 컨트롤 데이터 세팅
void AABCharacterBase::SetCharacterControlData(const UABCharacterControlData* CharacterControlData)
{
// Pawn
bUseControllerRotationYaw = CharacterControlData->bUseControllerRotationYaw;
// CharacterMovement
GetCharacterMovement()->bOrientRotationToMovement = CharacterControlData->bOrientRotationToMovement;
GetCharacterMovement()->bUseControllerDesiredRotation = CharacterControlData->bUseControllerDesiredRotation;
GetCharacterMovement()->RotationRate = CharacterControlData->RotationRate;
}
void AABCharacterBase::ProcessComboCommand()
{
// 콤보가 시작되지 않았다면
if (CurrentCombo == 0)
{
ComboActionBegin();
}
}
void AABCharacterBase::ComboActionBegin()
{
// Combo Status
CurrentCombo = 1;
// Movement Setting
// MovementMode::MOVE_None - 이동기능이 없어짐
// 온전하게 콤보 기능
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
// Animation Setting
// 재생 속도 지정
const float AttackSpeedRate = 1.0f;
// 몽타주를 재생하기 위해서는 AnimInstance의 포인터를 가져와야함.
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
// Montage_Play()에 몽타주 애셋을 지정해서 특정한 몽타주를 재생하도록 설정할 수 있음.
AnimInstance->Montage_Play(ComboActionMontage, AttackSpeedRate);
// 몽타주가 시작되자마자 몽타주가 종료될 때 ComboActionEnd함수가 호출되도록 함
// 구조체처럼 선언하고 그 안에 관련된 함수 정보를 넣음
FOnMontageEnded EndDelegate;
// 구조체에 바인딩할 정보 추가
// 현재 인스턴스의 ComboActionEnd 맵핑
EndDelegate.BindUObject(this, &AABCharacterBase::ComboActionEnd);
// 첫번째 인자에 바인드까지 시킨 구조체를 연결
// 두번째 인자에 몽타주 지정
AnimInstance->Montage_SetEndDelegate(EndDelegate, ComboActionMontage);
}
void AABCharacterBase::ComboActionEnd(UAnimMontage* TargetMontage, bool IsProperlyEnded)
{
// 콤보가 종료될 때 CurrentCombo는 절대 0이 될 수 없음
// Assertion함수를 사용해서 검증
ensure(CurrentCombo != 0);
CurrentCombo = 0;
// 캐릭터 무브먼트 값 복원
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
}
ABCharacterBase.cpp
BP_CharacterPlayer를 열어서 Combo Action Data 지정
저장 후 게임을 실행해보면 공격 중 움직이지 않게 잘 동작.
이후 타이머를 설정해서 해당 타이머가 발동되기 이전에 입력이 들어오면 다음 섹션으로 넘어가는 기능 구현.
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "ABCharacterBase.generated.h"
// 컨트롤 데이터 ENUM
UENUM()
enum class ECharacterControlType : uint8
{
Shoulder,
Quater
};
UCLASS()
class ARENABATTLE_API AABCharacterBase : public ACharacter
{
GENERATED_BODY()
public:
AABCharacterBase();
protected:
// 캐릭터 컨트롤 데이터 애셋을 입력으로 받음
// Pawn과 Movement 데이터 설정
virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData);
// 두 가지 애셋 오브젝트를 얻어올 Map
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();
// 몽타주가 모두 종료됐을 때 호출
// 몽타주에 설정된 델리게이트를 통해 바로 호출될 수 있도록
// 파라미터 맞추기
// 두 가지의 타입으로 되어 있는데,
// UAnimMotage에 선언되어 있는 델리게이트의 파라미터와 맞춘 것
// (FOnMontageEnded, class UAnimMontage* bool/*bInterrupted*/)
void ComboActionEnd(class UAnimMontage* TargetMontage, bool IsProperlyEnded);
// 타이머를 발동시킬 함수
void SetComboCheckTimer();
// 타이머가 발동되면 입력이 들어왔는지 안들어왔느지를 체크하는 함수
void ComboCheck();
// 현재 콤보가 어디까지 진행됐는지를 저장하기 위한 변수
// 내부에서만 사용할 것이기 때문에 UPROPERTY는 붙이지 않았음
// 0 일때는 콤보가 시작되지 않은 것, 1보다 크거나 같으면 콤보가 시작된 것
int32 CurrentCombo = 0;
// 언리얼 엔진 월드에서 제공하는 타이머 기능을 이용해서
// 원하는 시간에 특정 함수를 호출하도록 설정할 수 있는 구조체
FTimerHandle ComboTimerHandle;
// 발동한 타이머 이전에 입력 커맨드가 들어왔는지 점검
// 내부에서만 사용하기 때문에 UPROPERTY를 붙이지 않고,
// 그렇기 때문에 정수형으로 boolean 값을 쓰지 않아도 됨
bool HasNextComboCommand = false;
};
ABCharacterBase.h
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ABCharacterBase.h"
#include "Components/CapsuleComponent.h"
// Component's'임
#include "ABCharacterControlData.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Animation/AnimMontage.h"
#include "ABComboActionData.h"
// Sets default values
AABCharacterBase::AABCharacterBase()
{
// Pawn 기본 설정
// 폰의 회전을 지정하기 위한 값
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
// 루트 컴포넌트, 캡슐(Capsule) 설정
// GetCapsuleComponent를 사용해 가져올 수 있음, 헤더 추가
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
GetCapsuleComponent()->SetCollisionProfileName(TEXT(""));
// 움직임(Movement) 설정
// 점프의 크기, 이동 속도 등
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
GetCharacterMovement()->JumpZVelocity = 700.f;
GetCharacterMovement()->AirControl = 0.35f;
GetCharacterMovement()->MaxWalkSpeed = 500.f;
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
// 메쉬(Mesh) 설정
// 애니메이션 모드와 캐릭터들의 모델링 파일을 진행방향에 맞춰 상대 위치와 회전을 지정해주어야 함.
GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -100.0f), FRotator(0.0f, -90.0f, 0.0f));
GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
GetMesh()->SetCollisionProfileName(TEXT("CharacterMesh"));
// 스켈레탈 메쉬 컴포넌트의 실제 애셋 부착
// ThirdPersonCharacter에서 제공하는 메쉬와 클래스 사용
static ConstructorHelpers::FObjectFinder<USkeletalMesh> CharacterMeshRef(TEXT("/Script/Engine.SkeletalMesh'/Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Cardboard.SK_CharM_Cardboard'"));
if (CharacterMeshRef.Object)
{
// 스켈레탈에는 메쉬를 가져와서 등록
GetMesh()->SetSkeletalMesh(CharacterMeshRef.Object);
}
static ConstructorHelpers::FClassFinder<UAnimInstance> AnimInstanceClassRef(TEXT("/Game/ArenaBattle/Animation/ABP_ABCharacter.ABP_ABCharacter_C"));
if (AnimInstanceClassRef.Class)
{
// 애니메이션에는 메쉬가 아니라 클래스 등록
GetMesh()->SetAnimInstanceClass(AnimInstanceClassRef.Class);
}
// CharacterControlManager(Map)에 두 가지 컨트롤 데이터 추가
static ConstructorHelpers::FObjectFinder<UABCharacterControlData> ShoulderDataRef(TEXT("/Script/ArenaBattle.ABCharacterControlData'/Game/ArenaBattle/CharacterControl/ABC_Shoulder.ABC_Shoulder'"));
if (ShoulderDataRef.Object)
{
CharacterControlManager.Add(ECharacterControlType::Shoulder, ShoulderDataRef.Object);
}
static ConstructorHelpers::FObjectFinder<UABCharacterControlData> QuaterDataRef(TEXT("/Script/ArenaBattle.ABCharacterControlData'/Game/ArenaBattle/CharacterControl/ABC_Quarter.ABC_Quarter'"));
if (QuaterDataRef.Object)
{
CharacterControlManager.Add(ECharacterControlType::Quater, QuaterDataRef.Object);
}
}
// 컨트롤 데이터 세팅
void AABCharacterBase::SetCharacterControlData(const UABCharacterControlData* CharacterControlData)
{
// Pawn
bUseControllerRotationYaw = CharacterControlData->bUseControllerRotationYaw;
// CharacterMovement
GetCharacterMovement()->bOrientRotationToMovement = CharacterControlData->bOrientRotationToMovement;
GetCharacterMovement()->bUseControllerDesiredRotation = CharacterControlData->bUseControllerDesiredRotation;
GetCharacterMovement()->RotationRate = CharacterControlData->RotationRate;
}
void AABCharacterBase::ProcessComboCommand()
{
// 콤보가 시작되지 않았다면
if (CurrentCombo == 0)
{
// 콤보가 시작되고 리턴
ComboActionBegin();
return;
}
// 만약 타이머가 설정되어 있지 않을 때 입력이 들어온다면
// (이미 타이머가 발동되어 시기를 놓쳤거나 더 이상 진행할 필요가 없다면)
if (!ComboTimerHandle.IsValid())
{
HasNextComboCommand = false;
}
// 만약 타이머가 유효하다면
// 체크하기 전에 다음 섹션으로 이동시킬 커맨드가 발동했다는 뜻
else
{
HasNextComboCommand = true;
}
}
void AABCharacterBase::ComboActionBegin()
{
// Combo Status
CurrentCombo = 1;
// Movement Setting
// MovementMode::MOVE_None - 이동기능이 없어짐
// 온전하게 콤보 기능
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
// Animation Setting
// 재생 속도 지정
const float AttackSpeedRate = 1.0f;
// 몽타주를 재생하기 위해서는 AnimInstance의 포인터를 가져와야함.
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
// Montage_Play()에 몽타주 애셋을 지정해서 특정한 몽타주를 재생하도록 설정할 수 있음.
AnimInstance->Montage_Play(ComboActionMontage, AttackSpeedRate);
// 몽타주가 시작되자마자 몽타주가 종료될 때 ComboActionEnd함수가 호출되도록 함
// 구조체처럼 선언하고 그 안에 관련된 함수 정보를 넣음
FOnMontageEnded EndDelegate;
// 구조체에 바인딩할 정보 추가
// 현재 인스턴스의 ComboActionEnd 맵핑
EndDelegate.BindUObject(this, &AABCharacterBase::ComboActionEnd);
// 첫번째 인자에 바인드까지 시킨 구조체를 연결
// 두번째 인자에 몽타주 지정
AnimInstance->Montage_SetEndDelegate(EndDelegate, ComboActionMontage);
// 콤보가 시작될 때 타이머 발동
// 타이머를 초기화해준 뒤
ComboTimerHandle.Invalidate();
SetComboCheckTimer();
}
void AABCharacterBase::ComboActionEnd(UAnimMontage* TargetMontage, bool IsProperlyEnded)
{
// 콤보가 종료될 때 CurrentCombo는 절대 0이 될 수 없음
// Assertion함수를 사용해서 검증
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;
// 정상속도로 진행했을 때 소요될 시간 계산
// ComboActionData에 설정된 값을 가져와서 ComboActionData에 설정된 FrameRate로 나눈 뒤, 그 값을 AttackSpeed로 나눔
// 발동할 시간을 알 수 있게 됨
float ComboEffectiveTime = (ComboActionData->EffectiveFrameCount[ComboIndex] / ComboActionData->FrameRate) / AttackSpeedRate;
if (ComboEffectiveTime > 0.0f)
{
// 월드로부터 시간 서비스 받기
// SetTimer(타이머핸들, 현재 클래스, ComboCheck함수, 시간, 반복X)
GetWorld()->GetTimerManager().SetTimer(ComboTimerHandle, this, &AABCharacterBase::ComboCheck, ComboEffectiveTime, false);
}
}
void AABCharacterBase::ComboCheck()
{
// 타이머가 발동되면 타이머 핸들 초기화
ComboTimerHandle.Invalidate();
// 타이머 발동 전에 들어온 ComboCommand가 있다면
if (HasNextComboCommand)
{
// 몽타주의 다음 섹션으로 넘기기
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
// MaxComboCount를 벗어나지 않도록 Clamp 걸기
CurrentCombo = FMath::Clamp(CurrentCombo + 1, 1, ComboActionData->MaxComboCount);
// 다음 섹션에 대한 이름 정보 가져오기
// String을 조합하기 때문에 Printf 사용
// 에셋에 선언한 MontageSectionNameProfix(접두사)정보와 콤보값을 조합해서
// 네임으로 변환, 섹션이름으로 지정
FName NextSection = *FString::Printf(TEXT("%s%d"), *ComboActionData->MontageSectionNameProfix, CurrentCombo);
// Montage_JumpToSection 해당 이름 섹션으로 바로 점프해서 재생
AnimInstance->Montage_JumpToSection(NextSection, ComboActionMontage);
// 바로 타이머 다시 걸기
SetComboCheckTimer();
HasNextComboCommand = false;
}
}
ABCharacterBase.cpp
빌드 후 실행하면 한번만 누르거나 입력이 늦으면 하나의 애니메이션만, 여러번 눌렀을 때는 최대 4번까지 연속공격이 진행된다.
이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 강의 | 이득우 - 인프런
이득우 | 대기업 현업자들이 수강하는 언리얼 C++ 프로그래밍 전문 과정입니다. 언리얼 C++ 프로그래밍을 사용해 핵&슬래시 로그라이크 게임 예제를 처음부터 끝까지 체계적으로 제작하는 방법을
www.inflearn.com