UE4 C++ 델리게이트_1


Tags :


델리게이트

다음 6가지의 델리게이트 유형을 배우고 활용해 보자

델리게이트 : C++ 오브젝트 클래스의 멤버 함수를 가리키고 실행시키는 데이터 유형
블루프린트에서 사용한 EventDispatcher와 C++의 함수포인터 사용 방식을 결합한듯한 느낌이다

매크로 캐스트 반환값 직렬화 비고
DECLARE_DELEGATE 싱글 return O X  
DECLARE_MULTICAST_DELEGATE 멀티 return X X  
DECLARE_EVENT 멀티 return X X 클래스 내부에서만 사용가능
DECLARE_DYNAMIC_DELEGATE 싱글 return O O 바인딩함수의 매개변수
갯수, 타입, 이름
모두 일치해야 함
DECLARE_DYNAMIC_SPARSE_DELEGATE 싱글 return O O  
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE 멀티 return O O  

Sparse Delegate (Sparse=얕은) : 사용빈도에 따라 효율적으로 메모리를 할당한다


점등 트리거

28 델리게이트를 활용해서 접근하면 Light가 활성화되는 Trigger를 구현해보자

활용 델리게이트 유형 : 싱글캐스트 델리게이트

  • DECLARE_DELEGATE : 리턴 값, 매개변수가 없는 기본 델리게이트
  • DECLARE_DELEGATE_RetVal_OneParam : 리턴과 매개변수가 있는 델리게이트

Trigger

C++ Actor클래스로 C04_Trigger클래스 생성하기
캐릭터와 Component Begin/End Overlap 판정될 때, C04_Light클래스의 함수를 Call하도록
델리게이트FBoxLightOverlap과 인자를 받고 반환값이 있는 델리게이트FBoxLightColorOverlap구현하기

  • 헤더
      #pragma once
      #include "CoreMinimal.h"
      #include "GameFramework/Actor.h"
      #include "C04_Trigger.generated.h"
    
      DECLARE_DELEGATE(FBoxLightOverlap);		// 델리게이터는 이름 앞에 F를 붙여준다
      DECLARE_DELEGATE_RetVal_OneParam(FString, FBoxLightColorOverlap, FLinearColor);
      // 함수 선언순서와 같다 -> 리턴 자료형, 델리게이터명, 매개변수 자료형
      UCLASS()
      class U03_BASIC_API AC04_Trigger : public AActor
      {
          GENERATED_BODY()
    	
      public:	
          AC04_Trigger();
    
      protected:
          virtual void BeginPlay() override;
    
      private:
          UPROPERTY(VisibleAnywhere)
              class USceneComponent*             Root;
          UPROPERTY(VisibleAnywhere)
              class UBoxComponent*                 Box;
          UPROPERTY(VisibleAnywhere)
              class UTextRenderComponent*  Text;
    
      private:
          UFUNCTION()
              void OnComponentBeginOverlap
              (UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
              int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
          UFUNCTION()
              void OnComponentEndOverlap
              (UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
               int32 OtherBodyIndex);
    
      public:
          // 외부에서 Function받을 수 있게 바인딩할 예정(C04_Light에 아래 변수 바인드)
          FBoxLightOverlap		OnBoxLightBeginOverlap;
          FBoxLightOverlap		OnBoxLightEndOverlap;
          FBoxLightColorOverlap	OnBoxLightColorOverlap;
      };
    
  • 소스
      #include "03_Collision/C04_Trigger.h"
      #include "Global.h"
      #include "Components/BoxComponent.h"
      #include "Components/TextRenderComponent.h"
    
      AC04_Trigger::AC04_Trigger()
      {
      	// 컴포넌트들 생성
      	CHelpers::CreateComponent<USceneComponent>(this, &Root, "Root");
      	CHelpers::CreateComponent<UBoxComponent>(this, &Box, "Box", Root);
      	CHelpers::CreateComponent<UTextRenderComponent>(this, &Text, "Text", Root);
    
      	// 텍스트
      	Text->SetRelativeLocation(FVector(0, 0, 100));
      	Text->SetRelativeRotation(FRotator(0, 180, 0));
      	Text->SetWorldScale3D(FVector(2));
        	Text->TextRenderColor = FColor::Red;
      	Text->HorizontalAlignment = EHorizTextAligment::EHTA_Center;
      	Text->Text = FText::FromString(GetName().Replace(L"Default__", L""));
    
      	// 박스
      	Box->bHiddenInGame = false;
      	Box->SetRelativeScale3D(FVector(3));
      }
    

    충돌이벤트는 인게임 중 확인되므로 BeginPlay에서 AddDynamic을 통해 충돌이벤트에 델리게이트 연결

      void AC04_Trigger::BeginPlay()
      {
      	Super::BeginPlay();
    
      	// AddDynamic : 콜리전 이벤트시 바인드할 델리게이트 추가하기
      	Box->OnComponentBeginOverlap.AddDynamic(this, &AC04_Trigger::OnComponentBeginOverlap);
      	Box->OnComponentEndOverlap.AddDynamic(this, &AC04_Trigger::OnComponentEndOverlap);
      }
    

    델리게이트 Execute()

      void AC04_Trigger::OnComponentBeginOverlap
      (UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
       int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
      {
      	// IsBound : 델리게이트가 바인딩 되어있다면
          // Execute : AC04_Trigger에서 콜리전이 발생되면 AC04_Light의 함수 실행
      	if (OnBoxLightBeginOverlap.IsBound())
      		OnBoxLightBeginOverlap.Execute();
      }
    
      void AC04_Trigger::OnComponentEndOverlap
      (UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
       int32 OtherBodyIndex)
      {
      	// IsBound : 델리게이트가 바인딩 되어있다면
          // AC04_Trigger에서 콜리전이 발생되면 AC04_Light의 함수 실행
          if (OnBoxLightEndOverlap.IsBound())
          	OnBoxLightEndOverlap.Execute();
      }
    

    Light

    C++ Actor클래스로 C04_Light클래스 생성하기
    멤버변수로 class AC04_Trigger trigger를 갖고 trigger의 델리게이트들에 OnLight(),OffLight(),OnRandomColor()를 BindUFunction해주어 trigger를 통해 실행시킨다

  • 헤더
    #pragma once
    #include "CoreMinimal.h"
    #include "GameFramework/Actor.h"
    #include "C04_Light.generated.h"
    
      UCLASS()
      class U03_BASIC_API AC04_Light : public AActor
      {
          GENERATED_BODY()
    	
      public:	
          AC04_Light();
    
      protected:
          virtual void BeginPlay() override;
    
      private:
          UPROPERTY(VisibleAnywhere)
              class USceneComponent*			Root;
          UPROPERTY(VisibleAnywhere)
              class UPointLightComponent*		PointLight1;
          UPROPERTY(VisibleAnywhere)
          	class UPointLightComponent*		PointLight2;
          UPROPERTY(VisibleAnywhere)
              class UTextRenderComponent*		Text;
    
      private:
          UFUNCTION()
              void OnLight();
          UFUNCTION()
              void OffLight();
          UFUNCTION()
              FString OnRandomColor(FLinearColor InColor);
      };
    
  • CHelper.h
    반복되는 TextRender생성을 매크로화 시키자
    // 텍스트랜더 컴포넌트 생성 - 자주쓸거니까
    #define CreateTextRender() \
    { \
    CHelpers::CreateComponent<UTextRenderComponent>(this, &Text, "Text", Root);\
    Text->SetRelativeLocation(FVector(0, 0, 100));\
    Text->SetRelativeRotation(FRotator(0, 180, 0));\
    Text->SetWorldScale3D(FVector(2));\
    Text->TextRenderColor = FColor::Red;\
    Text->HorizontalAlignment = EHorizTextAligment::EHTA_Center;\
    Text->Text = FText::FromString(GetName().Replace(L"Default__", L""));\
    }\
    

    GetCurrentLevel()->Actors 받아오는 템플릿함수 FindActor() 만들기

      // 뷰포트 옆 월드아웃라이너에 배치되어있는 모든 Actor들 받아오기
      template<typename T>
      static T* FindActor(UWorld* InWorld)
      {
          for (AActor* actor : InWorld->GetCurrentLevel()->Actors)
          {
              // IsA ) Class<AC04_Trigger>와 상속관계 - A is B(상속), A has B(컴포넌트) 
              if (!!actor&& actor->IsA<T>())
                  return Cast<T>(actor); // 찾아진 액터 캐스트변환해서 반환
          }
          return nullptr; // if조건에 맞는 actor가 없다면 nullptr반환
      }
    
  • 소스
    #include "03_Collision/C04_Light.h"
    #include "Global.h"
    #include "C04_Trigger.h"    // Trigger헤더도 포함해야 함!
    #include "Components/PointLightComponent.h"
    #include "Components/TextRenderComponent.h"
    
      AC04_Light::AC04_Light()
      {
          // 컴포넌트들 생성
          CHelpers::CreateComponent<USceneComponent>(this, &Root, "Root");
          CHelpers::CreateComponent<UPointLightComponent>(this, &PointLight1, "PointLight1", Root);
          CHelpers::CreateComponent<UPointLightComponent>(this, &PointLight2, "PointLight2", Root);
    
          // Text CHelpers에 추가한 매크로
          CreateTextRender();
    
          // PointLight1
          PointLight1->SetRelativeLocation(FVector(0, -50, 0));
          PointLight1->LightColor = FColor::Red;
          PointLight1->Intensity = 5e+5f;	// 5 * 10^5 = 500,000.0f;
          PointLight1->AttenuationRadius = 200;
    
          // PointLight2
          PointLight2->SetRelativeLocation(FVector(0, 50, 0));
          PointLight2->LightColor = FColor::Red;
          PointLight2->Intensity = 5e+5f;	// 5 * 10^5 = 500,000.0f;
          PointLight2->AttenuationRadius = 200;
      }
    

    BeginPlay에서 trigger 정의 후 델리게이트에 BindUFunction

      void AC04_Light::BeginPlay()
      {
          Super::BeginPlay();
          OffLight();
            
          AC04_Trigger* trigger = CHelpers::FindActor<AC04_Trigger>(GetWorld());
          CheckNull(trigger);
            
          trigger->OnBoxLightBeginOverlap.BindUFunction(this, "OnLight");			// OnLight로 연결됨
          trigger->OnBoxLightEndOverlap.BindUFunction(this, "OffLight");			// OffLight로 연결됨
            
          trigger->OnBoxLightColorOverlap.BindUFunction(this, "OnRandomColor");	// OnRandomColor와 연결됨
      }
    

    OnLight(), OffLight(), OnRandomColor() 정의

      void AC04_Light::OnLight()
      {
          PointLight1->SetVisibility(false);
          FLinearColor color = FLinearColor::MakeRandomColor();
          CLog::Print(OnRandomColor(color));
      }
        
      void AC04_Light::OffLight()
      {
          PointLight1->SetVisibility(true);
          PointLight2->SetVisibility(false);
      }
        
      FString AC04_Light::OnRandomColor(FLinearColor InColor)
      {
          PointLight2->SetVisibility(true);
          PointLight2->SetLightColor(InColor);
            
          return "Color : " + InColor.ToString();
      }
    

28_1


멀티 트리거

29 델리게이트를 활용해서 접근하면 Box가 떨어지고 동시에 SpotLight가 활성화되는 Trigger를 구현해보자

활용 델리게이트 유형 : 멀티캐스트 델리게이트

  • DECLARE_MULTICAST_DELEGATE_TwoParams : 매개변수를 2개 받는 멀티캐스트 델리게이트

MultiTrigger

C++ Actor클래스로 C05_MultiTrigger클래스 생성하기
캐릭터와 Component Begin Overlap 판정될 때, C05_SpotLightC05_FallingBox의 함수를 Call하도록
멀티 델리게이트OnMultiLightOverlap 구현하기

  • 헤더
      #include "CoreMinimal.h"
      #include "GameFramework/Actor.h"
      #include "C05_MultiTrigger.generated.h"
        
      // 멀티 델리게이트!
      DECLARE_MULTICAST_DELEGATE_TwoParams(FMultiLightOverlap, int32, FLinearColor);
    
      UCLASS()
      class U03_BASIC_API AC05_MultiTrigger : public AActor
      {
          GENERATED_BODY()
      public:	
          AC05_MultiTrigger();
        
      protected:
          virtual void BeginPlay() override;
        
      private:
          UPROPERTY(VisibleAnywhere)
              class USceneComponent*			Root;
          UPROPERTY(VisibleAnywhere)
              class UBoxComponent*			Box;
          UPROPERTY(VisibleAnywhere)
              class UTextRenderComponent*		Text;
                
      private:
          UFUNCTION()
          void OnComponentBeginOverlap
          (UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp,
           int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
        
      public:
          FMultiLightOverlap OnMultiLightOverlap;
      };
    
  • 소스
    생성자는 컴포넌트들 생성하는 과정 C04_Trigger와 동일하니 생략,
    BeginPlay에서 AddDynamic
      void AC05_MultiTrigger::BeginPlay()
      {
          Super::BeginPlay();
        
          // 콜리전 발생되면 우리가 정의한 delegate함수로 Call
          Box->OnComponentBeginOverlap.AddDynamic(this, &AC05_MultiTrigger::OnComponentBeginOverlap);
      }
    

    멀티 델리게이트의 실행함수는 Execute()가 아니라 Broadcast() - 방송하듯 불특정 다수에게 보내는 메세지

      void AC05_MultiTrigger::OnComponentBeginOverlap
      (UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, 
      int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
      {
          CheckFalse(OnMultiLightOverlap.IsBound());	// False면 그냥 return할 것
            
          // KismetMathLibrary의 랜덤함수 사용하기
          int32 index = UKismetMathLibrary::RandomIntegerInRange(0, 2);
          FLinearColor color = FLinearColor::MakeRandomColor();
            
          // 멀티 델리게이트 함수의 Execute = Broadcast > 불특정 다수에게 보내는 메세지
          // SpotLight, FallingBox에 트리거될것
          OnMultiLightOverlap.Broadcast(index, color);
      }
    

SpotLight

C++ Actor클래스로 C05_SpotLight클래스 생성하기
멤버변수로 class AC05_MultiTrigger trigger를 갖고 trigger의 델리게이트들에 OnLight()를 BindUFunction해주어 trigger를 통해 실행시킨다

  • 헤더
    반복되는 코드들이니까 멤버변수, 함수들만!
      private:
          UPROPERTY(VisibleAnywhere)
              class USceneComponent*      Root;
          UPROPERTY(VisibleAnywhere)
              class USpotLightComponent*  Lights[3];
          UPROPERTY(VisibleAnywhere)
              class UTextRenderComponent* Text;
                
      private:
          UFUNCTION()
              void OnLight(int32 InIndex, FLinearColor InColor);
    
  • 소스
    생성자에서 다른 컴포넌트들과 SpotLight컴포넌트 3개를 for문을 통해 생성해주기
      #include "03_Collision/C05_SpotLight.h"
      #include "Global.h"
      #include "C05_MultiTrigger.h"
      #include "Components/SpotLightComponent.h"
      #include "Components/TextRenderComponent.h"
        
      AC05_SpotLight::AC05_SpotLight()
      {
          // 루트 컴포넌트
          CHelpers::CreateComponent<USceneComponent>(this, &Root, "Root");
            
          // 스포트라이트 컴포넌트
          for (int32 i = 0; i < 3; i++)
          {
              FString str;
              str.Append("Lights");
              str.Append(FString::FromInt(i + 1));
                
              CHelpers::CreateComponent<USpotLightComponent>(this, &Lights[i], FName(str), Root);
                
              Lights[i]->SetRelativeLocation(FVector(0, i * 150, 0));
              Lights[i]->SetRelativeRotation(FRotator(-90, 0, 0));	// 스포트라이트는 기본값이 아래에서 위바향으로 빛을 비춘다
              Lights[i]->Intensity = 5e+5f;
              Lights[i]->OuterConeAngle = 25;
          }
            
          // 텍스트 컴포넌트
          CreateTextRender();
      }
    

    BeginPlay에서 AddUFunction - Multi니까 추가바인드한다는 의미
    OnLight정의

      void AC05_SpotLight::BeginPlay()
      {
          Super::BeginPlay();
            
          // Broadcast Delegate(Trigger) 세팅!
            
          AC05_MultiTrigger* trigger = CHelpers::FindActor<AC05_MultiTrigger>(GetWorld());
          CheckNull(trigger);
          trigger->OnMultiLightOverlap.AddUFunction(this, "OnLight");
      }
        
      void AC05_SpotLight::OnLight(int32 InIndex, FLinearColor InColor)
      {
          for (int32 i = 0; i < 3; i++)
              Lights[i]->SetLightColor(FLinearColor::White);
            
          Lights[InIndex]->SetLightColor(InColor);
      }
    

FallingBox

  • 헤더
      private:
          UPROPERTY(VisibleAnywhere)
              class USceneComponent*      Root;
          UPROPERTY(VisibleAnywhere)
              class UStaticMeshComponent* Meshes[3];
          UPROPERTY(VisibleAnywhere)
              class UTextRenderComponent* Text;
        
      private:
          class   UMaterialInstanceDynamic* Materials[3];
          FVector InitWorldLocation[3];
          FQuat   InitWorldRotation[3];
        
      private:
          UFUNCTION()
              void OnPhysics(int32 InIndex, FLinearColor InColor);
    
  • 소스
    생성자에서 3개의 Mesh들과 다른 컴포넌트들 생성
      #include "03_Collision/C05_FallingBox.h"
      #include "Global.h"
      #include "C05_MultiTrigger.h"
      #include "Components/StaticMeshComponent.h"
      #include "Components/TextRenderComponent.h"
      #include "Materials/MaterialInstanceDynamic.h"
      #include "Materials/MaterialInstanceConstant.h"
        
      AC05_FallingBox::AC05_FallingBox()
      {
          // 루트 컴포넌트
          CHelpers::CreateComponent<USceneComponent>(this, &Root, "Root");
            
          // 매쉬 컴포넌트
          UStaticMesh* mesh;
          CHelpers::GetAsset<UStaticMesh>(&mesh, "StaticMesh'/Game/Meshes/Cube.Cube'");
            
          for (int32 i = 0; i < 3; i++)
          {
              FString str;
              str.Append("Meshes");
              str.Append(FString::FromInt(i + 1));
                
              CHelpers::CreateComponent<UStaticMeshComponent>(this, &Meshes[i], FName(str), Root);
                
              Meshes[i]->SetRelativeLocation(FVector(0, i * 150, 0));
              Meshes[i]->SetStaticMesh(mesh);
              Meshes[i]->SetSimulatePhysics(true);
          }
            
          // 텍스트 컴포넌트
          CreateTextRender();
      }
    

    BeginPlay에서 MaterialInstanceDynamic Set
    trigger의 델리게이트에 OnPhysics()함수 AddUFuction

      void AC05_FallingBox::BeginPlay()
      {
          Super::BeginPlay();
            
          // 머터리얼 인스턴스 Set
          UMaterialInstanceConstant* material;
          CHelpers::GetAssetDynamic<UMaterialInstanceConstant>(&material,"MaterialInstanceConstant'/Game/Materials/M_White_Inst.M_White_Inst'");
            
          for (int32 i = 0; i < 3; i++)
          {
              Materials[i] = UMaterialInstanceDynamic::Create(material, this);
              Materials[i]->SetVectorParameterValue("Color", FLinearColor::White);
              Meshes[i]->SetMaterial(0, Materials[i]);
              Meshes[i]->SetSimulatePhysics(false);
                
              // 이니셜 트랜스폼 정보 미리 받아두기 (초기화를 위해)
              FTransform transform = Meshes[i]->GetComponentToWorld();	// 월드좌표로 변환해서 Get
              InitWorldLocation[i] = transform.GetLocation();
              InitWorldRotation[i] = transform.GetRotation();
          }
            
          // Broadcast Delegate(Trigger) 세팅!
          AC05_MultiTrigger* trigger = CHelpers::FindActor<AC05_MultiTrigger>(GetWorld());
          CheckNull(trigger);
          trigger->OnMultiLightOverlap.AddUFunction(this, "OnPhysics");
      }
    

    OnPhysics 함수 정의

      void AC05_FallingBox::OnPhysics(int32 InIndex, FLinearColor InColor)
      {
          for (int32 i = 0; i < 3; i++)
          {
              // 이니셜 값으로 우선 초기화 시키기
              Materials[i]->SetVectorParameterValue("Color", FLinearColor::White);
              Meshes[i]->SetWorldLocation(InitWorldLocation[i]);
              Meshes[i]->SetWorldRotation(InitWorldRotation[i]);
              Meshes[i]->SetSimulatePhysics(false);
          }
            
          Materials[InIndex]->SetVectorParameterValue("Color", InColor);
          Meshes[InIndex]->SetSimulatePhysics(true);
      }
    

29_1