델리게이트
다음 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=얕은) : 사용빈도에 따라 효율적으로 메모리를 할당한다
점등 트리거
델리게이트를 활용해서 접근하면 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 정의 후 델리게이트에
BindUFunctionvoid 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(); }
멀티 트리거
델리게이트를 활용해서 접근하면 Box가 떨어지고 동시에 SpotLight가 활성화되는 Trigger를 구현해보자
활용 델리게이트 유형 : 멀티캐스트 델리게이트
DECLARE_MULTICAST_DELEGATE_TwoParams: 매개변수를 2개 받는 멀티캐스트 델리게이트
MultiTrigger
C++ Actor클래스로 C05_MultiTrigger클래스 생성하기
캐릭터와 Component Begin Overlap 판정될 때, C05_SpotLight과 C05_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에서 AddDynamicvoid 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()함수 AddUFuctionvoid 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); }