К основному контенту

Создание делегата, который связан с UFUNCTION.

Делегаты позволяют нам вызывать функцию не знаю какая именно назначена функция. Они являются безопасной версией указателя на функцию. Здесь будет разобрано как ассоциировать UFUNCTION с делегатом, чтобы она вызывалась, когда он выполняется.

Чтобы получился этот пример, необходимо использовать проект с классом TriggerVolume из прошлой статьи.

Как это сделать...

  1. Внутри заголовочного файла GameMode объявить делегат с помощью специального макроса, который нужно написать над UCLASS():
    DECLARE_DELEGATE(FStandardDelegateSignature)
    UCLASS()
    class EVENT_API AEventGameModeBase : public AGameModeBase
    
  2. Добавить новый член класса в GameMode:
    FStandardDelegateSignature MyStandardDelegate;
  3. Создать в редакторе движка новый класс Actor, назвав его DelegateListener. И добавить в заголовочный файл нового класса следующие объявления:
    UFUNCTION()
    void EnableLight();
    
    UPROPERTY()
    UPointLightComponent* PointLight;
  4. В конструктор класса, надо добавить код для создания компоненты направленного освещения:
    PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight");
    RootComponent = PointLight;
    PointLight->SetVisibility(false);
  5. Внутри функции DelegateListener::BeginPlay(), добавить следующую реализацию:
    const UWorld* TheWorld = GetWorld();
    if (TheWorld != nullptr)
    {
        AGameModeBase* GameMode = UGameplayStatics::GetGameMode(TheWorld);
        AEventGameModeBase * MyGameMode = Cast<AEventGameModeBase>(GameMode);
        if (MyGameMode != nullptr)
        {
            MyGameMode->MyStandardDelegate.BindUObject(this, &ADelegateListener::EnableLight);
        }
    }
  6. Написать функцию EnableLight():
    void ADelegateListener::EnableLight()
    {
        PointLight->SetVisibility(true);
    }
  7. Поместить следующий код в функцию NotifyActorBeginOverlap() класса TriggerVolume:
    void AMyTriggerVolume::NotifyActorBeginOverlap(AActor * OtherActor)
    {
        const UWorld* TheWorld = GetWorld();
        if (TheWorld != nullptr)
        {
            AGameModeBase* GameMode = UGameplayStatics::GetGameMode(TheWorld);
            AEventGameModeBase * MyGameMode = Cast<AEventGameModeBase>(GameMode);
            MyGameMode->MyStandardDelegate.ExecuteIfBound();
        }
    }
  8. Теперь можно скомпилировать проект. Также необходимо убедиться, что для уровня установлен мой GameMode, что можно сделать в настройках мира внутри редактора движка.

После этого надо перетащить на уровень объекты классов AMyTriggerVolume и ADelegateListener.

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

Как это работает...

  1. Внутри заголовочного файла GameMode устанавливается тип делегата без параметров, который называется FTriggerHitSignature. (Не знаю, опечатка это или я что-то упускаю, но не вижу ничего с таким названием.)
  2. Далее создается делегат как член класса GameMode.
  3. Добавляется компонент PointLight в класс DelegateListener, таким образом теперь у нас есть визуальное представление выполнения делегата.
  4. В конструкторе создается компонент UPointLightComponent.
  5. В переопределенном BeginPlay() получаем указатель на текущий игровой мир и используем его для получения установленного для мира GameMode через функцию GetGameMode().
  6. С помощью шаблонной функции Cast приводим полученный AGameMode* к моему типу AEventGameModeBase.
  7. После этого появляется доступ к делегату внутри GameMode, которого связываем с функцией EnableLight(). Теперь если делегат будет выполнятся, то произойдет вызов этой функции. Т.е. класс DelegateListener подписывается на сигнал от делегата из GameMode.
  8. В данном случае с делегатом связывается мною созданная функция UFUNCTION(), когда используется BindUObject(). Если нужно связать обычную функцию класса С++, то можно использовать BindRaw(). А в случае, если надо связать статическую функцию, то надо использовать BindStatic().
  9. Когда игрок пересекает объект TriggerVolume, то тот находит GameMode и запускает выполнение делегата через ExecuteIfBound().
  10. ExecuteIfBound() проверяет есть ли присоединенные к делегату функции и затем вызывает их.
  11. Функция EnableLight() включает компонент PointLight, когда вызывается делегат.

Комментарии