7.2 델리게이트 및 애니메이션 노티파이
델리 게이트
특정 객체가 해야 할 로직을 다른 객체가 대신 처리
등록된 함수를 특정 이벤트에 맞게 실행 가능
사용 예시
애닝 인스턴스 제공 되는 델리게이트 사용
몽타주 재생 종료 시 발생하는 OnMontageEnded(UAnimMontage*, bool )라는 델리게이트 제공
게임 캐릭터에서 위의 델리게이트에 등록 해 몽타주 애니메이션 끝나는 시점 파악
언리얼 델리게이트 종류
델리게이트 : C++ 에서만 사용 가능, 블루 프린트 오브젝틑는 직렬화 매커니즘 때문에 사용 불가
다이나믹 델리게이트 : C++와 블루 프린트 객체에 모두 사용 할 수 있는 델리게이트, UFUNCTION 매크로
UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
GENERATED_BODY()
public:
//애닝 인스턴스에 OnAttackMontageEnded 델리게이트에 등록
virtual void PostInitializeComponents() override;
private:
//몽타주 애니메이션 종료에 등록할 함수
UFUNCTION()
void OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted);
private:
//몽타주 애니메이션 종료 확인하는 bool 변수
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
bool IsAttacking;
}
ABCHECK 매크로 사용 생성
조건이 참이 아닐 경우 에러 로그 발생
OnMontageEnded 은 멀티 캐스트 델리게이트라고 부를 수 있고
여러개의 함수를 등록, 헹동이 끝나면 모든 등록 함수에 알려주는 기능 제공
OnMontageEnded 선언 시 정의된 델리게이트 형식을 시그니처 라고 한다
void AABCharacter::Attack()
{
if (IsAttacking)
return;
auto AnimInstance = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
ABCHECK(AnimInstance != nullptr)
AnimInstance->PlayAttackMontage();
IsAttacking = true;
}
void AABCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
auto AnimInstance = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
ABCHECK(AnimInstance != nullptr);
//OnMontageEnded 델리게이트에 함수 등록
//AddDynamic 은 델리게이트 매크로 제공
AnimInstance->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);
}
void AABCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
//IsAttacking 이 false 면 return
ABCHECK(IsAttacking);
IsAttacking = false;
}
애닝 인스턴스를 맴버 변수로 구조 변경
class ARENABATTLE_API AABCharacter : public ACharacter
{
GENERATED_BODY()
prevate:
//코드 중복 소스 제거 및 최적화
UPROPERTY()
class UABAnimInstance* ABAnim;
}
void AABCharacter::PostInitializeComponents()
{
Super::PostInitializeComponents();
//ABAnim 초기화
ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
ABCHECK(ABAnim != nullptr);
//OnMontageEnded 델리게이트에 함수 등록
//AddDynamic 은 델리게이트 매크로 제공
ABAnim->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);
}
void AABCharacter::Attack()
{
if (IsAttacking)
return;
//Cast 부분 제거
ABAnim->PlayAttackMontage();
IsAttacking = true;
}
애니메이션 노티파이
애니메이션 재생 동안 특정 타이밍에 애님 인스턴스에 신호 보내는 기능
공격 애니메이션의 특정 타이밍에 공격 판정 할 수 있게 몽타주 시스템에 추가
몽타주에 노티파이 추가
Attack1의 8프레임에 AttackHitCheck 노티파이 추가
노티파이 호출 시 애님 인스턴스 클래스의 AnimNotify_노티파이명 맴버 함수 호출
애님 인스턴스 클래스의 AnimNotify_노티파이명 맴버 함수 구현
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
GENERATED_BODY()
private:
//몽타주에 추가한 노티파이 이벤트 함수
UFUNCTION()
void AnimNotify_AttackHitCheck();
}
void UABAnimInstance::AnimNotify_AttackHitCheck()
{
ABLOG_S(Warning);
}
콤보 공격의 구현
일정 시간 동안 공격 명령 시 다음 동작으로 이동하는 콤보 기능
이어 지지 않으면 IDLE 애니메이션으로 전환
몽타주 공격 섹션 분리
attack1, attack2~4 등으로 섹션 분리
4개의 섹션 확인 및 미리보기
NextAttackCheck 노티파이 추가
각 공격 애니메이션 사이에 공격이 이어지는 타이밍 체크
노티파이 카테고리의 Montage Tick Type 변경
Queued type : 비동기 방식으로 신호 처리, 타이밍에 민감하지 않는 사운드나 이펙트
Branching Point tpye : 해당 프레임에 즉각적으로 반응 하는 방식, 타이밍에 민감할 때 사용
ABCharacter 에서 콤보 관련 변수 추가
class ARENABATTLE_API AABCharacter : public ACharacter
{
GENERATED_BODY()
private:
//콤보 시작 시 관련 속성 지정 하는 함수 추가
void AttackStartComboState();
void AttackEndComboState();
private:
//다음 콤보로 이동 가능 여부
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
bool CanNextCombo;
//콤보 입력 여부
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
bool IsComboInputOn;
//현재 콤보 횟수
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
int32 CurrentCombo;
//최대 콤보 횟수
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
int32 MaxCombo;
}
AABCharacter::AABCharacter()
{
MaxCombo = 4;
AttackEndComboState();
}
void AABCharacter::AttackStartComboState()
{
CanNextCombo = true;
IsComboInputOn = false;
//MaxCombo - 1 >= CurrentCombo >= 0 시 true
ABCHECK(FMath::IsWithinInclusive<int32>(CurrentCombo, 0, MaxCombo - 1));
//현재 CurrentCombo 값 증가 후, 1 ~ maxcombo 값 사이로 지정
CurrentCombo = FMath::Clamp<int32>(CurrentCombo + 1, 1, MaxCombo);
}
void AABCharacter::AttackEndComboState()
{
IsComboInputOn = false;
CanNextCombo = false;
CurrentCombo = 0;
}
ABAnimInstance
콤보 카운트를 받아 해당 몽타주 섹션 실행
각각의 애니메이션 노티 파이 이벤트 마다 델리게이트 함수 호출 및 사용
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "ArenaBattle.h"
#include "Animation/AnimInstance.h"
#include "ABAnimInstance.generated.h"
//멀티 캐스트 델리게이트 선언
//애니메이션의 노티파이 발생 시 델리게이트 호출
DECLARE_MULTICAST_DELEGATE(FOnNextAttackCheckDelegate);
DECLARE_MULTICAST_DELEGATE(FOnAttackHitCheckDelegate);
/**
*
*/
UCLASS()
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
//NewSection 맞는 섹션 실행
void JumpToAttackMontageSection(int32 NewSection);
public:
//델리게이트 선언
FOnNextAttackCheckDelegate OnNextAttackCheck;
FOnAttackHitCheckDelegate OnAttackHitCheck;
private:
//AttackHitCheck 노트파이 이벤트 실행
UFUNCTION()
void AnimNotify_AttackHitCheck();
//NextAttackCheck 노트파이 함수 호출
UFUNCTION()
void AnimNotify_NextAttackCheck();
//섹션 넘버를 받아 센셕 이름으로 변환 후 리턴
FName GetAttackMontageSectionName(int32 Section);
};
void UABAnimInstance::JumpToAttackMontageSection(int32 NewSection)
{
//현재 몽타주 실행 중 인지 체크
ABCHECK(Montage_IsPlaying(AttackMontage));
//해당 몽타주의 NewSection 섹션으로 실행
Montage_JumpToSection(GetAttackMontageSectionName(NewSection), AttackMontage);
}
void UABAnimInstance::AnimNotify_AttackHitCheck()
{
OnAttackHitCheck.Broadcast();
}
void UABAnimInstance::AnimNotify_NextAttackCheck()
{
OnNextAttackCheck.Broadcast();
}
FName UABAnimInstance::GetAttackMontageSectionName(int32 Section)
{
//Section 값이 1 ~ 4 범위 검사
ABCHECK(FMath::IsWithinInclusive<int32>(Section, 1, 4), NAME_None);
//FName : Attack1~4 리턴
return FName(*FString::Printf(TEXT("Attack%d"), Section));
}
ABCharacter 플레이어 콤보
1. 공격 명령 시 콤보가 가능 한지 파악 후 대응
2. 공격 시작 후 NextAttackCheck 타이밍 확인
3. 공격 명령이 들어오면 CanNextCombo 속성 변경
OnNextAttackCheck 델리게이트에 등록할 로직을 람다식 사용
void AABCharacter::PostInitializeComponents()
{
//노티파이 이벤트에 델리게이트 함수 등록
ABAnim->OnNextAttackCheck.AddLambda([this]() -> void {
ABLOG(Warning, TEXT("OnNextAttackCheck"));
CanNextCombo = false;
//콤보 인풋 확인
if (IsComboInputOn)
{
AttackStartComboState();
ABAnim->JumpToAttackMontageSection(CurrentCombo);
}
});
}
void AABCharacter::Attack()
{
//공격 상태 확인
if (IsAttacking)
{
//콤보 공격 입력
ABCHECK(FMath::IsWithinInclusive<int32>(CurrentCombo, 1, MaxCombo));
if (CanNextCombo)
{
IsComboInputOn = true;
}
}
else
{
//첫번 째 공격 입력
ABCHECK(CurrentCombo == 0);
AttackStartComboState();
ABAnim->PlayAttackMontage();
ABAnim->JumpToAttackMontageSection(CurrentCombo);
IsAttacking = true;
}
}
void AABCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
//IsAttacking, CurrentCombo > 0 일때 동작
ABCHECK(IsAttacking);
ABCHECK(CurrentCombo > 0);
IsAttacking = false;
AttackEndComboState();
}
void AABCharacter::AttackEndComboState()
{
IsComboInputOn = false;
CanNextCombo = false;
CurrentCombo = 0;
}