【转载】UE4 代理委托(代理,动态代理,单播,多播)

919 阅读17分钟

版权声明:本文为CSDN博主「落寞根号七」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/luomogenhao…

官方例子
创建基于官方第三人称模版 C++ 工程,项目名字:MyTP,以下例子基于该工程实现。
代理委托就是 函数指针(类成员函数指针),函数指针指向函数地址,然后调用该函数指针,实现所需效果。

1、代理声明、绑定、执行(单播)

新建 MyTPCharacter.h

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
 
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyTPCharacter.generated.h"
 
 
/// @note 声明无参代理
DECLARE_DELEGATE(D_NoParam)
/// @note 声明 1 个参数的代理
DECLARE_DELEGATE_OneParam(D_1Param, FString)
/// @note 声明 5 个参数的代理
DECLARE_DELEGATE_FiveParams(D_5Param, FString, FString, FString, FString, FString)
/// @note 返回值代理
DECLARE_DELEGATE_RetVal(FString, D_RV)
 
 
UCLASS(config=Game)
class AMyTPCharacter : public ACharacter
{
	GENERATED_BODY()
 
	/** Camera boom positioning the camera behind the character */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class USpringArmComponent* CameraBoom;
 
	/** Follow camera */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
	class UCameraComponent* FollowCamera;
public:
	AMyTPCharacter();
 
	/** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseTurnRate;
 
	/** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Camera)
	float BaseLookUpRate;
 
 
	/// @note 定义代理
	D_NoParam DelegateNoParam;
	D_1Param Delegate1Param;
	D_5Param Delegate5Param;
	D_RV DelegateRV;
 
	/// @note 绑定到代理的函数
	void FuncNoParam();
	void Func1Param(FString Str);
	void Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5);
	FString FuncRV();
 
 
	void OnRunEvent();///< 绑定到 Q 键函数,执行所有代理
	void PrintScreenStr(FString Str);// 打印日志到屏幕函数
 
protected:
 
	/** Resets HMD orientation in VR. */
	void OnResetVR();
 
	/** Called for forwards/backward input */
	void MoveForward(float Value);
 
	/** Called for side to side input */
	void MoveRight(float Value);
 
	/** 
	 * Called via input to turn at a given rate. 
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
	void TurnAtRate(float Rate);
 
	/**
	 * Called via input to turn look up/down at a given rate. 
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
	void LookUpAtRate(float Rate);
 
	/** Handler for when a touch input begins. */
	void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);
 
	/** Handler for when a touch input stops. */
	void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);
 
protected:
	// APawn interface
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
	// End of APawn interface
 
public:
	/** Returns CameraBoom subobject **/
	FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
	/** Returns FollowCamera subobject **/
	FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};

新建 MyTPCharacter.cpp

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#include "MyTPCharacter.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"

#include "Engine/GameEngine.h"

//
// AMyTPCharacter

AMyTPCharacter::AMyTPCharacter()
{

    /// @note 绑定函数
    DelegateNoParam.BindUObject(this, &AMyTPCharacter::FuncNoParam);
    Delegate1Param.BindUObject(this, &AMyTPCharacter::Func1Param);
    Delegate5Param.BindUObject(this, &AMyTPCharacter::Func5Param);
    DelegateRV.BindUObject(this, &AMyTPCharacter::FuncRV);

    // Set size for collision capsule
    GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

    // set our turn rates for input
    BaseTurnRate = 45.f;
    BaseLookUpRate = 45.f;

    // Don't rotate when the controller rotates. Let that just affect the camera.
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;
    bUseControllerRotationRoll = false;

    // Configure character movement
    GetCharacterMovement()->bOrientRotationToMovement = true;            // Character moves in the direction of input...
    GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
    GetCharacterMovement()->JumpZVelocity = 600.f;
    GetCharacterMovement()->AirControl = 0.2f;

    // Create a camera boom (pulls in towards the player if there is a collision)
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(RootComponent);
    CameraBoom->TargetArmLength = 300.0f;       // The camera follows at this distance behind the character
    CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

    // Create a follow camera
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    FollowCamera->bUsePawnControlRotation = false;                              // Camera does not rotate relative to arm

    // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
    // are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)
}

//
// Input

void AMyTPCharacter::SetupPlayerInputComponent(class UInputComponent *PlayerInputComponent)
{
    // Set up gameplay key bindings
    check(PlayerInputComponent);

    PlayerInputComponent->BindAction("RunEvent", IE_Pressed, this, &AMyTPCharacter::OnRunEvent);

    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

    PlayerInputComponent->BindAxis("MoveForward", this, &AMyTPCharacter::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &AMyTPCharacter::MoveRight);

    // We have 2 versions of the rotation bindings to handle different kinds of devices differently
    // "turn" handles devices that provide an absolute delta, such as a mouse.
    // "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
    PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
    PlayerInputComponent->BindAxis("TurnRate", this, &AMyTPCharacter::TurnAtRate);
    PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    PlayerInputComponent->BindAxis("LookUpRate", this, &AMyTPCharacter::LookUpAtRate);

    // handle touch devices
    PlayerInputComponent->BindTouch(IE_Pressed, this, &AMyTPCharacter::TouchStarted);
    PlayerInputComponent->BindTouch(IE_Released, this, &AMyTPCharacter::TouchStopped);

    // VR headset functionality
    PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AMyTPCharacter::OnResetVR);
}

void AMyTPCharacter::PrintScreenStr(FString Str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Red, Str);
    }
}

void AMyTPCharacter::OnRunEvent()
{
    /// @note 执行委托
    DelegateNoParam.ExecuteIfBound();
    Delegate1Param.ExecuteIfBound("Delegate1Param!");
    Delegate5Param.ExecuteIfBound("1Param!", "2Param!", "3Param!", "4Param!", "5Param!");
    FString CurRV = DelegateRV.Execute();
    PrintScreenStr(CurRV);
}

/// @note 绑定到委托的函数定义
void AMyTPCharacter::FuncNoParam()
{
    PrintScreenStr("NoParam");
}
void AMyTPCharacter::Func1Param(FString Str)
{
    PrintScreenStr(Str);
}
void AMyTPCharacter::Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5)
{
    PrintScreenStr(Str1 + " " + Str2 + " " + Str3 + " " + Str4 + " " + Str5);
}
FString AMyTPCharacter::FuncRV()
{
    return FString("FuncRV");
}
/// --------------------------------

void AMyTPCharacter::OnResetVR()
{
    UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
}

void AMyTPCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
{
    Jump();
}

void AMyTPCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
{
    StopJumping();
}

void AMyTPCharacter::TurnAtRate(float Rate)
{
    // calculate delta for this frame from the rate information
    AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void AMyTPCharacter::LookUpAtRate(float Rate)
{
    // calculate delta for this frame from the rate information
    AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}

void AMyTPCharacter::MoveForward(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
        // find out which way is forward
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // get forward vector
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
}

void AMyTPCharacter::MoveRight(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
        // find out which way is right
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // get right vector
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
        // add movement in that direction
        AddMovementInput(Direction, Value);
    }
}

绑定 Q 键如下图:

image.png

启动 游戏,摁下 Q 键盘,屏幕信息如下:

image.png

2、代理各种绑定(单播)

还是基于步骤 1,编辑器创建一个基于 None 的类,名字:MyTest

新建 MyTest.h

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

#include "CoreMinimal.h"

/**
 * 
 */
class MYTP_API MyTest
{
public:
    MyTest();
    ~MyTest();
    void TestFunc(FString Str);
};

新建 MyTest.cpp

// Fill out your copyright notice in the Description page of Project Settings.
 
#include "MyTest.h"
#include "Engine/GameEngine.h"

MyTest::MyTest()
{
}

MyTest::~MyTest()
{
}

void MyTest::TestFunc(FString Str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, Str);
    }
}

更新 MyTPCharacter.h

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
 
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyTest.h"
#include "MyTPCharacter.generated.h"

/// @note 声明无参代理
DECLARE_DELEGATE(D_NoParam)
/// @note  声明 1 个参数的代理
DECLARE_DELEGATE_OneParam(D_1Param, FString)
/// @note 声明 5 个参数的代理
DECLARE_DELEGATE_FiveParams(D_5Param, FString, FString, FString, FString, FString)
/// @note 返回值代理
DECLARE_DELEGATE_RetVal(FString, D_RV)

static void Static_Func(FString Str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, Str);
    }
}

UCLASS(config = Game)
class AMyTPCharacter : public ACharacter
{
    GENERATED_BODY()

    /** Camera boom positioning the camera behind the character */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    class USpringArmComponent *CameraBoom;

    /** Follow camera */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    class UCameraComponent *FollowCamera;

public:
    AMyTPCharacter();

    /** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
    float BaseTurnRate;

    /** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
    float BaseLookUpRate;

    /// @note 定义代理
    D_NoParam DelegateNoParam;
    D_1Param Delegate1Param;
    D_5Param Delegate5Param;
    D_RV DelegateRV;

    /// @note 绑定到代理的函数
    void FuncNoParam();
    void Func1Param(FString Str);
    void Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5);
    FString FuncRV();

    void OnRunEvent();                ///< 绑定到 Q 键函数,执行所有代理
    void PrintScreenStr(FString Str); ///< 打印日志到屏幕函数

    //定义多种绑定
    D_1Param DelegateLambda;
    D_1Param DelegateRaw;
    D_1Param DelegateSP;
    D_1Param DelegateStatic;
    D_1Param DelegateUFunction;

    UFUNCTION() ///< 可以声明 BlueprintCallable 等其他标记
    void TestUFunc(FString str);
    MyTest MyT;
    TSharedPtr<MyTest> MyT_SPtr;

protected:
    /** Resets HMD orientation in VR. */
    void OnResetVR();

    /** Called for forwards/backward input */
    void MoveForward(float Value);

    /** Called for side to side input */
    void MoveRight(float Value);

    /** 
	 * Called via input to turn at a given rate. 
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
    void TurnAtRate(float Rate);

    /**
	 * Called via input to turn look up/down at a given rate. 
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
    void LookUpAtRate(float Rate);

    /** Handler for when a touch input begins. */
    void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);

    /** Handler for when a touch input stops. */
    void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);

protected:
    // APawn interface
    virtual void SetupPlayerInputComponent(class UInputComponent *PlayerInputComponent) override;
    // End of APawn interface

public:
    /** Returns CameraBoom subobject **/
    FORCEINLINE class USpringArmComponent *GetCameraBoom() const { return CameraBoom; }
    /** Returns FollowCamera subobject **/
    FORCEINLINE class UCameraComponent *GetFollowCamera() const { return FollowCamera; }
};

更新 MyTPCharacter.cpp:

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
 
#include "MyTPCharacter.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"

#include "Engine/GameEngine.h"
#include "MyTest.h"

//
// AMyTPCharacter

AMyTPCharacter::AMyTPCharacter()
{

    // Set size for collision capsule
    GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

    // set our turn rates for input
    BaseTurnRate = 45.f;
    BaseLookUpRate = 45.f;

    // Don't rotate when the controller rotates. Let that just affect the camera.
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;
    bUseControllerRotationRoll = false;

    // Configure character movement
    GetCharacterMovement()->bOrientRotationToMovement = true;            // Character moves in the direction of input...
    GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
    GetCharacterMovement()->JumpZVelocity = 600.f;
    GetCharacterMovement()->AirControl = 0.2f;

    // Create a camera boom (pulls in towards the player if there is a collision)
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(RootComponent);
    CameraBoom->TargetArmLength = 300.0f;       // The camera follows at this distance behind the character
    CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

    // Create a follow camera
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    FollowCamera->bUsePawnControlRotation = false;                              // Camera does not rotate relative to arm

    // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
    // are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)

    DelegateNoParam.BindUObject(this, &AMyTPCharacter::FuncNoParam); ///< this 只能是继承自 UObject 的类, 绑定继承 UObject 类的对象函数
    Delegate1Param.BindUObject(this, &AMyTPCharacter::Func1Param);
    Delegate5Param.BindUObject(this, &AMyTPCharacter::Func5Param);
    DelegateRV.BindUObject(this, &AMyTPCharacter::FuncRV);

    /// @note Lambda 表达式
    auto LambdaFunc = [&](FString str)
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
        }
    };
    DelegateLambda.BindLambda(LambdaFunc);        ///< 绑定 lambda 表达式
    DelegateRaw.BindRaw(&MyT, &MyTest::TestFunc); ///< 绑定到一个原始的 C++ 类函数绑定到一个原始的 C++ 指针全局函数代理上。原始指针不使用任何引用,所以如果从代理的底层删除了该对象,那么调用它可能是不安全的。因此,当调用 Execute() 时一定要小心!

    MyT_SPtr = MakeShareable(new MyTest());
    DelegateSP.BindSP(MyT_SPtr.ToSharedRef(), &MyTest::TestFunc); ///< 绑定一个基于共享指针的成员函数代理。共享指针代理保持到您的对象的弱引用。您可以使用 ExecuteIfBound() 来调用它们。
    DelegateStatic.BindStatic(Static_Func);                       ///< 绑定全局静态函数
    DelegateUFunction.BindUFunction(this, "TestUFunc");           ///< 绑定 this 指向对象类蓝图函数
}

//
// Input

void AMyTPCharacter::SetupPlayerInputComponent(class UInputComponent *PlayerInputComponent)
{
    // Set up gameplay key bindings
    check(PlayerInputComponent);

    PlayerInputComponent->BindAction("RunEvent", IE_Pressed, this, &AMyTPCharacter::OnRunEvent);

    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

    PlayerInputComponent->BindAxis("MoveForward", this, &AMyTPCharacter::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &AMyTPCharacter::MoveRight);

    // We have 2 versions of the rotation bindings to handle different kinds of devices differently
    // "turn" handles devices that provide an absolute delta, such as a mouse.
    // "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
    PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
    PlayerInputComponent->BindAxis("TurnRate", this, &AMyTPCharacter::TurnAtRate);
    PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    PlayerInputComponent->BindAxis("LookUpRate", this, &AMyTPCharacter::LookUpAtRate);

    // handle touch devices
    PlayerInputComponent->BindTouch(IE_Pressed, this, &AMyTPCharacter::TouchStarted);
    PlayerInputComponent->BindTouch(IE_Released, this, &AMyTPCharacter::TouchStopped);

    // VR headset functionality
    PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AMyTPCharacter::OnResetVR);
}

void AMyTPCharacter::PrintScreenStr(FString Str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Red, Str);
    }
}

void AMyTPCharacter::OnRunEvent()
{
    DelegateNoParam.ExecuteIfBound();
    Delegate1Param.ExecuteIfBound("Delegate1Param!");
    Delegate5Param.ExecuteIfBound("1Param!", "2Param!", "3Param!", "4Param!", "5Param!");
    FString CurRV = DelegateRV.Execute();
    PrintScreenStr(CurRV);

    DelegateLambda.ExecuteIfBound("DelegateLambda!");
    DelegateRaw.ExecuteIfBound("DelegateRaw!");
    DelegateSP.ExecuteIfBound("DelegateSP!");
    DelegateStatic.ExecuteIfBound("DelegateStatic!");
    DelegateUFunction.ExecuteIfBound("DelegateUFunction!");
}
void AMyTPCharacter::FuncNoParam()
{
    PrintScreenStr("NoParam");
}
void AMyTPCharacter::Func1Param(FString Str)
{
    PrintScreenStr(Str);
}
void AMyTPCharacter::Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5)
{
    PrintScreenStr(Str1 + " " + Str2 + " " + Str3 + " " + Str4 + " " + Str5);
}
FString AMyTPCharacter::FuncRV()
{
    return FString("FuncRV");
}

void AMyTPCharacter::TestUFunc(FString str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
    }
}
/// ----------------------------------------------------------------------------

void AMyTPCharacter::OnResetVR()
{
    UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
}

void AMyTPCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
{
    Jump();
}

void AMyTPCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
{
    StopJumping();
}

void AMyTPCharacter::TurnAtRate(float Rate)
{
    // calculate delta for this frame from the rate information
    AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void AMyTPCharacter::LookUpAtRate(float Rate)
{
    // calculate delta for this frame from the rate information
    AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}

void AMyTPCharacter::MoveForward(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
        // find out which way is forward
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // get forward vector
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
}

void AMyTPCharacter::MoveRight(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
        // find out which way is right
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // get right vector
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
        // add movement in that direction
        AddMovementInput(Direction, Value);
    }
}

按 Q 键,运行输出到屏幕如下图:

image.png

3、多播代理委托

多播代理的功能和单播代理几乎一样。区别是它们对目标为弱引用,可以和结构体一起使用,可以很方便地进行拷贝,等等。 和普通代理一样,多播代理可被载入/保存,并远程触发;但是,多播代理的函数无法使用返回值。最好将它们用于方便地传递代理集合。事件 是特定类型的多播代理,同时具有对 Broadcast(), IsBound(), 和 Clear() 函数的受限访问。

多播代理在代理触发时可能会调用多个函数绑定。因此,绑定函数在语句中看起来更为像是数组。

多播代理允许您附加多个函数代理,然后通过调用多播代理的 Broadcast() 函数一次性执行所有函数代理。多播代理的签名不能使用返回值。

任何时候在多播代理上调用 Broadcast() 函数都是安全的,即时它没有绑定任何函数也可以。唯一需要注意的时候是您使用代理来 初始化 输出变量时,这样做一般是非常不好的。

当调用 Broadcast() 函数时,绑定函数的执行顺序是不确定的。可能并不按照函数的添加顺序执行

  • 步骤1 和 2 单播代理委托,指的是只能绑定 一个函数指针 的委托,实现一对一的通知。
  • 多播代理委托,指的是能绑定 多个函数指针 的委托,实现一对多的通知。
  • 多播代理委托的定义是有 “MULTICAST” 修饰的委托

继续基于步骤 1、2 实现,在编辑器创建基于 C++ 类继承于 Actor 两个类,名字分别叫做:TestActorTestActor2

新建 TestActor.h

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

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

UCLASS()
class MYTP_API ATestActor : public AActor
{
    GENERATED_BODY()

public:
    // Sets default values for this actor's properties
    ATestActor();

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

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

    void FuncNoParam();
    void Func1Param(FString str);
};

新建 TestActor.cpp

// Fill out your copyright notice in the Description page of Project Settings.
 
#include "TestActor.h"
#include "Engine/GameEngine.h"
#include "MyTPCharacter.h"

// Sets default values
ATestActor::ATestActor()
{
    // 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;
}

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

    APlayerController *PC = GetWorld()->GetFirstPlayerController();
    if (nullptr == PC)
        return;
    ACharacter *Cha = PC->GetCharacter();
    if (nullptr == Cha)
        return;
    AMyTPCharacter *MyTPC = Cast<AMyTPCharacter>(Cha);
    if (nullptr == MyTPC)
        return;
    
    /// @note 绑定多播
    MyTPC->DelegateMulticastD_NoParam.AddUObject(this, &ATestActor::FuncNoParam);
    MyTPC->DelegateMulticastD_1Param.AddUObject(this, &ATestActor::Func1Param);
}

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

void ATestActor::FuncNoParam()
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Yellow, "TestActor FuncNoParam");
    }
}
void ATestActor::Func1Param(FString str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Yellow, "TestActor Func1Param" + str);
    }
}

新建 TestActor2.h

// Fill out your copyright notice in the Description page of Project Settings.
 
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestActor2.generated.h"

UCLASS()
class MYTP_API ATestActor2 : public AActor
{
    GENERATED_BODY()

public:
    // Sets default values for this actor's properties
    ATestActor2();

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

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

    void FuncNoParam();
    void Func1Param(FString str);
};

新建 TestActor2.cpp

// Fill out your copyright notice in the Description page of Project Settings.
 
#include "TestActor2.h"
#include "Engine/GameEngine.h"
#include "MyTPCharacter.h"

// Sets default values
ATestActor2::ATestActor2()
{
    // 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;
}

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

    APlayerController *PC = GetWorld()->GetFirstPlayerController();
    if (nullptr == PC)
        return;
    ACharacter *Cha = PC->GetCharacter();
    if (nullptr == Cha)
        return;
    AMyTPCharacter *MyTPC = Cast<AMyTPCharacter>(Cha);
    if (nullptr == MyTPC)
        return;
    
    /// @note 绑定多播
    MyTPC->DelegateMulticastD_NoParam.AddUObject(this, &ATestActor2::FuncNoParam);
    MyTPC->DelegateMulticastD_1Param.AddUObject(this, &ATestActor2::Func1Param);
}

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

void ATestActor2::FuncNoParam()
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Yellow, "TestActor2 FuncNoParam");
    }
}
void ATestActor2::Func1Param(FString str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Yellow, "TestActor2 Func1Param" + str);
    }
}

更新 MyTPCharacter.h

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
 
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyTest.h"
#include "MyTPCharacter.generated.h"

// 声明无参代理
DECLARE_DELEGATE(D_NoParam)
// 声明 1 个参数的代理
DECLARE_DELEGATE_OneParam(D_1Param, FString)
// 声明 5 个参数的代理
DECLARE_DELEGATE_FiveParams(D_5Param, FString, FString, FString, FString, FString)
// 返回值代理
DECLARE_DELEGATE_RetVal(FString, D_RV)

static void Static_Func(FString Str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, Str);
    }
}

/// @note 声明多播委托,与单播委托一样支持多参数传入,动态多播代理的名称开头须为 F,否则会编译报错
DECLARE_MULTICAST_DELEGATE(FMulticastD_NoParam);                  ///< 声明无参数多播
DECLARE_MULTICAST_DELEGATE_OneParam(FMulticastD_1Param, FString); ///< 声明一个参数多播

UCLASS(config = Game)
class AMyTPCharacter : public ACharacter
{
    GENERATED_BODY()

    /** Camera boom positioning the camera behind the character */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    class USpringArmComponent *CameraBoom;

    /** Follow camera */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    class UCameraComponent *FollowCamera;

public:
    AMyTPCharacter();

    /** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
    float BaseTurnRate;

    /** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
    float BaseLookUpRate;

    // 定义代理
    D_NoParam DelegateNoParam;
    D_1Param Delegate1Param;
    D_5Param Delegate5Param;
    D_RV DelegateRV;

    // 绑定到代理的函数
    void FuncNoParam();
    void Func1Param(FString Str);
    void Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5);
    FString FuncRV();

    void OnRunEvent();                // 绑定到 Q 键函数,执行所有代理
    void PrintScreenStr(FString Str); // 打印日志到屏幕函数

    // 定义多种绑定
    D_1Param DelegateLambda;
    D_1Param DelegateRaw;
    D_1Param DelegateSP;
    D_1Param DelegateStatic;
    D_1Param DelegateUFunction;

    UFUNCTION() // 可以声明 BlueprintCallable 等其他标记
    void TestUFunc(FString str);
    MyTest MyT;
    TSharedPtr<MyTest> MyT_SPtr;

    /// @note 定义多播
    FMulticastD_NoParam DelegateMulticastD_NoParam;
    FMulticastD_1Param DelegateMulticastD_1Param;

protected:
    /** Resets HMD orientation in VR. */
    void OnResetVR();

    /** Called for forwards/backward input */
    void MoveForward(float Value);

    /** Called for side to side input */
    void MoveRight(float Value);

    /** 
     * Called via input to turn at a given rate. 
     * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
     */
    void TurnAtRate(float Rate);

    /**
     * Called via input to turn look up/down at a given rate. 
     * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
     */
    void LookUpAtRate(float Rate);

    /** Handler for when a touch input begins. */
    void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);

    /** Handler for when a touch input stops. */
    void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);

protected:
    // APawn interface
    virtual void SetupPlayerInputComponent(class UInputComponent *PlayerInputComponent) override;
    // End of APawn interface

public:
    /** Returns CameraBoom subobject **/
    FORCEINLINE class USpringArmComponent *GetCameraBoom() const { return CameraBoom; }
    /** Returns FollowCamera subobject **/
    FORCEINLINE class UCameraComponent *GetFollowCamera() const { return FollowCamera; }
};

更新 MyTPCharacter.cpp

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
 
#include "MyTPCharacter.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"

#include "Engine/GameEngine.h"
#include "MyTest.h"

//
// AMyTPCharacter

AMyTPCharacter::AMyTPCharacter()
{

    // Set size for collision capsule
    GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

    // set our turn rates for input
    BaseTurnRate = 45.f;
    BaseLookUpRate = 45.f;

    // Don't rotate when the controller rotates. Let that just affect the camera.
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;
    bUseControllerRotationRoll = false;

    // Configure character movement
    GetCharacterMovement()->bOrientRotationToMovement = true;            // Character moves in the direction of input...
    GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
    GetCharacterMovement()->JumpZVelocity = 600.f;
    GetCharacterMovement()->AirControl = 0.2f;

    // Create a camera boom (pulls in towards the player if there is a collision)
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(RootComponent);
    CameraBoom->TargetArmLength = 300.0f;       // The camera follows at this distance behind the character
    CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

    // Create a follow camera
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    FollowCamera->bUsePawnControlRotation = false;                              // Camera does not rotate relative to arm

    // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
    // are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)

    DelegateNoParam.BindUObject(this, &AMyTPCharacter::FuncNoParam); // this 只能是继承自 UObject 的类,绑定继承 UObject 类的对象函数
    Delegate1Param.BindUObject(this, &AMyTPCharacter::Func1Param);
    Delegate5Param.BindUObject(this, &AMyTPCharacter::Func5Param);
    DelegateRV.BindUObject(this, &AMyTPCharacter::FuncRV);

    // Lambda 表达式
    auto LambdaFunc = [&](FString str)
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
        }
    };
    DelegateLambda.BindLambda(LambdaFunc);        // 绑定 lambda 表达式
    DelegateRaw.BindRaw(&MyT, &MyTest::TestFunc); // 绑定到一个原始的 C++ 类函数绑定到一个原始的 C++ 指针全局函数代理上。原始指针不使用任何引用,所以如果从代理的底层删除了该对象,那么调用它可能是不安全的。因此,当调用 Execute() 时一定要小心!

    MyT_SPtr = MakeShareable(new MyTest());
    DelegateSP.BindSP(MyT_SPtr.ToSharedRef(), &MyTest::TestFunc); // 绑定一个基于共享指针的成员函数代理。共享指针代理保持到您的对象的弱引用。您可以使用 ExecuteIfBound() 来调用它们。
    DelegateStatic.BindStatic(Static_Func);                       // 绑定全局静态函数
    DelegateUFunction.BindUFunction(this, "TestUFunc");           // 绑定 this 指向对象类蓝图函数
}

//
// Input

void AMyTPCharacter::SetupPlayerInputComponent(class UInputComponent *PlayerInputComponent)
{
    // Set up gameplay key bindings
    check(PlayerInputComponent);

    PlayerInputComponent->BindAction("RunEvent", IE_Pressed, this, &AMyTPCharacter::OnRunEvent);

    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

    PlayerInputComponent->BindAxis("MoveForward", this, &AMyTPCharacter::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &AMyTPCharacter::MoveRight);

    // We have 2 versions of the rotation bindings to handle different kinds of devices differently
    // "turn" handles devices that provide an absolute delta, such as a mouse.
    // "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
    PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
    PlayerInputComponent->BindAxis("TurnRate", this, &AMyTPCharacter::TurnAtRate);
    PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    PlayerInputComponent->BindAxis("LookUpRate", this, &AMyTPCharacter::LookUpAtRate);

    // handle touch devices
    PlayerInputComponent->BindTouch(IE_Pressed, this, &AMyTPCharacter::TouchStarted);
    PlayerInputComponent->BindTouch(IE_Released, this, &AMyTPCharacter::TouchStopped);

    // VR headset functionality
    PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AMyTPCharacter::OnResetVR);
}

void AMyTPCharacter::PrintScreenStr(FString Str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Red, Str);
    }
}

void AMyTPCharacter::OnRunEvent()
{
    // 执行单播
    DelegateNoParam.ExecuteIfBound();
    Delegate1Param.ExecuteIfBound("Delegate1Param!");
    Delegate5Param.ExecuteIfBound("1Param!", "2Param!", "3Param!", "4Param!", "5Param!");
    FString CurRV = DelegateRV.Execute();
    PrintScreenStr(CurRV);

    DelegateLambda.ExecuteIfBound("DelegateLambda!");
    DelegateRaw.ExecuteIfBound("DelegateRaw!");
    DelegateSP.ExecuteIfBound("DelegateSP!");
    DelegateStatic.ExecuteIfBound("DelegateStatic!");
    DelegateUFunction.ExecuteIfBound("DelegateUFunction!");

    /// @note 执行多播
    DelegateMulticastD_NoParam.Broadcast();
    DelegateMulticastD_1Param.Broadcast(FString("@1Param!!!"));
}
void AMyTPCharacter::FuncNoParam()
{
    PrintScreenStr("NoParam");
}
void AMyTPCharacter::Func1Param(FString Str)
{
    PrintScreenStr(Str);
}
void AMyTPCharacter::Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5)
{
    PrintScreenStr(Str1 + " " + Str2 + " " + Str3 + " " + Str4 + " " + Str5);
}
FString AMyTPCharacter::FuncRV()
{
    return FString("FuncRV");
}

void AMyTPCharacter::TestUFunc(FString str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
    }
}

void AMyTPCharacter::OnResetVR()
{
    UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
}

void AMyTPCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
{
    Jump();
}

void AMyTPCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
{
    StopJumping();
}

void AMyTPCharacter::TurnAtRate(float Rate)
{
    // calculate delta for this frame from the rate information
    AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void AMyTPCharacter::LookUpAtRate(float Rate)
{
    // calculate delta for this frame from the rate information
    AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}

void AMyTPCharacter::MoveForward(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
        // find out which way is forward
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // get forward vector
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
}

void AMyTPCharacter::MoveRight(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
        // find out which way is right
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // get right vector
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
        // add movement in that direction
        AddMovementInput(Direction, Value);
    }
}

编辑器分别创建继承于 TestActorTestActor2 的蓝图类 TestATestA2,并且把它们摆放到测试场景关卡生成;

image.png

启动游戏后,摁 Q 键,运行效果如下:

image.png

4、动态多播代理委托(与蓝图混合使用)

动态委托可以序列化,它们的函数可以通过名称找到,而且它们比常规委托。动态代理的使用方法和普通代理相似,不过动态多播代理可以暴露给蓝图绑定,方便在蓝图做处理,特别是 C++ 和 UMG 数据通知交互。

还是基于步骤 1/2/3 来实现,代码过程如下:

更新 MyTPCharacter.h

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
 
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyTest.h"
#include "MyTPCharacter.generated.h"

//声明无参代理
DECLARE_DELEGATE(D_NoParam)
//声明1个参数的代理
DECLARE_DELEGATE_OneParam(D_1Param, FString)
//声明5个参数的代理
DECLARE_DELEGATE_FiveParams(D_5Param, FString, FString, FString, FString, FString)
//返回值代理
DECLARE_DELEGATE_RetVal(FString, D_RV)

static void Static_Func(FString Str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, Str);
    }
}

/// @note 声明多播委托,与单播委托一样支持多参数传入,动态多播代理的名称开头须为 F,否则会编译报错
DECLARE_MULTICAST_DELEGATE(FMulticastD_NoParam);                  ///< 声明无参数多播
DECLARE_MULTICAST_DELEGATE_OneParam(FMulticastD_1Param, FString); ///< 声明一个参数多播
/// @note 声明动态多播
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDynamicMulticastD_1Param, FString, Str); ///< 声明一个参数动态多播,动态多播代理的名称开头须为 F,否则会编译报错

UCLASS(config = Game)
class AMyTPCharacter : public ACharacter
{
    GENERATED_BODY()

    /** Camera boom positioning the camera behind the character */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    class USpringArmComponent *CameraBoom;

    /** Follow camera */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
    class UCameraComponent *FollowCamera;

public:
    AMyTPCharacter();

    /** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
    float BaseTurnRate;

    /** Base look up/down rate, in deg/sec. Other scaling may affect final rate. */
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera)
    float BaseLookUpRate;

    // 定义代理
    D_NoParam DelegateNoParam;
    D_1Param Delegate1Param;
    D_5Param Delegate5Param;
    D_RV DelegateRV;

    // 绑定到代理的函数
    void FuncNoParam();
    void Func1Param(FString Str);
    void Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5);
    FString FuncRV();

    void OnRunEvent();                //绑定到 Q 键函数,执行所有代理
    void PrintScreenStr(FString Str); //打印日志到屏幕函数

    // 定义多种绑定
    D_1Param DelegateLambda;
    D_1Param DelegateRaw;
    D_1Param DelegateSP;
    D_1Param DelegateStatic;
    D_1Param DelegateUFunction;

    UFUNCTION() //可以声明 BlueprintCallable 等其他标记
    void TestUFunc(FString str);
    MyTest MyT;
    TSharedPtr<MyTest> MyT_SPtr;

    /// @note 多播
    FMulticastD_NoParam DelegateMulticastD_NoParam;
    FMulticastD_1Param DelegateMulticastD_1Param;

    UPROPERTY(BlueprintAssignable)                              //可在蓝图上绑定分配
    FDynamicMulticastD_1Param DelegateDynamicMulticastD_1Param; ///< 动态多播

protected:
    /** Resets HMD orientation in VR. */
    void OnResetVR();

    /** Called for forwards/backward input */
    void MoveForward(float Value);

    /** Called for side to side input */
    void MoveRight(float Value);

    /** 
	 * Called via input to turn at a given rate. 
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
    void TurnAtRate(float Rate);

    /**
	 * Called via input to turn look up/down at a given rate. 
	 * @param Rate	This is a normalized rate, i.e. 1.0 means 100% of desired turn rate
	 */
    void LookUpAtRate(float Rate);

    /** Handler for when a touch input begins. */
    void TouchStarted(ETouchIndex::Type FingerIndex, FVector Location);

    /** Handler for when a touch input stops. */
    void TouchStopped(ETouchIndex::Type FingerIndex, FVector Location);

protected:
    // APawn interface
    virtual void SetupPlayerInputComponent(class UInputComponent *PlayerInputComponent) override;
    // End of APawn interface

public:
    /** Returns CameraBoom subobject **/
    FORCEINLINE class USpringArmComponent *GetCameraBoom() const { return CameraBoom; }
    /** Returns FollowCamera subobject **/
    FORCEINLINE class UCameraComponent *GetFollowCamera() const { return FollowCamera; }
};

更新 MyTPCharacter.cpp

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
 
#include "MyTPCharacter.h"
#include "HeadMountedDisplayFunctionLibrary.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "GameFramework/SpringArmComponent.h"

#include "Engine/GameEngine.h"
#include "MyTest.h"

//
// AMyTPCharacter

AMyTPCharacter::AMyTPCharacter()
{

    // Set size for collision capsule
    GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);

    // set our turn rates for input
    BaseTurnRate = 45.f;
    BaseLookUpRate = 45.f;

    // Don't rotate when the controller rotates. Let that just affect the camera.
    bUseControllerRotationPitch = false;
    bUseControllerRotationYaw = false;
    bUseControllerRotationRoll = false;

    // Configure character movement
    GetCharacterMovement()->bOrientRotationToMovement = true;            // Character moves in the direction of input...
    GetCharacterMovement()->RotationRate = FRotator(0.0f, 540.0f, 0.0f); // ...at this rotation rate
    GetCharacterMovement()->JumpZVelocity = 600.f;
    GetCharacterMovement()->AirControl = 0.2f;

    // Create a camera boom (pulls in towards the player if there is a collision)
    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
    CameraBoom->SetupAttachment(RootComponent);
    CameraBoom->TargetArmLength = 300.0f;       // The camera follows at this distance behind the character
    CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller

    // Create a follow camera
    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    FollowCamera->bUsePawnControlRotation = false;                              // Camera does not rotate relative to arm

    // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character)
    // are set in the derived blueprint asset named MyCharacter (to avoid direct content references in C++)

    DelegateNoParam.BindUObject(this, &AMyTPCharacter::FuncNoParam); // this 只能是继承自 UObject 的类,绑定继承 UObject 类的对象函数
    Delegate1Param.BindUObject(this, &AMyTPCharacter::Func1Param);
    Delegate5Param.BindUObject(this, &AMyTPCharacter::Func5Param);
    DelegateRV.BindUObject(this, &AMyTPCharacter::FuncRV);

    // Lambda 表达式
    auto LambdaFunc = [&](FString str)
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
        }
    };
    DelegateLambda.BindLambda(LambdaFunc);        //绑定 lambda 表达式
    DelegateRaw.BindRaw(&MyT, &MyTest::TestFunc); //绑定到一个原始的 C++ 类函数绑定到一个原始的 C++ 指针全局函数代理上。原始指针不使用任何引用,所以如果从代理的底层删除了该对象,那么调用它可能是不安全的。因此,当调用 Execute() 时一定要小心!

    MyT_SPtr = MakeShareable(new MyTest());
    DelegateSP.BindSP(MyT_SPtr.ToSharedRef(), &MyTest::TestFunc); // 绑定一个基于共享指针的成员函数代理。共享指针代理保持到您的对象的弱引用。您可以使用 ExecuteIfBound() 来调用它们。
    DelegateStatic.BindStatic(Static_Func);                       // 绑定全局静态函数
    DelegateUFunction.BindUFunction(this, "TestUFunc");           // 绑定 this 指向对象类蓝图函数
}

//
// Input

void AMyTPCharacter::SetupPlayerInputComponent(class UInputComponent *PlayerInputComponent)
{
    // Set up gameplay key bindings
    check(PlayerInputComponent);

    PlayerInputComponent->BindAction("RunEvent", IE_Pressed, this, &AMyTPCharacter::OnRunEvent);

    PlayerInputComponent->BindAction("Jump", IE_Pressed, this, &ACharacter::Jump);
    PlayerInputComponent->BindAction("Jump", IE_Released, this, &ACharacter::StopJumping);

    PlayerInputComponent->BindAxis("MoveForward", this, &AMyTPCharacter::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &AMyTPCharacter::MoveRight);

    // We have 2 versions of the rotation bindings to handle different kinds of devices differently
    // "turn" handles devices that provide an absolute delta, such as a mouse.
    // "turnrate" is for devices that we choose to treat as a rate of change, such as an analog joystick
    PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
    PlayerInputComponent->BindAxis("TurnRate", this, &AMyTPCharacter::TurnAtRate);
    PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
    PlayerInputComponent->BindAxis("LookUpRate", this, &AMyTPCharacter::LookUpAtRate);

    // handle touch devices
    PlayerInputComponent->BindTouch(IE_Pressed, this, &AMyTPCharacter::TouchStarted);
    PlayerInputComponent->BindTouch(IE_Released, this, &AMyTPCharacter::TouchStopped);

    // VR headset functionality
    PlayerInputComponent->BindAction("ResetVR", IE_Pressed, this, &AMyTPCharacter::OnResetVR);
}

void AMyTPCharacter::PrintScreenStr(FString Str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Red, Str);
    }
}

void AMyTPCharacter::OnRunEvent()
{
    // 执行单播
    DelegateNoParam.ExecuteIfBound();
    Delegate1Param.ExecuteIfBound("Delegate1Param!");
    Delegate5Param.ExecuteIfBound("1Param!", "2Param!", "3Param!", "4Param!", "5Param!");
    FString CurRV = DelegateRV.Execute();
    PrintScreenStr(CurRV);

    DelegateLambda.ExecuteIfBound("DelegateLambda!");
    DelegateRaw.ExecuteIfBound("DelegateRaw!");
    DelegateSP.ExecuteIfBound("DelegateSP!");
    DelegateStatic.ExecuteIfBound("DelegateStatic!");
    DelegateUFunction.ExecuteIfBound("DelegateUFunction!");

    /// @note 执行多播
    DelegateMulticastD_NoParam.Broadcast();
    DelegateMulticastD_1Param.Broadcast(FString("@1Param!!!"));
    DelegateDynamicMulticastD_1Param.Broadcast(FString("@1DynamicMulticast!")); ///< 执行动态多播
}
void AMyTPCharacter::FuncNoParam()
{
    PrintScreenStr("NoParam");
}
void AMyTPCharacter::Func1Param(FString Str)
{
    PrintScreenStr(Str);
}
void AMyTPCharacter::Func5Param(FString Str1, FString Str2, FString Str3, FString Str4, FString Str5)
{
    PrintScreenStr(Str1 + " " + Str2 + " " + Str3 + " " + Str4 + " " + Str5);
}
FString AMyTPCharacter::FuncRV()
{
    return FString("FuncRV");
}

void AMyTPCharacter::TestUFunc(FString str)
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 30, FColor::Blue, str);
    }
}

void AMyTPCharacter::OnResetVR()
{
    UHeadMountedDisplayFunctionLibrary::ResetOrientationAndPosition();
}

void AMyTPCharacter::TouchStarted(ETouchIndex::Type FingerIndex, FVector Location)
{
    Jump();
}

void AMyTPCharacter::TouchStopped(ETouchIndex::Type FingerIndex, FVector Location)
{
    StopJumping();
}

void AMyTPCharacter::TurnAtRate(float Rate)
{
    // calculate delta for this frame from the rate information
    AddControllerYawInput(Rate * BaseTurnRate * GetWorld()->GetDeltaSeconds());
}

void AMyTPCharacter::LookUpAtRate(float Rate)
{
    // calculate delta for this frame from the rate information
    AddControllerPitchInput(Rate * BaseLookUpRate * GetWorld()->GetDeltaSeconds());
}

void AMyTPCharacter::MoveForward(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
        // find out which way is forward
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // get forward vector
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
}

void AMyTPCharacter::MoveRight(float Value)
{
    if ((Controller != NULL) && (Value != 0.0f))
    {
        // find out which way is right
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);

        // get right vector
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
        // add movement in that direction
        AddMovementInput(Direction, Value);
    }
}

定义多播 DelegateDynamicMulticastD_1Param 在蓝图 TestATestA1ThirdPersonCharacter 蓝图上 绑定 ,如下图

image.png

image.png

image.png

运行游戏,摁下 Q 键,运行效果如下:

image.png

5、代理委托解绑

  • UnBind():解除绑定该代理。
  • Remove():将函数从这个多播代理的调用列表中移除(性能为 O(N))。请注意代理的顺序可能不会被保留!
  • RemoveAll():将所有函数从与特定 UserObject 绑定的多播代理的调用列表中移除。请注意代理的顺序可能不会被保留!RemoveAll() 将会移除所有与提供的指针绑定的注册代理! 不与对象指针绑定的原始代理将不会被此函数移除!

注意:代理委托绑定时候,绑定函数返回一个代理委托,可以在绑定注册代理委托对象类的 EndPlayDestroyed 等结束函数解绑该代理委托。

完毕!解释都在代码里看到,代理委托有点像软件设计模式中的 “观察者模式(Observer)” 的具体运用,类与类可以降低低耦合!