事件分发器/委托快速入门 ue5

事件分发器/委托快速入门 ue5

1.首先创建一个第三人称的项目

2.创建Boss Actor和OnBossDied的委托

创建一个BossActor的类,然后加入一个声明Delegate
DECLARE_DELEGATE(FOnBossDiedDelegate);



学习delegate

在类的下面写一个函数表示处理bossdiedevent这个的函数。还有一个boxcomp这个box 的组件,并且还有虚函数NotifyActorBeginOverlap

  1. 这里学习一下这个代理的有关知识也就是叫做delegates。
  2. 代理可以call很多在cpp对象的方法。一个delegate可以绑定很多的任意的方法。即使不知道是什么类型的,但是就是可以
  3. 并且delegates是安全的copy,我们同样可以用值来传递这个delegates,但是不建议这样做,因为这个处理是在堆存的,如果可以那么就用reference去传递。
  4. 有三种delegates,single,multicast,dynamic。
Declaring Delegates

to finish this task,we use one of the macros below. you can select from these macro based on the signature of the funtion that u intend to bind .
eg. functions returning a value, declared as const …

here are the table :
function() --------- DECLARE_DELEGATE(DelegateName)
function(p1) ------- DECLARE_DELEGATE_OneParam(DelegateName,p1type)

returnval function - DECLARE_DELEGATE_RetVal(RetValType,DelegateName)

定义大致是这样,就是写一个DECLARE_DELEGATE然后后面加按照你线稿绑定的function写,参数或者是return的参数的type和名字。
我们也可以在.h中定义一下类似UFUNCTION,我们使用的是UDELEGATE的macro。

eg:

    UDELEGATE(BlueprintAuthorityOnly)
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FInstigatedAnyDamageSignature, float, Damage, const UDamageType*, DamageType, AActor*, DamagedActor, AActor*, DamageCauser);

如果我们直接declare出来的是单播的就是我们前面的single,如果想要定义multicast或者是dynamic的话用下面的:(或者是复合的)
DECLARE_MULTICAST_DELEGATE,DECLARE_DYNAMIC_DELEGATE , DECLARE_DYNAMIC_MULTICAST_DELEGATE.
我们的delegate signature 可以存在在global scope, within a namespace ,or even within a declaration.这些declarations 可能不能存在在function的内部

Binding Delegates

delegate sys understands certain types of objects.

additional features are enabled when using these objects. if i bind a delegate to a UObject or pointer class, the sys can keep a weak reference to
the object, so that if the object destroyed out from underneath the delegate, you can handle these cases by using IsBound or ExecuteIfBound functions.

here are the special binding 语法:
Bind 绑定一个存在的delegate object
BindStatic 绑定一个raw的cpp pointer global function delegate.
BindRaw 绑定一个raw cpp pointer , 一旦pointer不适用任何的refer,call execute or executeifBound 在删除这个object的是unsafe
bindObject 绑定UObject,保持弱的refer,可以使用executerifbound来call
UnBind 解除绑定

Executing Delegates

当我们call这个delegate的Execute() function的时候,绑定function的delegate将会运行。 你必须检查这个delegate是否在运行前绑定。这会让code更加safe。
运行没有绑定的delegate的时候,会scribble over memory in some distances.我们可以call IsBound() to check if 这个delegate是否safe去执行。
对于有些没有return的value,我们可以来调用ExecuteIfBound(),对于一些output的属性可能会没有初始化。

Execute : 执行一个delegate没有checking its bindins
ExecuteIfBound : 检查这个delegate是否bound,如果是那么就Execute
IsBound : 检查这个delegate是否bound,通常在编码之前会包含一个Execute call。

Example Usage

假设你有一个类class里面有个方法writetolog
为了call这个function我们需要创建一个delegate for this function’s signature

  1. declare
    DECLARE_DELEGATE_OneParam(FStringDelegate, FString);
    创建了一个叫FStringDelegate 的代理如何在类中使用这个代理?
class FMyClass
    {
        FStringDelegate WriteToLogDelegate;
    };

这个代码让你可以在任何的类中都可以拿着一个指针来使用这个方法。这个类只知道这个delegate的是function的signature
然后为了assign这个delegate,简单创建一个delegate类的实例,通过拥有方法的这个类传递。传递这个

eg:下面是创建了一个FLogWriter的实例,然后创建了一个代理给那个对象实例的WriteToLog方法。

    TSharedRef<FLogWriter> LogWriter(new FLogWriter());
    WriteToLogDelegate.BindSP(LogWriter, &FLogWriter::WriteToLog);

我理解是创建了一个实例的flogwriter,然后和delegate进行绑定。

接下来就是运行这个delegate:WriteToLogDelegate.Execute(TEXT("Delegates are great!"));
WriteToLogDelegate.ExecuteIfBound(TEXT("Only executes if a function was bound!"));

总结一下

我们的代理有几个步骤,一个是定义这个代理,然后在类中要实例化一个delegate,然后bind,然后execute这个代理中的方法。回到我们的正常的实现



在声明之后,在类中实例化他。下面的代码中,hadle是函数,虚函数是继承的需要实现的一个函数,还有就是我们实例化的一个delegate,OnBossDied

protected:
        UFUNCTION()
        void HandleBossDiedEvent();
        UPROPERTY(EditInstanceOnly, BlueprintReadWrite)
        class UBoxComponent* BoxComp;
        virtual void NotifyActorBeginOverlap(AActor* OtherActor);

    public:
        FOnBossDiedDelegate OnBossDied;

然后在cpp中我们绑定一个简单的box组件设置基础的星系。然后在handle函数中执行这个delegate。如果我们的notify这个函数发生那么调用这个handler

   ABossActor::ABossActor()
    {
        BoxComp = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxComp"));
        BoxComp->SetBoxExtent(FVector(128, 128, 64));
        BoxComp->SetVisibility(true);
    }

    void ABossActor::HandleBossDiedEvent()
    {
        OnBossDied.ExecuteIfBound();
    }

    void ABossActor::NotifyActorBeginOverlap(AActor* OtherActor)
    {
        HandleBossDiedEvent();
    }

含义便是我们已经实现好的notify中,当触碰到的时候,出发这个函数,函数调用查看是否绑定,如果绑定那么执行绑定的function。
但是有个很明显的问题,我们现在没有绑定,那么我们在执行的时候,他执行哪个函数呢?所以应该执行不了。

3.创建交互式门

创建door的actor,然后映入timeline的component

实现下面的类定义:

  // 用于保留曲线资产的变量
        UPROPERTY(EditInstanceOnly)
        UCurveFloat* DoorTimelineFloatCurve;

    protected:

        void BossDiedEventFunction();
        UPROPERTY(EditInstanceOnly,BlueprintReadWrite)
        class ABossActor* BossActorReference;

        //表示门的网格体组件
        UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
        UStaticMeshComponent* DoorFrame;
        UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
        UStaticMeshComponent* Door;

        //用于对门网格体进行动画处理的时间轴组件
        UPROPERTY(VisibleAnywhere, BlueprintReadWrite)
        UTimelineComponent* DoorTimelineComp;

        //用于处理我们的更新轨道事件的浮点轨道签名
        FOnTimelineFloat UpdateFunctionFloat;

        //用于使用时间轴图表更新门相对位置的函数
        UFUNCTION()
        void UpdateTimelineComp(float Output);

这段代码中不同的就是事件轴的组件没有用过,并且我们的timeline自带的signature。
在dooractor中我们把上一个的bossactor include进来。

代码如下:

    ADoorActor::ADoorActor()
    {
        //创建我们的默认组件
        DoorFrame = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorFrameMesh"));
        Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DoorMesh"));
        DoorTimelineComp = CreateDefaultSubobject<UTimelineComponent>(TEXT("DoorTimelineComp"));

        //设置绑定
        DoorFrame->SetupAttachment(RootComponent);
        Door->AttachToComponent(DoorFrame, FAttachmentTransformRules::KeepRelativeTransform);
        Door->SetRelativeLocation(FVector(0, 35, 0));
    }

    // 在游戏开始或重生(Spawn)时调用
    void ADoorActor::BeginPlay()
    {
        Super::BeginPlay();

        //将浮点轨道绑定到UpdateTimelineComp函数的输出
        UpdateFunctionFloat.BindDynamic(this, &ADoorActor::UpdateTimelineComp);

        //如果有浮点曲线,将其图表绑定到我们的更新函数
        if (DoorTimelineFloatCurve)
        {
            DoorTimelineComp->AddInterpFloat(DoorTimelineFloatCurve, UpdateFunctionFloat);
        }

        if (BossActorReference)
        {
        BossActorReference->OnBossDied.BindUObject(this, &ADoorActor::BossDiedEventFunction);
        }
    }

    void ADoorActor::BossDiedEventFunction()
    {
        DoorTimelineComp->Play();
    }

    void ADoorActor::UpdateTimelineComp(float Output)
    {
        // 基于时间轴曲线的输出,创建并设置门的新相对位置
        FRotator DoorNewRotation = FRotator(0.0f, Output, 0.f);
        Door->SetRelativeRotation(DoorNewRotation);
    }

所以我们看到了,这里的bind在beginplay这里,我们在BindDynamic里面第一个参数是object,第二个是funcname。
所以绑定的是将这个signature和当前的objectdoor,updatetimelinecomp绑定。
如果我们的bossactor的reference存在那么使用的是之前的OnBossDied来绑定给当前的door和bossdiedeventfunction,是这个类下创建的方法,调用的时候是play()
也即是我们的notify的时候,如果我们接触之后,我们将会调用的就是这函数开门。

好的,现在我们大致了解了这个过程,现在我们来实现一下出发事件,走到一个地方然后出发一个转换视角的功能。

转化视角

回顾之前,创建的是一个bossactor这个类,这个东西就是出发的地方,那么我们可以起一个名字叫ChangeView的actor.这个主要实现的内容是
能够做一个触发器,当我们的角色走到这里的时候,有一个delegate提供给其他的函数进行绑定,如果这代理被touch,那么就会检查一下是否bound然后执行
被绑定的函数。

void AChangeView::NotifyActorBeginOverlap(AActor* OthrerActor)
{
	HandleTouchEvent();
}
void AChangeView::HandleTouchEvent()
{
	OnTouch.ExecuteIfBound();
}

表示,当我们接触之后就执行这个函数,这个函数中表示的是执行和这个delegate绑定的函数。

个人定制actor实践

设置转化camera的功能

这个actor他可以检测绑定一个camera的actor,这个actor露出去可以进行设置,之后在场景中拖出这个camera即可。
那么我们进行绑定的时候,就是需要绑定object的名字和这个函数的名字,当然这个delegate还可以在其他的地方进行使用,这里直接使用在这个类里面。

我总结一下,我们的这个切换的时候,其实不需要delegate,我们只需要在同一个class里面在接触到方块和分开方块的时候,进行一个切换操作即可。
但是我们也可以设置一个delegate,这样的话,我们可以绑定多个的方法,再一次execute的时候同时执行很多类的很多函数。

这里的处理,直接先将他和我本身转化的函数结合起来。
遇到了很多的bug,首先是StaticCamerasCharacter无法使用。其次是有关绑定的东西,好像无法绑定,我怀疑是否和参数有关系
已经绑定自己的这个东西如何去做。
第一个就是如果我们想要绑定的是带参数的,那么这个delegate就需要带参数。
解决了一个个问题,例如绑定的相关东西,以及修改参数之后在运行的时候需要考虑传递参数的东西。算是把传递一个参数的东西给练习了一下吧。
最后可以达到预想的效果。
到达和离开使用两个提供的函数:


void AViewManager::HandleTouchEvent(AActor* OtherActor)
{
	OnTouch.ExecuteIfBound(OtherActor);
}

void AViewManager::NotifyActorBeginOverlap(AActor* OtherActor)
{
	HandleTouchEvent(OtherActor);
}

void AViewManager::HandleOffTouchEvent(AActor* OtherActor)
{
	OffTouch.ExecuteIfBound(OtherActor);
}

void AViewManager::NotifyActorEndOverlap(AActor* OtherActor)
{
	HandleOffTouchEvent(OtherActor);
}


void AViewManager::UpdateViewChange(AActor* OtherActor)
{
	AEventPracCharacter* PlayerCharacher = Cast<AEventPracCharacter>(OtherActor);
	if (PlayerCharacher) {
		if (APlayerController* PlayerCharacterController = Cast<APlayerController>(PlayerCharacher->GetController())) {

			PlayerCharacterController->SetViewTargetWithBlend(CameraOne, CameraBlendTime);
		}
	}
}

void AViewManager::UpdateViewChangeBack(AActor* OtherActor)
{
	AEventPracCharacter* PlayerCharacher = Cast<AEventPracCharacter>(OtherActor);
	if (PlayerCharacher) {
		if (APlayerController* PlayerCharacterController = Cast<APlayerController>(PlayerCharacher->GetController())) {

			PlayerCharacterController->SetViewTargetWithBlend(PlayerCharacterController->GetPawn(), CameraBlendTime);
		}
	}
}

在beginplay中进行初始化,绑定。

    this->OnTouch.BindUObject(this, &AViewManager::UpdateViewChange);
	this->OffTouch.BindUObject(this, &AViewManager::UpdateViewChangeBack);
}

在.h中对代理的定义和实例化:
(我们有两个状态所以需要两个delegate,由于我们使用的是单播,之后也可以改成多播的形式,需要调整这个绑定和执行的函数细节不同)
然后还有就是可以使用参数来判断是哪一种,直接代理两个当然也是可以的。(比较合乎逻辑)。

DECLARE_DELEGATE_OneParam(FOnTouchDelegate, AActor*);
	FOnTouchDelegate OnTouch;
	FOnTouchDelegate OffTouch;

最后说:直接在NotifyActorEndOverlap和NotifyActorBeginOverlap中进行修改也可以,不适用delegate。这里是为了学习delegate。
修改相机的含义是找到这个character类型的东西查看是否有(也就是第三人称的角色是否撞到了这个方块,如果撞到了,那么就会将参数传进来)
然后根据找到的这个角色找到他的controller,然后设置viewtarget,实现一个camera的转化,这个camera在属性中定义好了,所以在引擎中找到一个合适的
拖进去,details中选择,那么这个触发器就会直接跳转到那个camera。

再做一个功能是瞬间移动,我们使用之前的那个actor

这个移动需要的还是瞬间移动。

本功能使用的是找到pawn的位置 APawn* myPawn = Cast(UGameplayStatics::GetPlayerPawn(GetWorld(), 0));
然后设置相对位置,然后使用set:myPawn->SetActorLocation(NextLocation,false);设置新的位置,然后设置false没有移动的过程,效果就是瞬间移动

做一个按下变化(以速度为例子)

这个的做法大概是需要每一帧来进行一个判断是否有按下某个键,如果按下了,那么需要修改我们的状态直到我们松开的时候修改一次状态,
也就是我们需要维护一个bool来进行这个东西。
那么首先是每秒都进行判断这个属性状态,然后是这个状态的更新函数。设置两个状态一个是按下的状态一个是松开的状态这两个状态函数中
写的是设置状态为true或false,然后设置toggle即可。最后在tick中每秒检测这个状态是什么,然后就行。

你可能感兴趣的:(ue5,c++)