728x90
이 글은 Inflearn - Rookiss : 언리얼 엔진4 입문 강의를 듣고 정리한 글입니다.

 

목차.

  1. 델리게이트의 개념
  2. 문제의 코드
  3. 코드 개선

 

델리게이트의 개념

 

델리게이트 : C++ 오브젝트 상의 멤버 함수를 가리키고 실행시키는 데이터 유형입니다.


델리게이트(Delegate)로 C++ 오브젝트 상의 멤버 함수 호출을 일반적이고 유형적으로 안전한 방식으로 할 수 있습니다.

델리게이트를 사용하여 임의 오브젝트의 멤버 함수에 동적으로 바인딩시킬 수 있으며, 그런 다음 그 오브젝트에서 함수를 호출할 수 있습니다. 호출하는 곳에서 오브젝트의 유형을 몰라도 말이죠.

 

델리게이트 오브젝트는 복사해도 완벽히 안전합니다. 델리게이트는 값으로 전달 가능하나 보통 추천할 만 하지는 않는데, heap에 메모리를 할당해야 하기 때문입니다. 가급적이면 델리게이트는 항상 참조 전달해야 합니다.

 

델리게이트는 싱글-캐스트(형 변환)와 멀티-캐스트 모두 지원되며, 디스크에 안전하게 직렬화(Serialize)시킬 수 있는 "다이내믹" 델리게이트도 물론입니다.

 

델리게이트 | 언리얼 엔진 문서 (unrealengine.com)

 

델리게이트

C++ 오브젝트 상의 멤버 함수를 가리키고 실행시키는 데이터 유형입니다.

docs.unrealengine.com

 

C#에서는 델리게이트가 기본 문법으로 들어가있지만 C++은 그렇지 않습니다.

그래서 언리얼에서는 아래와 같이 자체적으로 함수들을 만들어두었습니다.

PlayerInputComponent->BindAction(TEXT("Jump"), EInputEvent::IE_Pressed, this, &AMyCharacter::Jump);
PlayerInputComponent->BindAction(TEXT("Attack"), EInputEvent::IE_Pressed, this, &AMyCharacter::Attack);

 

문제의 코드

 

그래서 델리게이트를 이용해서 전에 작성한 코드를 더 나은 코드로 개선시켜 보겠습니다.

 

기존에는 플레이어가 마우스 클릭을 하면 Attack 함수가 호출되고 AnimInstace의 PlayAttackMontage함수를 다시 호출

 

MyCharacter.cpp

void AMyCharacter::Attack()
{
	auto AnimInstance = Cast<UMyAnimInstance>(GetMesh()->GetAnimInstance());
	if (AnimInstance)
	{
		AnimInstance->PlayAttackMontage();
	}
}

 

그리고 조건문을 통해 이미 애니메이션이 작동 중이 아닐 때만 애니메이션을 Play 시키는 코드를 작성했었습니다.

 

MyAnimInstance.cpp

void UMyAnimInstance::PlayAttackMontage()
{
	if (!Montage_IsPlaying(AttackMontage))
	{
		Montage_Play(AttackMontage, 1.f);
	}
}

 

하지만 위 코드의 문제는 플레이어가 공격 중인지, 아닌지 상태를 확인하기 위해서는 계속해서 PlayAttackMontage 함수를 호출해야 한다는 것입니다.

 

물론 이 정도의 코드는 큰 비용이 드는 것이 아니지만 조금 더 개선할 수 있다고 한다면 MyCharacter 클래스에서 공격이 끝났다는 것을 따로 관리했으면 좋겠다는 생각이 듭니다.

 

그래서 MyAnimInstance에서 플레이어가 공격이 끝났는지 확인하는 게 아니라 MyCharacter 클래스에서 플레이어가 공격이 끝났는지 아닌지 확인할 수 있도록 코드를 수정해 보겠습니다.

 

코드 개선

 

MyCharacter 클래스에 IsAttacking 변수를 선언

 

MyCharacter.h

private:
....
    UPROPERTY(VisibleAnywhere, Category=Pawn) //카테고리는 Pawn
    bool IsAttacking = false;
....

 

위 코드를 이용해서 플레이어가 공격을 하는 동안에는 IsAttacking을 true로 해줄 것이고, 공격이 끝난다면 false로 해줄 것입니다.

 

MyCharacter.cpp

void AMyCharacter::Attack()
{
	if (IsAttacking)
		return;
....
....
	IsAttacking = true;
}

 

기존 Attack 함수에서 IsAttacking이 true 라면 바로 return이 되어 다른 코드는 실행되지 않게 해주고 만약 false였다면 

다른 코드가 실행된 뒤, 마지막에 IsAttacking이 true가 되도록 해주었습니다.

 

이제 IsAttacking을 false로 만들어줄 코드가 필요합니다.

 

일단 AnimInstacne를 자주 사용할 예정이기 때문에 AnimInstance를 전방선언을 해 멤버 변수로 만들겠습니다.

 

MyCharacter.h

private:
....
    UPROPERTY()
    class UMyAnimInstance* AnimInstance; //class -> 전방선언
....

 

그리고 BeginPlay() 함수에서 캐스팅을 해주고 

AddDynamic을 이용해서 몽타주가 끝나면 함수가 호출되도록 코드를 추가해 주겠습니다.

 

MyCharacter.cpp

// Called when the game starts or when spawned
void AMyCharacter::BeginPlay()
{
	Super::BeginPlay();
	
	AnimInstance = Cast<UMyAnimInstance>(GetMesh()->GetAnimInstance());
	AnimInstance->OnMontageEnded.AddDynamic(this, &AMyCharacter::OnAttackMontageEnded);
}

 

이제 Attack에서 UMyAnimInstance를 캐스팅해주던 코드는 삭제해주겠습니다.

auto AnimInstance = Cast<UMyAnimInstance>(GetMesh()->GetAnimInstance()); //삭제

 

 

AddDynamic을 이용해 연동을 해주는 함수(OnAttackMontageEnded)가 마음대로 정의할 수 있는 게 아니라 아래 사진과 같이 지정된 형태대로 만들 수 있습니다(형태는 다양함)

 

몽타주 Ended 델리게이트이 구조

 

지금까지 계속 사용한 BindAction과 BindAxis의 예로 들자면 BindAxis에서는 함수의 인자가 무조건 float이었고 BindAction은 인자가 없는 함수형태였습니다.

 

이제 OnAttackMontageEnded 함수를 구현해 보겠습니다.

 

MyCharacter.cpp

public:
....
    UFUNCTION()
    void OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted);
....

 

MyCharacter.h

void AMyCharacter::OnAttackMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
	IsAttacking = false;
}

 

함수의 구조가 이렇게 간단한 이유는 OnMontageEnded()를 이용해서 애니메이션이 끝나면 자동적으로 OnAttackMontageEnded()가 호출되기 때문입니다.

 

이제 이렇게 된다면 PlayAttackMontage()의 코드도 상당히 간결해집니다.

void UMyAnimInstance::PlayAttackMontage()
{
	Montage_Play(AttackMontage, 1.f);
}

 

전에는 조건문을 통해서 애니메이션이 작동 중인지 아닌지 검사를 해야 했지만 이제 필요 없어졌습니다.

 

조건문이나 다른 코드없이 쉽게 공격중인지 확인할 수 있었다

 

오른쪽 하단을 보면 폰 항목에 Is Attacking이 공격할 때는 체크가 되고 공격이 끝나면 체크해제가 되는 것을 볼 수 있습니다.

728x90

+ Recent posts