Отличия делегатов в Unreal Engine

Делегаты в Unreal Engine - это реализация известного паттерна проектирования Observer. Клиентский класс подписывается на делегат и слушает программные события, а класс, публикующий делегат, может в свою очередь уведомлять всех клиентов, даже не зная, существуют они или нет. В целом, это делает код менее связанным.

Я решил написать эту небольшую шпаргалку, чтобы самому разобраться в отличиях между делегатами. Потому что раньше, чтобы ответить на вопрос: "Какой делегат использовать сейчас?" мне приходилось лазить по туториалам и докам, собирая всю информацию вместе.

Какой делегат использовать сейчас?

Static Delegates

Static Delegate - это самый простой делегат, он позволяет добавлять только один обработчик событий.

Допустим, каждый раз когда вы стреляете, вам надо отправлять событие. Вот так вы декларируете и объявляете делегат:

DECLARE_DELEGATE(FStaticDelegate); class FFireHandler { void OnFire(); } class AGunner : public ACharacter { FFireHandler* FireHandler; FStaticDelegate OnFireDelegate; }

А вот так использовать его:

FireHandler = new FFireHandler(); OnFireDelegate.BindRaw(FireHandler, &FFireHandler::OnFire); ... OnFireDelegate.ExecuteIfBound();

Статический делегат работает только с обычными классами, но если вы хотите слушать его из наследника UObject, то используйте функцию: OnFireDelegate.BindUObject

Когда использовать?


Когда вам нужен самый простой и быстрый способ обработать событие.

Static Multicast Delegates

В отличии от простого статического делегата, к Static Multicast Delegate можно добавлять несколько обработчиков событий с помощью метода AddRaw:

DECLARE_MULTICAST_DELEGATE(FStaticMulticastDelegate); class FFireHandler { void OnFire(); } class AGunner : public ACharacter { FFireHandler* FireHandler; FFireHandler* FireHandler2; FStaticMulticastDelegate OnFireDelegate; }

И код клиентского класса:

FireHandler = new FFireHandler(); FireHandler2 = new FFireHandler(); OnFireDelegate.AddRaw(FireHandler, &FFireHandler::OnFire); OnFireDelegate.AddRaw(FireHandler2, &FFireHandler::OnFire); ... OnFireDelegate.Broadcast();

Обратите внимание, что для отправления события используется метод Broadcast. Он хорош тем, что не нужно проверять, есть ли обработчики событий у данного делегата или нет.

Когда использовать?


Когда вам нужен больше чем один обработчик.

Dynamic Delegates

Простые динамические делегаты - самые скучные из всех. Их отличие от статических в том, что их можно сериализовывать. При этом они медленнее.

Объявлять их надо так:

DECLARE_DYNAMIC_DELEGATE(FDynamicDelegate); class AGunner : public ACharacter { FDynamicDelegate OnFireDelegate; UFUNCTION() void OnFireHandler(); }

Важный момент: функцию-обработчик следует аннотировать макросом UFUNCTION.

А так использовать:

OnFireDelegate.BindDynamic(this, &AGunner::OnFireHandler) ... OnFireDelegate.ExecuteIfBound();

Когда использовать?

Когда вы имеете дело с сериализацией.

Dynamic Multicast Delegates

И наконец, самый многофункциональный делегат. Dynamic Multicast Delegates обладают характеристиками всех остальных. И это позволяет использовать их в блюпринтах.

Вот так они объявляются:

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDynamicMulticastDelegate); class AGunner : public ACharacter { UPROPERTY(BlueprintAssignable); FDynamicMulticastDelegate OnFireDelegate; }

Делегат нужно аннотировать макросом UPROPERTY(BlueprintAssignable), чтобы использовать его в блюпринте:

Отличия делегатов в Unreal Engine

Конечно же, такой делегат можно обрабатывать и в C++:

OnFireDelegate.AddDynamic(this, &AGunner::OnFireHandler) ... OnFireDelegate.Broadcast();

Так же, как и в случае с Dynamic Delegate, функцию-обработчик нужно аннотировать макросом UFUNCTION.

Когда использовать?


Когда обрабатывать событие нужно в блюпринте.

p.s. Это перевод статьи из моего англоязычного блога. Оригинал тут:

5454
13 комментариев

Сейчас проводим набор на стажировку в компанию, в тестовом у нас есть вопрос про различия делегатов.

Почти у всех вижу, что динамик делегаты можно сериализовать, но почему то никто не пишет, что динамик делегаты биндят функцию по имени, а не по адресу и по этому могут быть использованы в блюпринтах, считаю это упущением данной статьи.

На мой взгляд основное отличие dynamic делегата от статического это именно то, как происходит бинд, от сюда вытакает и все остальное и дает более понимание о механизмах работы.

То есть именно потому что динамик делегат знает имя функции он может быть сериализован, использован в блюпринтах и требует UFUNCTION(). По этому же он и медленнее. Так же хочу заметить, что юникаст динамик делегат тоже может быть использован в блюпринтах как инпут-пин функции.

5
Ответить

ну вы посморите какой умный

2
Ответить

Их можно байндить по имени функции - все верно. Но если мне не изменяет память - в блюпринтах их всё-таки использовать нельзя. Проверить сейчас не могу 🤷🏻‍♂️

И кстати по адресу их тоже можно байндить

Ответить

Действительно полезная шпаргалка, спасибо.

3
Ответить

Спасибо!
Было бы круто еще добавть про
1. Делегаты с параметрами
2. Как их создавать в блюпринтах без С++ (хотя это уже не про виды, а про реализацию)

2
Ответить

👌🏻 Про параметры, возвращаемые значения ещё напишу отдельно. Но там несложно на самом деле

1
Ответить

Забыл упомянуть про Events, добавь в статью.

1
Ответить