• 노티파이가 호출되면, 언리얼 엔진은 이를 보고 자동으로 애님인스턴스 클래스
  • 'AnimNotify_노티파이명' 이라는 멤버 함수를 찾아서 호출한다.
  • ex) AnimNotify_AttackHitCheck();
//ABAnimInstance.h
UCLASS()
class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{
...
private:
	UFUNCTION()
	void AnimNotify_AttackHitCheck();

	UFUNCTION()
	void AnimNotify_NextAttackCheck();
}

//ABAnimInstance.cpp
void UABAnimInstance::AnimNotify_AttackHitCheck()
{
	//OnAttackHitCheck.Broadcast();
}

void UABAnimInstance::AnimNotify_NextAttackCheck()
{
	//OnNextAttackCheck.Broadcast();
}

 


* 애니메이션 노티파이는 노티파이 공간에서 지정한 수만큼 발생한다

 


* 이번에는 각 공격 동작을 섹션으로 분리한 후 일정 시간 내에 공격명령을 내리면 다음 공격 동작으로 이동하는 콤보 공격을 구현해 본다. Attactk2, Attactk3, Attactk4의 이름으로 새로운 섹션을 추가하고 섹션마다 공격 애니메이션을 하나씩 할당한다.

* 그리고 우클릭 메뉴로 섹션을 추가하고, 섹션을 구분하는 녹샌선을 애니메이션 사이에 적당히 드래그 하면 애니메이션 종료시점으로 섹션이 이동한다.

섹션 추가 

 

녹색 x 버튼을 눌러 각 섹션들을 독립적으로 분리


* 이제 노티파이 설정으로 가서 각 섹션별로 공격판정을 할 타이밍을 재설정한다.
* + 버튼을 눌러서 노티파이 작 업 영역을 한 줄 늘리고, 여기에는 다음 공격으로 점프할 타이밍을 파악하는 새로운 노티파이를 설정한다. 새로운 애니메이션 노티파이의 이름은 NextAttackCheck로 한다.
*  NextAttackCheck가 발동하는 타이밍은 공격 모션이 끝나고 다시 IDLE 모션으로 되돌아가기 전으로 설정한다.

* 마지막 공격인 4번쨰에는 연계할 콤보가 없으므로 NextAttackCheck는 설정하지 않는다.

노티파이 한줄 추가 등록


* 애니메이션 노티파이를 설정한 후 에는 해당 프레임에 즉각적으로 반응하는 방식인 Branching Point 값으로 틱타입을 변경하는 것 좋다 
* 기본값인 Queued로 설정하게 되면 비동기방식으로 신호를 받게 돼서 적절한 타이밍에 신호를 받는 것을 놓치게 될 수 있다.
* Queued 값은 주로 타이밍에 민감하지 않은 사운드나 이펙트를 발생시킬떄 사용하는 것이 적합니다.


 

* ABCharacter의 선언으로 가서 캐릭터가 사용할 수 있는
* 콤보 카운터의 최대치 (MaxCombo).
* 현재 실행 중인 콤보 카운터 (CurrentCombo). 
* 다음 콤보로 이동 가능 여부 (CanNextCombo),
* 콤보 입력 여부를 각각 변수 (IsComboInputOn ) 선언한다.

* 그리고 공격이 시작할 떄 관련 속성을 지정하도록 AttackStartComboState 함수와 공격이 종료할 떄, 사용할 AttackEndComboState 함수를 선언하고 구현한다.

//ABCharacter.h
{
private:
	void AttackStartComboState();
	void AttackEndComboState();

private:
	UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = Attack, Meta = (AllowPrivateAccess = true))
	bool IsAttacking;

	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;
  }  
  
  //ABCharacter.cpp
  AABCharacter::AABCharacter()
{
..
	MaxCombo = 4;
	AttackEndComboState();
 }
 
 void AABCharacter::AttackStartComboState()
{
	CanNextCombo = true;
	IsComboInputOn = false;
	ABCHECK(FMath::IsWithinInclusive<int32>(CurrentCombo, 0, MaxCombo - 1));
	CurrentCombo = FMath::Clamp<int32>(CurrentCombo + 1, 1, MaxCombo);
}

void AABCharacter::AttackEndComboState()
{
	IsComboInputOn = false;
	CanNextCombo = false;
	CurrentCombo = 0;
}
 
 
 

* 이제 ABAnimInstance 클래스로 이동해 콤보 카운트를 전달받으면 해당 몽타주 섹션을 재생하도록 기능을 구현한다.
그리고 앞서 선언한 NextAttackCheck 애니메이션 노티파이가 발생할 때마다 ABCharacter에 이를 전달할 델리게이트를 선언한고 애니메이션 노티파이 함수에서 이를 호출한다,

* 이렇게 델리게이트 기능을 사용하면, 애님 인스턴스는 자신의 델리게이트를 사용하는 객체가 어떤 것인지 몰라도 델리게이트에 연결된 함수만 호출하면 되므로, 다른 클래스와 연결되지 않는 의존성 없는 설계를 할 수 있다는 장점이 생긴다.

* 반환값과 인자 갑시 없는 함수 유형으로 델리게이트를 선언하되, 여러개의 함수가 등록되도록 멀티캐스트로 선언해본다. 멀티캐스틑 델리게이트에 등록된 모든 함수를 호출하는 멀티캐스트 델리게이트 명령은 Broadcast 이다.

//ABAnimInstance.h
DECLARE_MULTICAST_DELEGATE(FOnNextAttackCheckDelegate);
DECLARE_MULTICAST_DELEGATE(FOnAttackHitCheckDelegate);


class ARENABATTLE_API UABAnimInstance : public UAnimInstance
{

public:
	UABAnimInstance();
	virtual void NativeUpdateAnimation(float DeltaSeconds) override;

	void PlayAttackMontage();
	void JumpToAttackMontageSection(int32 NewSection);

public:
	FOnNextAttackCheckDelegate OnNextAttackCheck;
	FOnAttackHitCheckDelegate OnAttackHitCheck;
    
private:
	UFUNCTION()
	void AnimNotify_AttackHitCheck();

	UFUNCTION()
	void AnimNotify_NextAttackCheck();

	FName GetAttackMontageSectionName(int32 Section);
};

//ABAnimInstance.cpp

void UABAnimInstance::JumpToAttackMontageSection(int32 NewSection)
{
	ABCHECK(!IsDead);
	ABCHECK(Montage_IsPlaying(AttackMontage));
	Montage_JumpToSection(GetAttackMontageSectionName(NewSection), AttackMontage);
}

void UABAnimInstance::AnimNotify_AttackHitCheck()
{
	OnAttackHitCheck.Broadcast();
}

void UABAnimInstance::AnimNotify_NextAttackCheck()
{
	OnNextAttackCheck.Broadcast();
}

FName UABAnimInstance::GetAttackMontageSectionName(int32 Section)
{
	ABCHECK(FMath::IsWithinInclusive<int32>(Section, 1, 4), NAME_None);
	return FName(*FString::Printf(TEXT("Attack%d"), Section));
}

* 플레이어가 공격 명령을 내리면 ABCharacter는 콤보가 가능한지 아닌지 파악하고 각상황에 따라 대응한다. 공격을 시작하고 NextAttackCheck 타이밍 전까지 공격 명령이 들어오면 NextAttackCheck 타이밍에서 다음 콤보 공격을 시작한다. 이를 파악하기 위해 CanNextcombo 속성을 사용한다.

* ABAnimInstance의 OnNextAttackCheck 델리게이트와 등록할 로직을 ABCharacter에서 선언하고 구현한다.

//AABCharacter.cpp

void AABCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	ABAnim = Cast<UABAnimInstance>(GetMesh()->GetAnimInstance());
	ABCHECK(nullptr != ABAnim);

	ABAnim->OnMontageEnded.AddDynamic(this, &AABCharacter::OnAttackMontageEnded);

	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)
{
	ABCHECK(IsAttacking);
	ABCHECK(CurrentCombo > 0);
	IsAttacking = false;
	AttackEndComboState();

	OnAttackEnd.Broadcast();
}

 

 

+ Recent posts