게임 개발 공부/Unreal Engine

언리얼 Delegate

Vetenir 2025. 6. 26. 22:01

델리게이트는 ‘이벤트를 발생시키는 쪽’과 ‘처리하는 쪽’을 분리시켜 주는 도구로, 복잡한 시스템에서도 깔끔한 구조를 유지하게 해줍니다. 게임 내 수많은 이벤트(입력, 충돌, 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에서 델리게이트를 관리하는 것이 좋다고 합니다.