【UE4_C++】处理事件和代理 (Delegate)

本章包含以下内容:

  • 处理通过虚函数实现的事件

  • 创建绑定到UFUNCTION的代理

  • 注销代理

  • 创建接受输入参数的代理

  • 使用代理绑定传递有效负载数据

  • 创建多播代理

  • 创建自定义事件

  • 创建一个钟表盘的时间处理器

  • 为第一人称射击游戏创建一个重新生成的拾取道具

一、处理通过虚函数实现的事件

Unreal 提供的一些 Actor 和 Component 类包括虚函数形式的事件处理程序。 这一小节将讲述如何通过重写问题中的虚拟函数来自定义这些处理程序。

创建新类:
在这里插入图片描述
添加代码:

MyTriggerVolume.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"
#include "MyTriggerVolume.generated.h"

UCLASS()
class NEWTUTORIALPROJECT_API AMyTriggerVolume : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    AMyTriggerVolume();
    UPROPERTY()
        UBoxComponent* TriggerZone;

    UFUNCTION()
        virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;

    UFUNCTION()
        virtual void NotifyActorEndOverlap(AActor* OtherActor) override;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

MyTriggerVolume.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyTriggerVolume.h"
#include "Engine/Engine.h"


// Sets default values
AMyTriggerVolume::AMyTriggerVolume()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    // Create a new component for the instance and initializeit
    TriggerZone = CreateDefaultSubobject<UBoxComponent>("TriggerZone"); 
    TriggerZone->SetBoxExtent(FVector(200, 200, 100));
}

void AMyTriggerVolume::NotifyActorBeginOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s entered me"),*(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
}

void AMyTriggerVolume::NotifyActorEndOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s left me"),*(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
}

// Called when the game starts or when spawned
void AMyTriggerVolume::BeginPlay()
{
    Super::BeginPlay();
    
}

// Called every frame
void AMyTriggerVolume::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

编译并且再编辑器中在场景中添加:

在这里插入图片描述
运行结果如下:
角色进入时:
在这里插入图片描述
角色离开时:

在这里插入图片描述

二、创建绑定到UFUNCTION的代理

指针是一个强大的工具,因为我们能够在运行时分配它们,并可以改变它们在内存中指向的位置。 除了标准类型之外,我们还可以创建指向函数的指针,但是这些原始函数指针不适合使用,原因有很多。代理是一个更安全的函数指针版本,它使我们能够灵活地调用函数,而不必知道哪个函数被赋值,直到被调用的那一刻。 这种灵活性是选择代理而非静态函数的主要原因之一。 这个例子将会讲述如何将 UFUNCTION 关联到代理,以便在执行代理时调用该代理。

我们将使用到上一个例子中创建的MyTriggerVolume,和Demo中已有的InventoryGameMode来进行该例子的讲解:

首先创建新类:
在这里插入图片描述
添加代码:
MyTriggerVolume.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"
#include "MyTriggerVolume.generated.h"

UCLASS()
class NEWTUTORIALPROJECT_API AMyTriggerVolume : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    AMyTriggerVolume();
    UPROPERTY()
        UBoxComponent* TriggerZone;

    UFUNCTION()
        virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;

    UFUNCTION()
        virtual void NotifyActorEndOverlap(AActor* OtherActor) override;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

MyTriggerVolume.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyTriggerVolume.h"
#include "Engine/Engine.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/GameModeBase.h"
#include "RPG/InventoryGameMode.h"
#include "Engine/World.h"


// Sets default values
AMyTriggerVolume::AMyTriggerVolume()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    // Create a new component for the instance and initializeit
    TriggerZone = CreateDefaultSubobject<UBoxComponent>("TriggerZone"); 
    TriggerZone->SetBoxExtent(FVector(200, 200, 100));
}

void AMyTriggerVolume::NotifyActorBeginOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s entered me"),*(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
    // Call our delegate 
    UWorld* TheWorld = GetWorld();

    if (TheWorld != nullptr)
    {
        AGameModeBase* GameMode =UGameplayStatics::GetGameMode(TheWorld); 
        AInventoryGameMode * MyGameMode =Cast<AInventoryGameMode>(GameMode);
        if (MyGameMode != nullptr)
        { 
            MyGameMode->MyStandardDelegate.ExecuteIfBound(); 
        }
    }
}

void AMyTriggerVolume::NotifyActorEndOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s left me"),*(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
}

// Called when the game starts or when spawned
void AMyTriggerVolume::BeginPlay()
{
    Super::BeginPlay();
    
}

// Called every frame
void AMyTriggerVolume::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

InventoryGameMode.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "InventoryCharacter.h"
#include "InventoryGameMode.generated.h"

DECLARE_DELEGATE(FStandardDelegateSignature)
/**
 * 
 */
UCLASS()
class NEWTUTORIALPROJECT_API AInventoryGameMode : public AGameModeBase
{
    GENERATED_BODY()
public:
    AInventoryGameMode();
    
    FStandardDelegateSignature MyStandardDelegate;
    
};

InventoryGameMode.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "InventoryGameMode.h"

AInventoryGameMode::AInventoryGameMode()
{
    DefaultPawnClass = AInventoryCharacter::StaticClass();
}

DelegateListener.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/PointLightComponent.h"
#include "DelegateListener.generated.h"

UCLASS()
class NEWTUTORIALPROJECT_API ADelegateListener : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    ADelegateListener();
    UFUNCTION()
        void EnableLight();
    UPROPERTY()
        UPointLightComponent* PointLight;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

DelegateListener.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "DelegateListener.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/GameModeBase.h"
#include "RPG/InventoryGameMode.h"
#include "Engine/World.h"


// Sets default values
ADelegateListener::ADelegateListener()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    // Create a point light 
    PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight"); RootComponent = PointLight;

// Turn it off at the beginning so we can turn it on later
// through code 
    PointLight->SetVisibility(false);

// Set the color to blue to make it easier to see 
    PointLight->SetLightColor(FLinearColor::Blue);
}

void ADelegateListener::EnableLight()
{
    PointLight->SetVisibility(true);
}

// Called when the game starts or when spawned
void ADelegateListener::BeginPlay()
{
    Super::BeginPlay();
    UWorld* TheWorld = GetWorld();
    if (TheWorld != nullptr) 
    {
        AGameModeBase* GameMode = UGameplayStatics::GetGameMode(TheWorld);
        AInventoryGameMode * MyGameMode = Cast<AInventoryGameMode>(GameMode);
        if (MyGameMode != nullptr) 
        {
            MyGameMode->MyStandardDelegate.BindUObject(this, &ADelegateListener::EnableLight); 
        }
    }
}

// Called every frame
void ADelegateListener::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}

在编辑器中将DelegateListener拖拽到场景中:

在这里插入图片描述
运行程序:
当我将角色移动进入触发体中时,可以看到灯亮了起来
在这里插入图片描述

三、注销代理

我们使用代理来处理事件时 有时,需要删除绑定。 这就像设置一个函数指针对象到 nullptr,这样它就不再引用已经被删除的对象。
上一例子中DelegateListener我们对EndPlay进行重写代码如下:

DelegateListener.h


   virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

DelegateListener.cpp

void ADelegateListener::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);
    UWorld* TheWorld = GetWorld();

    if (TheWorld != nullptr)
    {
        AGameModeBase* GameMode =UGameplayStatics::GetGameMode(TheWorld);
        AInventoryGameMode * MyGameMode =   Cast<AInventoryGameMode>(GameMode);
        if (MyGameMode != nullptr)
        {
            MyGameMode->MyStandardDelegate.Unbind();
        }
    }
}

这样可以做到当Actor销毁时代理自动注销。

四、创建接受输入参数的代理

前面例子主要是没有接受任何输入参数的代理,这个例子会讲解如何更改代理的签名让其接受某些输入。
我们还是使用上面例子中的GameMode

创建一个新类:
在这里插入图片描述
添加代码:

MyTriggerVolume.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"
#include "MyTriggerVolume.generated.h"

UCLASS()
class NEWTUTORIALPROJECT_API AMyTriggerVolume : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    AMyTriggerVolume();
    UPROPERTY()
        UBoxComponent* TriggerZone;

    UFUNCTION()
        virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;

    UFUNCTION()
        virtual void NotifyActorEndOverlap(AActor* OtherActor) override;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

MyTriggerVolume.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyTriggerVolume.h"
#include "Engine/Engine.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/GameModeBase.h"
#include "RPG/InventoryGameMode.h"
#include "Engine/World.h"


// Sets default values
AMyTriggerVolume::AMyTriggerVolume()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    // Create a new component for the instance and initializeit
    TriggerZone = CreateDefaultSubobject<UBoxComponent>("TriggerZone"); 
    TriggerZone->SetBoxExtent(FVector(200, 200, 100));
}

void AMyTriggerVolume::NotifyActorBeginOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s entered me"),*(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
    // Call our delegate 
    UWorld* TheWorld = GetWorld();

    if (TheWorld != nullptr)
    {
        AGameModeBase* GameMode =UGameplayStatics::GetGameMode(TheWorld); 
        AInventoryGameMode * MyGameMode =Cast<AInventoryGameMode>(GameMode);
        if (MyGameMode != nullptr)
        { 
            MyGameMode->MyStandardDelegate.ExecuteIfBound(); 
            // Call the function using a parameter 
            auto Color = FLinearColor(1, 0, 0, 1);
            MyGameMode->MyParameterDelegate.ExecuteIfBound(Color);
        }
    }

}

void AMyTriggerVolume::NotifyActorEndOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s left me"),*(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
}

// Called when the game starts or when spawned
void AMyTriggerVolume::BeginPlay()
{
    Super::BeginPlay();
    
}

// Called every frame
void AMyTriggerVolume::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

InventoryGameMode.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "InventoryCharacter.h"
#include "InventoryGameMode.generated.h"

DECLARE_DELEGATE(FStandardDelegateSignature)
DECLARE_DELEGATE_OneParam(FParamDelegateSignature, FLinearColor)
/**
 * 
 */
UCLASS()
class NEWTUTORIALPROJECT_API AInventoryGameMode : public AGameModeBase
{
    GENERATED_BODY()
public:
    AInventoryGameMode();
    
    FStandardDelegateSignature MyStandardDelegate;
    
    FParamDelegateSignature MyParameterDelegate;
};

InventoryGameMode.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "InventoryGameMode.h"




AInventoryGameMode::AInventoryGameMode()
{
    DefaultPawnClass = AInventoryCharacter::StaticClass();
}

ParamDelegateListener.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/PointLightComponent.h"
#include "ParamDelegateListener.generated.h"

UCLASS()
class NEWTUTORIALPROJECT_API AParamDelegateListener : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    AParamDelegateListener();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    UFUNCTION() 
        void SetLightColor(FLinearColor LightColor);

    UPROPERTY() 
        UPointLightComponent* PointLight;
    
    UFUNCTION()
        virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};

ParamDelegateListener.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "ParamDelegateListener.h"
#include "RPG/InventoryGameMode.h"
#include "GameFramework/GameModeBase.h"
#include "Kismet/GameplayStatics.h"
#include "Engine/World.h"


// Sets default values
AParamDelegateListener::AParamDelegateListener()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight");
    RootComponent = PointLight;
}

// Called when the game starts or when spawned
void AParamDelegateListener::BeginPlay()
{
    Super::BeginPlay();

    UWorld* TheWorld = GetWorld();

    if (TheWorld != nullptr)
    {
        AGameModeBase* GameMode =UGameplayStatics::GetGameMode(TheWorld);   
        AInventoryGameMode * MyGameMode =Cast<AInventoryGameMode>(GameMode);

        if (MyGameMode != nullptr)
        {
            MyGameMode->MyParameterDelegate.BindUObject(this,&AParamDelegateListener::SetLightColor);
        }
    }
}

// Called every frame
void AParamDelegateListener::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

void AParamDelegateListener::SetLightColor(FLinearColor LightColor)
{
    PointLight->SetLightColor(LightColor);
}

void AParamDelegateListener::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);
    UWorld* TheWorld = GetWorld();

    if (TheWorld != nullptr)
    {
        AGameModeBase* GameMode = UGameplayStatics::GetGameMode(TheWorld);
        AInventoryGameMode * MyGameMode = Cast<AInventoryGameMode>(GameMode);
        if (MyGameMode != nullptr)
        {
            MyGameMode->MyParameterDelegate.Unbind();
        }
    }
}

编译完成后将ParamDelegateListener拖拽到场景中:
在这里插入图片描述
角色进入之后可见原先的白色灯变为红色。
在这里插入图片描述

五、使用代理绑定传递有效负载数据

我们将上一例子中的代码进行一些小修改:
在这里插入图片描述
编译后,我们运行程序将角色移动到触发体中:
可以看到灯被关掉了
在这里插入图片描述

六、创建多播代理

到目前为止,我们在本章中使用的标准代理本质上是函数指针: 它们允许您对一个特定的对象实例调用一个特定的函数。 多播代理是函数指针的集合,每个指针都可能位于不同的对象上,所有这些指针都将在广播代理时调用。

本例子之前我们已经处理通过虚函数实现的事件,知道了如何创建 TriggerVolume ,本例将它用于广播多播代理。

创建新类:
在这里插入图片描述
添加代码:

MyTriggerVolume.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"
#include "MyTriggerVolume.generated.h"

UCLASS()
class NEWTUTORIALPROJECT_API AMyTriggerVolume : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    AMyTriggerVolume();
    UPROPERTY()
        UBoxComponent* TriggerZone;

    UFUNCTION()
        virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;

    UFUNCTION()
        virtual void NotifyActorEndOverlap(AActor* OtherActor) override;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

MyTriggerVolume.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyTriggerVolume.h"
#include "Engine/Engine.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/GameModeBase.h"
#include "RPG/InventoryGameMode.h"
#include "Engine/World.h"


// Sets default values
AMyTriggerVolume::AMyTriggerVolume()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    // Create a new component for the instance and initializeit
    TriggerZone = CreateDefaultSubobject<UBoxComponent>("TriggerZone");
    TriggerZone->SetBoxExtent(FVector(200, 200, 100));
}

void AMyTriggerVolume::NotifyActorBeginOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s entered me"), *(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
    // Call our delegate 
    UWorld* TheWorld = GetWorld();

    if (TheWorld != nullptr)
    {
        AGameModeBase* GameMode = UGameplayStatics::GetGameMode(TheWorld);
        AInventoryGameMode * MyGameMode = Cast<AInventoryGameMode>(GameMode);
        if (MyGameMode != nullptr)
        {
            MyGameMode->MyStandardDelegate.ExecuteIfBound();
            // Call the function using a parameter 
            auto Color = FLinearColor(1, 0, 0, 1);
            MyGameMode->MyParameterDelegate.ExecuteIfBound(Color);
            MyGameMode->MyMulticastDelegate.Broadcast();
        }
    }

}

void AMyTriggerVolume::NotifyActorEndOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s left me"), *(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
}

// Called when the game starts or when spawned
void AMyTriggerVolume::BeginPlay()
{
    Super::BeginPlay();

}

// Called every frame
void AMyTriggerVolume::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}


InventoryGameMode.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "InventoryCharacter.h"
#include "InventoryGameMode.generated.h"

DECLARE_DELEGATE(FStandardDelegateSignature)
DECLARE_DELEGATE_OneParam(FParamDelegateSignature, FLinearColor)
DECLARE_MULTICAST_DELEGATE(FMulticastDelegateSignature)
/**
 * 
 */
UCLASS()
class NEWTUTORIALPROJECT_API AInventoryGameMode : public AGameModeBase
{
    GENERATED_BODY()
public:
    AInventoryGameMode();
    
    FStandardDelegateSignature MyStandardDelegate;
    
    FParamDelegateSignature MyParameterDelegate;

    FMulticastDelegateSignature MyMulticastDelegate;
};

InventoryGameMode.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "InventoryGameMode.h"




AInventoryGameMode::AInventoryGameMode()
{
    DefaultPawnClass = AInventoryCharacter::StaticClass();
}

MulticastDelegateListener.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/PointLightComponent.h"
#include "MulticastDelegateListener.generated.h"

UCLASS()
class NEWTUTORIALPROJECT_API AMulticastDelegateListener : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    AMulticastDelegateListener();

    UFUNCTION() 
        void ToggleLight();

    UFUNCTION() 
        virtual void EndPlay(const EEndPlayReason::Type EndPlayReason)override;

    UPROPERTY() 
        UPointLightComponent* PointLight;

    FDelegateHandle MyDelegateHandle;

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

MulticastDelegateListener.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "MulticastDelegateListener.h"
#include "Engine/World.h"
#include "GameFramework/GameModeBase.h"
#include "RPG/InventoryGameMode.h"
#include "Kismet/GameplayStatics.h"


// Sets default values
AMulticastDelegateListener::AMulticastDelegateListener()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight"); RootComponent = PointLight;
}

void AMulticastDelegateListener::ToggleLight()
{
    PointLight->ToggleVisibility();
}

void AMulticastDelegateListener::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);

    UWorld* TheWorld = GetWorld();

    if (TheWorld != nullptr)
    {
        AGameModeBase* GameMode = UGameplayStatics::GetGameMode(TheWorld);

        AInventoryGameMode * MyGameMode =Cast<AInventoryGameMode>(GameMode);

        if (MyGameMode != nullptr)
        {
            MyGameMode -> MyMulticastDelegate.Remove(MyDelegateHandle);
        }
    }
}

// Called when the game starts or when spawned
void AMulticastDelegateListener::BeginPlay()
{
    Super::BeginPlay();
    UWorld* TheWorld = GetWorld();

    if (TheWorld != nullptr) {
        AGameModeBase* GameMode = UGameplayStatics::GetGameMode(TheWorld);

        AInventoryGameMode * MyGameMode = Cast<AInventoryGameMode>(GameMode);

        if (MyGameMode != nullptr) 
        {
            MyDelegateHandle = MyGameMode -> MyMulticastDelegate.AddUObject(this,&AMulticastDelegateListener::ToggleLight);
        }
    }
}

// Called every frame
void AMulticastDelegateListener::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

编译后在编辑器中将4个MultiCastDelegateListener拖拽到创建中:
角色进入触发体后四个灯同时灭掉了:
角色出来触发体再重新进去四个灯又同时亮了:
在这里插入图片描述
在这里插入图片描述
以下做一个小验证,将前面例子中的蓝灯再复制一个,运行程序让角色走进触发体中:
在这里插入图片描述
可以看到后来复制出来的才有效果,这个验证说明,这个非多播代理只有最后一个绑定的代理的对象才能绑定成功,出现新的绑定,之前的绑定会被作废。
在这里插入图片描述

七、创建自定义事件

自定义代理非常有用,但是它们的一个局限性是可以由其他第三方类在外部广播; 也就是说,它们的 execute / broadcast 方法是可公开访问的。
有时,我们可能需要一个代理,该代理可由其他类在外部分配,但只能由包含它们的类广播。 这是事件的主要目的。

这一小节,还是基于前面的GameMode和MyTriggerVolume来进行。

创建新类:
在这里插入图片描述
添加代码:

MyTriggerVolume.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"
#include "MyTriggerVolume.generated.h"


DECLARE_EVENT(AMyTriggerVolume, FPlayerEntered)

UCLASS()
class NEWTUTORIALPROJECT_API AMyTriggerVolume : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    AMyTriggerVolume();
    UPROPERTY()
        UBoxComponent* TriggerZone;

    UFUNCTION()
        virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;

    UFUNCTION()
        virtual void NotifyActorEndOverlap(AActor* OtherActor) override;


    FPlayerEntered OnPlayerEntered;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

MyTriggerVolume.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "MyTriggerVolume.h"
#include "Engine/Engine.h"
#include "Kismet/GameplayStatics.h"
#include "GameFramework/GameModeBase.h"
#include "RPG/InventoryGameMode.h"
#include "Engine/World.h"


// Sets default values
AMyTriggerVolume::AMyTriggerVolume()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    // Create a new component for the instance and initializeit
    TriggerZone = CreateDefaultSubobject<UBoxComponent>("TriggerZone");
    TriggerZone->SetBoxExtent(FVector(200, 200, 100));
}

void AMyTriggerVolume::NotifyActorBeginOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s entered me"), *(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
    // Call our delegate 
    UWorld* TheWorld = GetWorld();

    if (TheWorld != nullptr)
    {
        AGameModeBase* GameMode = UGameplayStatics::GetGameMode(TheWorld);
        AInventoryGameMode * MyGameMode = Cast<AInventoryGameMode>(GameMode);
        if (MyGameMode != nullptr)
        {
            MyGameMode->MyStandardDelegate.ExecuteIfBound();
            // Call the function using a parameter 
            auto Color = FLinearColor(1, 0, 0, 1);
            MyGameMode->MyParameterDelegate.ExecuteIfBound(Color);
            MyGameMode->MyMulticastDelegate.Broadcast();
            OnPlayerEntered.Broadcast();
        }
    }

}

void AMyTriggerVolume::NotifyActorEndOverlap(AActor* OtherActor)
{
    auto Message = FString::Printf(TEXT("%s left me"), *(OtherActor->GetName()));
    GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Red, Message);
}

// Called when the game starts or when spawned
void AMyTriggerVolume::BeginPlay()
{
    Super::BeginPlay();

}

// Called every frame
void AMyTriggerVolume::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

TriggerVolEventListener.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyTriggerVolume.h"
#include "Components/PointLightComponent.h"
#include "TriggerVolEventListener.generated.h"


UCLASS()
class NEWTUTORIALPROJECT_API ATriggerVolEventListener : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    ATriggerVolEventListener();

    UPROPERTY() 
        UPointLightComponent* PointLight;

    UPROPERTY(EditAnywhere)
        AMyTriggerVolume* TriggerEventSource;

    UFUNCTION() 
        void OnTriggerEvent();
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

TriggerVolEventListener.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "TriggerVolEventListener.h"


// Sets default values
ATriggerVolEventListener::ATriggerVolEventListener()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight"); 
    RootComponent = PointLight;
}

void ATriggerVolEventListener::OnTriggerEvent()
{
    PointLight->SetLightColor(FLinearColor(0, 1, 0, 1));
}

// Called when the game starts or when spawned
void ATriggerVolEventListener::BeginPlay()
{
    Super::BeginPlay();
    if (TriggerEventSource != nullptr) 
    {       
        TriggerEventSource->OnPlayerEntered.AddUObject(this,&ATriggerVolEventListener::OnTriggerEvent);
    }
}

// Called every frame
void ATriggerVolEventListener::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

编译完成后。在编辑器中我们拖拽一个TriggerVolEventListener到场景中,并且使用滴管选取MyTriggerVolume:
在这里插入图片描述
运行程序后,角色进去触发体后TriggerVolEventListener中的灯就亮起来了。
在这里插入图片描述

八、创建一个钟表盘的时间处理器

这个例子将会讲述如何使用前面例子中引入的概念来创建一个Actor,告知其他Actor游戏中的时间流逝。

创建新类:
在这里插入图片描述
Clock的截图是后面补上去的可以忽略掉报错提示信息。
在这里插入图片描述
添加代码:

TimeOfDayHandler.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TimeOfDayHandler.generated.h"


DECLARE_MULTICAST_DELEGATE_TwoParams(FOnTimeChangedSignature, int32,int32)
UCLASS()
class NEWTUTORIALPROJECT_API ATimeOfDayHandler : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    ATimeOfDayHandler();

    FOnTimeChangedSignature OnTimeChanged;

    UPROPERTY()
        int32 TimeScale;
    UPROPERTY()
        int32 Hours;
    UPROPERTY()
        int32 Minutes;
    UPROPERTY()
        float ElapsedSeconds;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

TimeOfDayHandler.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "TimeOfDayHandler.h"


// Sets default values
ATimeOfDayHandler::ATimeOfDayHandler()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    TimeScale = 60; Hours = 0; Minutes = 0; ElapsedSeconds = 0;
}

// Called when the game starts or when spawned
void ATimeOfDayHandler::BeginPlay()
{
    Super::BeginPlay();
    
}

// Called every frame
void ATimeOfDayHandler::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    ElapsedSeconds += (DeltaTime * TimeScale);

    if (ElapsedSeconds > 60) {
        ElapsedSeconds -= 60; 
        Minutes++;

        if (Minutes > 60) 
        { 
            Minutes -= 60; Hours++;
        }

        OnTimeChanged.Broadcast(Hours, Minutes);
    }
}

Clock.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/SceneComponent.h"
#include "Components/StaticMeshComponent.h"
#include "IDelegateInstance.h"
#include "Clock.generated.h"

UCLASS()
class NEWTUTORIALPROJECT_API AClock : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    AClock();
    UPROPERTY() 
        USceneComponent* RootSceneComponent;

    UPROPERTY()
        UStaticMeshComponent* ClockFace;

    UPROPERTY()
        USceneComponent* HourHandle;

    UPROPERTY()
        UStaticMeshComponent* HourHand;

    UPROPERTY() 
        USceneComponent* MinuteHandle;

    UPROPERTY() 
        UStaticMeshComponent* MinuteHand;

    UFUNCTION() 
        void TimeChanged(int32 Hours, int32 Minutes);

    FDelegateHandle MyDelegateHandle;
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

Clock.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "Clock.h"
#include "ConstructorHelpers.h"
#include "Kismet/GameplayStatics.h"
#include "TimeOfDayHandler.h"


// Sets default values
AClock::AClock()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    RootSceneComponent = CreateDefaultSubobject<USceneComponent>("RootSceneComponent");
    ClockFace = CreateDefaultSubobject<UStaticMeshComponent>("ClockFace");
    HourHand = CreateDefaultSubobject<UStaticMeshComponent>("HourHand");
    MinuteHand = CreateDefaultSubobject<UStaticMeshComponent>("MinuteHand");
    HourHandle = CreateDefaultSubobject<USceneComponent>("HourHandle");
    MinuteHandle = CreateDefaultSubobject<USceneComponent>("MinuteHandle");

    auto MeshAsset = ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("StaticMesh'/Engine/BasicShapes/Cylinder.Cylinder'"));

    if (MeshAsset.Object != nullptr)
    {
        ClockFace->SetStaticMesh(MeshAsset.Object);
        HourHand->SetStaticMesh(MeshAsset.Object);
        MinuteHand->SetStaticMesh(MeshAsset.Object);
    }

    RootComponent = RootSceneComponent;

    HourHand->AttachToComponent(HourHandle,FAttachmentTransformRules::KeepRelativeTransform);

    MinuteHand->AttachToComponent(MinuteHandle,FAttachmentTransformRules::KeepRelativeTransform);

    HourHandle->AttachToComponent(RootSceneComponent,FAttachmentTransformRules::KeepRelativeTransform);

    MinuteHandle->AttachToComponent(RootSceneComponent,FAttachmentTransformRules::KeepRelativeTransform);

    ClockFace->AttachToComponent(RootSceneComponent,FAttachmentTransformRules::KeepRelativeTransform);

    ClockFace->SetRelativeTransform(FTransform(FRotator(90, 0, 0),FVector(10, 0, 0),FVector(2, 2, 0.1)));

    HourHand->SetRelativeTransform(FTransform(FRotator(0, 0, 0),FVector(0, 0, 25),FVector(0.1, 0.1, 0.5)));

    MinuteHand->SetRelativeTransform(FTransform(FRotator(0, 0, 0),FVector(0, 0, 50),FVector(0.1, 0.1, 1)));
}

void AClock::TimeChanged(int32 Hours, int32 Minutes)
{
    HourHandle->SetRelativeRotation(FRotator(0, 0, 30 * Hours));
    MinuteHandle->SetRelativeRotation(FRotator(0, 0, 6 * Minutes));
}

// Called when the game starts or when spawned
void AClock::BeginPlay()
{
    Super::BeginPlay();

    TArray<AActor*> TimeOfDayHandlers;
    UGameplayStatics::GetAllActorsOfClass(GetWorld(),ATimeOfDayHandler::StaticClass(),TimeOfDayHandlers);
    if (TimeOfDayHandlers.Num() != 0)
    {
        auto TimeOfDayHandler = Cast<ATimeOfDayHandler>(TimeOfDayHandlers[0]); 
        MyDelegateHandle = TimeOfDayHandler->OnTimeChanged.AddUObject(this,&AClock::TimeChanged);
    }
}

// Called every frame
void AClock::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

编译完成后打开编辑器将Clock和TimeOfDayHandler拖拽到场景中:
在这里插入图片描述
运行程序我们会看到秒针开始动:
在这里插入图片描述
快速运行验证分针转动的方式则是 slomo 20 后面是数字是运行倍数。

九、为第一人称射击游戏创建一个重新生成的拾取道具

这个例子将会讲述如何创建一个可以防止的拾取道具,它会在一定的时间后重生,适合作为第一人称射击游戏的弹药或者其他可拾取道具

首先,创建新类:
在这里插入图片描述
在这里插入图片描述
添加代码:

Pickup.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GameFramework/RotatingMovementComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Pickup.generated.h"



DECLARE_DELEGATE(FPickedupEventSignature)
UCLASS()
class NEWTUTORIALPROJECT_API APickup : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    APickup();

    virtual void NotifyActorBeginOverlap(AActor* OtherActor)override;

    UPROPERTY()
        UStaticMeshComponent* MyMesh;

    UPROPERTY()
        URotatingMovementComponent* RotatingComponent;

    FPickedupEventSignature OnPickedUp;

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

Pickup.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "Pickup.h"
#include "ConstructorHelpers.h"


// Sets default values
APickup::APickup()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    MyMesh = CreateDefaultSubobject<UStaticMeshComponent>("MyMesh");

    RotatingComponent =CreateDefaultSubobject<URotatingMovementComponent>("RotatingComponent"); RootComponent = MyMesh;

    auto MeshAsset = ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("StaticMesh'/Engine/BasicShapes/Cube.Cube'"));

    if (MeshAsset.Object != nullptr) 
    { 
        MyMesh->SetStaticMesh(MeshAsset.Object); 
    }

    MyMesh->SetCollisionProfileName(TEXT("OverlapAllDynamic")); RotatingComponent->RotationRate = FRotator(10, 0, 10);
}

void APickup::NotifyActorBeginOverlap(AActor* OtherActor)
{
    OnPickedUp.ExecuteIfBound();
}

// Called when the game starts or when spawned
void APickup::BeginPlay()
{
    Super::BeginPlay();
    
}

// Called every frame
void APickup::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

PickupSpawner.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Pickup.h"
#include "Components/SceneComponent.h"
#include "PickupSpawner.generated.h"


UCLASS()
class NEWTUTORIALPROJECT_API APickupSpawner : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    APickupSpawner();

    UPROPERTY() 
        USceneComponent* SpawnLocation;

    UFUNCTION() 
        void PickupCollected();

    UFUNCTION() 
        void SpawnPickup();

    UPROPERTY() 
        APickup* CurrentPickup;

    FTimerHandle MyTimer;

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    
    
};

PickupSpawner.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "PickupSpawner.h"
#include "Engine/World.h"
#include "Pickup.h"


// Sets default values
APickupSpawner::APickupSpawner()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    SpawnLocation = CreateDefaultSubobject<USceneComponent>("SpawnLocation");
}

void APickupSpawner::PickupCollected()
{
    GetWorld()->GetTimerManager().SetTimer(MyTimer,this,&APickupSpawner::SpawnPickup,10,false);
    CurrentPickup->OnPickedUp.Unbind();
    CurrentPickup->Destroy();
}

void APickupSpawner::SpawnPickup()
{
    UWorld* MyWorld = GetWorld();

    if (MyWorld != nullptr)
    {
        CurrentPickup = MyWorld->SpawnActor<APickup>(APickup::StaticClass(),GetTransform());

        CurrentPickup->OnPickedUp.BindUObject(this,&APickupSpawner::PickupCollected);
    }
}

// Called when the game starts or when spawned
void APickupSpawner::BeginPlay()
{
    Super::BeginPlay();
    SpawnPickup();
}

// Called every frame
void APickupSpawner::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

编译完之后,在编辑器中将PickupSpawner拖拽到场景中:
运行之后我们会看到生成了一个Pickup:
在这里插入图片描述
我们将角色移动到Pickup上会发现Pickup被销毁掉,也就是所谓的被拾取:
在这里插入图片描述
等待10s,会发现又生成了一个新的Pickup
在这里插入图片描述

发布了5 篇原创文章 · 获赞 0 · 访问量 30

猜你喜欢

转载自blog.csdn.net/qq_34261504/article/details/104953752
今日推荐