본문 바로가기
Game DevTip/Unreal Engine

3. 언리얼 엔진 싱글케스트 델리게이트 기초

by LIKE IT.라이킷 2024. 12. 5.

 

사실 나는 델리게이트에 대해서 잘 모른다.

C#을 해본거도 아니고, 언리얼에서 함수 다룰때도 완전 초보식으로 다루기도 했고....

 

그래서 이 김에 한번 언리얼 싱글케스트 델리게이트를 공부한 내용들을 정리해보려고 한다.

(약간 틀릴 수도 있다.)

 




일단, 포인터는 런타임에 지정이 가능하다.

그리고 가리키는 메모리 주소를 바꿀수 있다는 점도 유용하다.

 

표준타입 외에 함수에 대해서도 포인터를 생성이 가능하지만,

여러가지 이유로 안전하지 않다고 한다.

 

그래서 이것 때문에 언리얼 엔진에서는 델리게이트를 쓴다고...

 

일단 델리게이트는 훨씬 안전한 함수 포인터라고 한다. 

어떤 함수가 할당되는지 그 함수가 호출되는 순간까지 알지 못하는 상태에서

미꾸라지마냥 유연하게 함수를 호출 할 수 있다고 한다.

 

이런 유연함이 델리게이트를 선호하는 핵심적 이유라는데.....

 

그러면 언리얼에서 델리게이트를 사용하려면 무엇을 해야할까?


 

일단 어떤 함수를 델리게이트로 연결하기 위해서는

UFUNTION 매크로가 필요하다.

 

즉, 아래 코드처럼 무조건 UFUNTION(언리얼 엔진에서 관리하는 함수)가 되어야 한다는 것이다.

 

//어떤 클래스의 헤더파일 아래에서

UFUNTION()
void AnyMethod();

 

일단 이렇게 함수를 선언 해줬다면,

우리는 그 함수가 있는 클래스의 헤더부분에 델리게이트를 선언해줄 것이다.

 

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "C_GameModeBase.generated.h"

DECLARE_DELEGATE(FStandardDelegate)

UCLASS()
class TESTPROJECT_API AC_GameModeBase : public AGameModeBase
{
	GENERATED_BODY()

public:
	FStandardDelegate MyDelegate;
	
};

 

이런식이다. 

 

헤더 아래쪽, 그리고 UCLASS() 코드 위쪽에 델리게이트 선언을 해준다.

델리게이트 선언도 좀 여러가지 방법들이 있는데, 이거는 조금 이따가 다뤄보기로 하고,

 

그리고 델리게이트를 선언한걸 이용해서 해당 클래스에 public으로 새로운 멤버를 추가한다.

 

그다음에 이 델리게이트를 활용할 Actor 클래스를 하나 만들어주고,

 

//Actor 클래스인 "C_Delegateistener.h"'부분.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "C_Delegateistener.generated.h"

UCLASS()
class TESTPROJECT_API AC_Delegateistener : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AC_Delegateistener();

	UFUNCTION()
	void EnableLight();
	
	UPROPERTY()
	class UPointLightComponent* PointLightComponent;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

};

 

이렇게 헤더를 짜준다. 우리는 이 클래스를 이용해서 불을 끄거나 키는 기능을 만들것이다.

 

//Actor 클래스인 "C_Delegateistener.cpp"'부분.

#include "C_Delegateistener.h"'
#include "Components/PointLightComponent.h"


// Sets default values
AC_Delegateistener::AC_Delegateistener()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	PointLightComponent = CreateDefaultSubobject<UPointLightComponent>("PointLight");
	RootComponent = PointLightComponent;

	PointLightComponent->SetVisibility(false);

	PointLightComponent->SetLightColor(FLinearColor::Blue);
}

 

일단 이 Actor 클래스의 생성자 부분에서는 헤더파일에서 PointLightComponent를 가지고와서

생성해주고 해당 PointLightComponent를 RootComponent(최상위 컴포넌트)로 만들어준다.

참고로 #include "Components/PointLightComponent.h"를 통해서 포인트 라이트 컴포넌트의 해더를 가지고 와야한다.

 

그리고 불은 나중에 키를 누르면 켜지도록 하기위해 게임이 시작하고 바로 불이 켜지는 것을 방지하기 위해서

SetVisibility를 false로 설정해주고, 라이트의 색상은 Blue로 바꿔준다.

 

 

 

 

//Actor 클래스인 "C_Delegateistener.cpp"' BeginPlay 함수부분.

void AC_Delegateistener::BeginPlay()
{
	Super::BeginPlay();

	UWorld* TheWorld = GetWorld();
	if(TheWorld != nullptr)
	{
		AGameModeBase* GameModeBase = UGameplayStatics::GetGameMode(TheWorld);
		
		AC_GameModeBase* MyGameMode = Cast<AC_GameModeBase>(GameModeBase);

		if(MyGameMode != nullptr)
		{
			MyGameMode->MyDelegate.BindUObject(this, &AC_Delegateistener::EnableLight);
		}
	}
}

void AC_Delegateistener::EnableLight()
{
	PointLightComponent->SetVisibility(true);
}

 

BeginPlay에서는 일단 월드(해당 레벨)을 할당하기 위해서 UWorld 객체를 통해 GetWorld()로 가지고온다.

그리고 제대로 생성이 되었는지 확인을 하기 위해서 if 문으로 TheWorld가 Nullptr인지 체크해주고,

 

AGameModeBase 객체를 하나 선언하여 해당 게임모드 베이스에 아까 생성했던 TheWorld 객체를 할당해준다.

그리고 맨처음 델리게이트를 선언했던 C_GameModeBase 클래스의 포인터 객체(MyGameMode)를 생성하여 

Cast(형변환)을 사용하여 C_GameModeBase에 TheWorld를 할당했던 GameModeBase 객체를 가지고온다.

 

그리고 MyGameMode가 nullptr이 아닌지 확인해주고, 

델리게이트를 이용해 이 액터클래스에 멤버 함수인 EnableLight를 바인딩(지정) 해준다.

 

BindUObject()는 다음의 기능을 제공한다.

- UObject 기반 멤버 함수 델리게이트를 바인딩합니다.
- UObject 델리게이트는 오브젝트로의 약한 레퍼런스를 유지합니다.
- ExecuteIfBound() 로 지정한 함수를 호출할 수 있습니다.

 

 

EnableLight() 부분은 아까 생성자에 했던 것과 반대로 SetVisibility를 true로 바꿔주어 구현을 해주어 마무리.

 

 

 

문제가 있다면 이 액터 클래스는 UGameplayStatics 클래스나 아까 우리가 만들어준

GameMode 클래스를 모르므로, 또 아래처럼 해더를 추가해줘야한다.

//Actor 클래스인 "C_Delegateistener.cpp"' 헤더 부분에 해당 헤더들을 추가..

#include "C_GameModeBase.h"
#include "Kismet/GameplayStatics.h"

 

 

자 그러면 이 델리게이트를 사용하여 EnableLight()멤버함수를 각각 다른 여러 클래스에서의

함수들에서 호출해야할때는 필수로 어떤 코드가 들어가야 할까?

 

	UWorld* TheWorld = GetWorld();
	if(TheWorld != nullptr)
	{
		AGameModeBase* GameModeBase = UGameplayStatics::GetGameMode(TheWorld);
		
		AC_GameModeBase* MyGameMode = Cast<AC_GameModeBase>(GameModeBase);

		if(MyGameMode != nullptr)
		{
			MyGameMode->MyDelegate.ExecuteIfBound();
		}
  	}

 

일단 기본적으로 델리게이트가 선언된 곳의 정보를 받아와야 하므로 앞서 만든

Actor 클래스의 부분과 거의 비슷하게 구현을 한다.

 

마찬가지로 당연히 해당 변수(정보)들이 있는 클래스를 헤더로 호출해줘야한다.

 

이후 Delegate를 사용하여 호출하는 부분은 ExecuteIfBound();를 사용하여

아까만든 액터클래스의 EnableLight()를 호출한다.

 

ExecuteIfBound()의 기능은 다음과 같다.

- 싱글캐스트 델리게이트에 바인딩된 함수를 확인하고 존재하면 함수를 실행합니다.

 

델리게이트는 실행하기전에 항상 바인딩 되어있는 함수가 존재하는지 반드시 확인을 해줘야한다.

(코드 안정성 보장 때문),

 

만약 바인딩 되어있지 않은 델리게이트를 실행시키면

일부 인스턴스에서 메모리 낭비가 발생할 수도 있다.

 

일단 이게 기본적인 델리게이트 기초이다.

 

 

반응형

댓글