델리게이트는 ‘이벤트를 발생시키는 쪽’과 ‘처리하는 쪽’을 분리시켜 주는 도구로, 복잡한 시스템에서도 깔끔한 구조를 유지하게 해줍니다. 게임 내 수많은 이벤트(입력, 충돌, UI, AI, 네트워크 등)에 효과적으로 대응할 수 있는 핵심 설계 요소입니다.
✅ 델리게이트의 특징
특징 | 설명 |
🧩 낮은 결합도(Loosely Coupled) | 클래스 간에 직접 참조 없이도 함수 호출이 가능. 코드 간 의존성 최소화. |
🔄 이벤트 기반 구조 | 어떤 이벤트 발생 시 다양한 반응(리스너)들을 유연하게 처리 가능. |
🧪 확장성/유지보수성 우수 | 다른 클래스 수정 없이 새 기능(리스너)을 추가 가능. |
🧠 함수 호출 추상화 | 델리게이트를 통해 함수를 저장/호출함으로써 동적 로직 구성 가능. |
🎮 멀티캐스트 지원 | 여러 개의 리스너를 동시에 바인딩해서 한 번에 호출 가능. |
🧵 비동기 처리나 콜백 처리에 유리 | 작업 완료 후 알림, UI 갱신, 게임 이벤트 처리 등에 적합. |
🧰 블루프린트와 연동 가능 (Dynamic Delegate) | 게임 디자이너가 블루프린트에서 쉽게 바인딩 가능. |
✅ 델리게이트 매크로 종류
매크로 이름 | 블루프린트에서 사용 가능? | 바인딩 대상 | 설명 |
DECLARE_DELEGATE | ❌ | C++ 함수 | 파라미터 없는 일반 델리게이트 |
DECLARE_DELEGATE_OneParam | ❌ | C++ 함수 | 파라미터 1개 |
DECLARE_DYNAMIC_DELEGATE | ✅ | C++/BP 함수 | 런타임 바인딩 가능한 동적 델리게이트 |
DECLARE_DYNAMIC_DELEGATE_OneParam | ✅ | C++/BP 함수 | 파라미터 1개 |
DECLARE_DYNAMIC_MULTICAST_DELEGATE | ✅ | 여러 개 | 블루프린트에서 바인딩 가능한 이벤트용 멀티캐스트 |
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam | ✅ | 여러 개 | 파라미터 1개 |
DECLARE_EVENT | ❌ | C++ 전용 | 멀티캐스트 이벤트 |
DECLARE_EVENT_OneParam | ❌ | C++ 전용 | 파라미터 1개 |
DECLARE_MULTICAST_DELEGATE | ❌ | C++ 전용 | 멀티캐스트 델리게이트 |
DECLARE_MULTICAST_DELEGATE_OneParam | ❌ | C++ 전용 | 파라미터 1개 |
✅ 매크로 인자 규칙
예: DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnScoreUpdated, int32, Score, FString, PlayerName);
- FOnScoreUpdated : 델리게이트 타입 이름
- int32 : 첫 번째 파라미터 타입
- Score : 첫 번째 파라미터 이름
- FString : 두 번째 파라미터 타입
- PlayerName : 두 번째 파라미터 이름
✅ 사용 예시
코드 요약
- ButtonActor와 DoorActor를 레벨에 배치
- 숫자 1을 누르면 DoorActor에서 Door Opened 출력
- 숫자 1을 떼면 Door Closed를 줄력
- 주석 처리가 된 코드를 사용하면 멀티캐스트로 사용 가능.
(예 : DoorActor를 추가로 배치하여 숫자 1을 클릭하면 모든 DoorActor가 Door Opened를 출력)
ButtonActor.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ButtonActor.generated.h"
//DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnButtonStateChanged, bool, bIsPressed);
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnButtonStateChanged, bool, bIsPressed);
UCLASS()
class MYPROJECT_API AButtonActor : public AActor
{
GENERATED_BODY()
public:
AButtonActor();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
//UPROPERTY(BlueprintAssignable, Category = "Events")
FOnButtonStateChanged OnButtonStateChanged;
private:
bool bWasKeyPressed;
};
ButtonActor.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ButtonActor.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
AButtonActor::AButtonActor()
{
PrimaryActorTick.bCanEverTick = true;
bWasKeyPressed = false;
}
void AButtonActor::BeginPlay()
{
Super::BeginPlay();
}
void AButtonActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (!PC) return;
bool bIsKeyPressed = PC->IsInputKeyDown(FKey("One"));
if (bIsKeyPressed != bWasKeyPressed)
{
//OnButtonStateChanged.Broadcast(bIsKeyPressed);
OnButtonStateChanged.ExecuteIfBound(bIsKeyPressed);
bWasKeyPressed = bIsKeyPressed;
}
}
DoorActor.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ButtonActor.h"
#include "DoorActor.generated.h"
UCLASS()
class MYPROJECT_API ADoorActor : public AActor
{
GENERATED_BODY()
public:
ADoorActor();
protected:
virtual void BeginPlay() override;
public:
UFUNCTION()
void OnButtonStateChanged(bool bIsPressed);
private:
AButtonActor* ConnectedButton;
};
DoorActor.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "DoorActor.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "EngineUtils.h"
ADoorActor::ADoorActor()
{
PrimaryActorTick.bCanEverTick = false;
ConnectedButton = nullptr;
}
void ADoorActor::BeginPlay()
{
Super::BeginPlay();
for (TActorIterator<AButtonActor> ActorIterator(GetWorld()); ActorIterator; ++ActorIterator)
{
AButtonActor* Button = *ActorIterator;
if (Button)
{
ConnectedButton = Button;
//ConnectedButton->OnButtonStateChanged.AddDynamic(this, &ADoorActor::OnButtonStateChanged);
ConnectedButton->OnButtonStateChanged.BindDynamic(this, &ADoorActor::OnButtonStateChanged);
break;
}
}
}
void ADoorActor::OnButtonStateChanged(bool bIsPressed)
{
if (bIsPressed)
{
GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Green, TEXT("Door Opened"));
}
else
{
GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("Door Closed"));
}
}
주의
위의 예시 코드에서는 간단한 예시를 만들기 위해 DoorActor가 ButtonActor.h를 직접 포함하여 델리게이트의 낮은 결합도 장점을 충분히 활용하지 못하지 못하고 있습니다. 실제로는 GameMode에서 델리게이트를 관리하는 것이 좋다고 합니다.
'게임 개발 공부 > Unreal Engine' 카테고리의 다른 글
아이템 랜덤 스포너 (2) | 2025.07.16 |
---|---|
Unreal MCP (0) | 2025.05.30 |
Unreal AI 구조 (0) | 2025.05.23 |
_Implementation, _Validate, Execute_ (0) | 2025.05.16 |
이벤트 디스패처(Event Dispatcher)와 블루프린트 인터페이스(Blueprint Interface) (0) | 2025.05.09 |