[UE5] TIL - 26 <Delegate, Dynamic Delegate>

ChangJin·2024년 4월 25일
0

Unreal Engine5

목록 보기
54/114
post-thumbnail

2024-04-25

깃허브!
https://github.com/ChangJin-Lee/ARproject
https://github.com/ChangJin-Lee/ToonTank

느낀점
Delegate 개념에 대해서 자세하게 알아보았다. c++ 오브젝트 상의 멤버 함수 호출을 하는 방식이었다. 그리고 그 델리게이트를 Serialize 가능하며, 그 함수를 이름으로 찾을 수도 있는 방식이 Dynamic Delegate이었다.

TIL

  • Delegate
  • Dynamic Delegate

Delegate

https://dev.epicgames.com/documentation/ko-kr/unreal-engine/delegates-and-lamba-functions-in-unreal-engine


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

  • Delegate (델리게이트)로 C++ 오브젝트 상의 멤버 함수 호출을 일반적이고 유형적으로 안전한 방식으로 할 수 있습니다. 델리게이트를 사용하여 임의 오브젝트의 멤버 함수에 동적으로 바인딩시킬 수 있으며, 그런 다음 그 오브젝트에서 함수를 호출할 수 있습니다. 호출하는 곳에서 오브젝트의 유형을 몰라도 말이지요.

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

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


  • 사용 예제를 위주로 적겠다.

사용 예시

  • 아무데서나 호출했으면 하는 메서드를 가진 클래스
class FLogWriter
{
	void WriteToLog( FString );
};

  • 델리게이트 선언
    - 해당 함수의 시그너처에 맞는 델리게이트 유형을 생성.
	DECLARE_DELEGATE_OneParam( FStringDelegate, FString );
class FMyClass
{
	FStringDelegate WriteToLogDelegate;
};

  • 델리게이트 바인딩
  • 페이로드 데이터 보관
    - 델리게이트 클래스의 인스턴스를 생성.
    • 그 메서드를 소유하는 클래스와 함께 템플릿 파라미터로 전해주기
    • 자기 오브젝트의 인스턴스와 그 메서드의 실제 함수 주소역시 전해주기
    • 클래스의 메서드에 델리게이트를 동적으로 바인딩하면 준비 완료
TSharedRef< FLogWriter > LogWriter( new FLogWriter() );
WriteToLogDelegate.BindSP( LogWriter, &FLogWriter::WriteToLog );

  • 델리게이트 실행
WriteToLogDelegate.Execute( TEXT( "델리게이트 쥑이네!" ) );
WriteToLogDelegate.ExecuteIfBound( TEXT( "함수가 바인딩되었을 때만 실행!" ) );


  • 실제로 적용했던 코드 1
    - 거의 대부분의 경우에 BeginPlay에서 델리게이트를 등록한다.
    • 그 다음 함수를 구현해두고 특정 이벤트가 발생했을 때 해당 함수가 실행되도록 만들 수 있다.
    • 특정 이벤트 혹은 시간이 될 수도 있다.

  • 헤더파일에 ProjectileMesh를 선언한다
private:
	UPROPERTY(EditDefaultsOnly, Category = "Combat")
	class UStaticMeshComponent* ProjectileMesh;

  • cpp 파일 생성자에서 다음처럼 생성한다
AProjectile::AProjectile()
{
	PrimaryActorTick.bCanEverTick = false;
	
	ProjectileMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Projectile Mesh"));
	RootComponent = ProjectileMesh;
}

  • BeginPlay에서 ProjectileMesh의 충돌 이벤트 관련 델리게이트를 바인딩한다.
void AProjectile::BeginPlay()
{
	Super::BeginPlay();

	ProjectileMesh->OnComponentHit.AddDynamic(this, &AProjectile::OnHit);
}

  • OnHit 멤버함수를 완성한다. 충돌했을 때 Particle을 방생시키고 Sound를 재생하고 카메라가 흔들리게 만드는 기능을 하는 함수이다.
void AProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
	AActor* MyOwner = GetOwner();
	
	if(MyOwner==nullptr)
	{
		Destroy();
		return;
	}

	AController* MyOwnerInstigator = MyOwner->GetInstigatorController();
	UClass* DamageTypeClass = UDamageType::StaticClass();

	if(OtherActor && OtherActor != this && OtherActor != MyOwner)
	{
		UGameplayStatics::ApplyDamage(OtherActor, Damage, MyOwnerInstigator, this, DamageTypeClass);
		if(HitParticles)
		{
			UGameplayStatics::SpawnEmitterAtLocation(this, HitParticles, GetActorLocation(), GetActorRotation());
		}
		if(HitSound)
		{
			UGameplayStatics::PlaySoundAtLocation(this, HitSound, GetActorLocation());
		}
		if(HitCameraShakeClass)
		{
			GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(HitCameraShakeClass);
		}
	}

	Destroy();
}


  • 실제로 적용했던 코드 2
    - 데미지를 적용하고 체력이 줄어들게 만드는 기능
  • 다음처럼 BeginPlay에 OnTakeAnyDamage로 바인딩한다
void UHealthComponent::BeginPlay()
{
	Super::BeginPlay();
	Health = MaxHealth;
	GetOwner()->OnTakeAnyDamage.AddDynamic(this, &UHealthComponent::DamageTaken);
	ToonTanksGameMode = Cast<AToonTanksGameMode>(UGameplayStatics::GetGameMode(this));
}

  • 멤버함수를 정의해둔다
void UHealthComponent::DamageTaken(AActor* DamagedActor, float Damage, const UDamageType* DamageType, class AController* Instigator, AActor* DamageCauser)
{
	if(Damage <= 0.f) return;

	Health -= Damage;
	UE_LOG(LogTemp, Warning, TEXT("Health: %f"), Health);

	if( Health <= 0 && ToonTanksGameMode)
	{
		ToonTanksGameMode->ActorDied(DamagedActor);
	}
}

Dynamic Delegate

  • serialize 가능하면서 리플렉션도 지원하는 델리게이트입니다.
  • 다이내믹 델리게이트의 선언은, 다이내믹 델리게이트 전용 변종 매크로를 사용한다는 점만 빼고는 표준 델리게이트 선언 시와 같은 방법으로 선언합니다.
  • 사용법도 매우 유사하다

  • 사용방법은 다음과 같다
    - 다이내믹 델리게이트 선언하기
    • 다이내믹 델리게이트 바인딩
    • 다이내믹 델리게이트 실행하기

  • 실제 예시

  • FTimer로 Time Delegate를 구현한 사례이다.


  • HandleGameStart함수에서
    - FTimerDelegate 지역 변수를 선언한다.
    • FTimerDelegate::CreateUObject()를 지역 변수에 할당한다.
    • ToonTanksPlayerController를 user object로 등록하고
    • AToonTanksPlayerController::SetPlayerEnabledState()를 바인딩한다
    • 이 function에 대한 input을 true로 만든다.

void AToonTanksGameMode::HandleGameStart()
{
	TargetTowers = GetTargetTower();
	Tank = Cast<ATank>(UGameplayStatics::GetPlayerPawn(this,0));
	ToonTanksPlayerController = Cast<AToonTanksPlayerController>(UGameplayStatics::GetPlayerController(this,0));

	StartGame();
	
	if (ToonTanksPlayerController)
	{
		ToonTanksPlayerController->SetPlayerEnabledState(false);

		FTimerHandle PlayerEnableTimerHandle;
		FTimerDelegate PlayerEnableTimerDelegate = FTimerDelegate::CreateUObject(
			ToonTanksPlayerController,
			&AToonTanksPlayerController::SetPlayerEnabledState,
			true
			);
		GetWorldTimerManager().SetTimer(PlayerEnableTimerHandle,
			PlayerEnableTimerDelegate,
			StartDelay,
			false
			);
	}
}
profile
게임 프로그래머

0개의 댓글