目录
1. 优化执行效率
上一节虽然实现了UI的实时更新,但绑定函数会每帧刷新进而造成资源浪费,毕竟血量变化的速度和一帧比起来还是慢太多。类似于CPU控制的IO操作会导致CPU持续忙等(一直检查状态但没有实际作用),一种解决办法就是使用中断控制来解放CPU。类似的,在设计模式中有一种订阅-发布(观察者)模式就很适合这样的场景:UI组件不再自行每帧查询血量,而是让血量自己在发生变化的同时通知UI进行刷新。
在UE中可使用多播委托自定义事件来实现,它的作用主要是只需调用一次Broadcast函数就可使所有绑定的对象触发相应功能。在上一节转到OnComponentBeginOverlap的定义时,我们已经看到事件在UE中的代码结构,这里我们将使用类似的方法来实现一个自定义事件。SurAttributeComponent.h和.cpp的完整代码如下。我在写这部分代码的时候VS反应很慢,OnHealthChanged甚至找不到Broadcast方法,如果你遇到同样的情况可以复制粘贴直接编译。
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SurAttributeComponent.generated.h"
// 发起者,控件拥有者,改变后的血量,变化值
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnHealthChanged, AActor*, InstigatorActor, USurAttributeComponent*, OwningComp, float, NewHealth, float, Delta);
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SURKEAUE_API USurAttributeComponent : public UActorComponent
{
GENERATED_BODY()
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Attributes")
float Health;
public:
USurAttributeComponent();
UPROPERTY(BlueprintAssignable)
FOnHealthChanged OnHealthChanged;
UFUNCTION(BlueprintCallable, Category="Attributes")
bool ApplyHealthChange(float Delta);
};
#include "SurAttributeComponent.h"
USurAttributeComponent::USurAttributeComponent()
{
Health = 100;
}
bool USurAttributeComponent::ApplyHealthChange(float Delta)
{
Health += Delta;
OnHealthChanged.Broadcast(nullptr, this, Health, Delta);
return true;
}
回到UE,在Player的AttributeComp中添加刚刚编写的OnHealthChanged事件,利用PrintString节点快速验证效果,我在这里把颜色换成了紫色,以便同之前的打印结果区分。


从测试结果来看,这个事件的效果完全满足显示血量的要求,但效率上毫无疑问地优于之前的实现方法。现在只需要把这个事件分配给相应的UI控件PlayerHealth_Widget即可,刚好其中的血条(进度条)还没有绑定数据。
我们直接在PlayerHealth_Widget的事件图表中编写蓝图程序,在“事件构造”一开始就将将玩家Pawn的AttributeComp绑定到OnHealthChanged事件上。在每次事件触发时计算当前血量与玩家默认血量的百分比,并设置给进度条。此外,我还顺便设置了文本块显示的内容,并在设计器中取消了上一节实现的绑定函数。如果在蓝图中找不到文本框变量,记得在设计器中把相应控件前的Is Variable打开。蓝图如下所示,完整蓝图代码在文末:

2. 简易脉冲动画
这部分通过在UI中添加血量减少时的脉冲动画,来了解UMG中动画的使用。
在左下角的“动画”中点击“添加动画”,命名为PulseHealthAnim,点击动画,并选择旁边的“时间轴”。选择显示血量数字的文本块,在细节中下滑找到“渲染变换”,将缩放添加为关键帧。

将时间轴拖拽到0.75,添加关键帧;在0.25处,将缩放的XY设置为0.5再次添加关键帧。此时拖动时间轴(或者按下空格键),就可预览到这个简易的脉冲动画。切换切换到蓝图中,添加播放动画的节点:

运行测试,发现动画正常播放了,但初始状态文本的缩放变成了0.5。

这是因为在刚才设置关键帧的时候更改了文本缩放的默认值,此时需要回到设计器中,点击黄色返回键将文本的缩放变为默认值:

类似的,在文本块的同一轨道中添加三个颜色的关键帧分别为白-红-白,并记得重置默认值,最终得到了如下的血量效果:

3. 完整代码
PlayerHealth_Widget蓝图
Begin Object Class=/Script/BlueprintGraph.K2Node_Event Name="K2Node_Event_1"
EventReference=(MemberParent=Class'"/Script/UMG.UserWidget"',MemberName="Construct")
bOverrideFunction=True
NodePosX=608
NodePosY=192
bCommentBubblePinned=True
NodeGuid=5C5C2C384FF042097E221CADF9B06BBD
CustomProperties Pin (PinId=FFBD93B8414DC401A295798563A0A8C2,PinName="OutputDelegate",Direction="EGPD_Output",PinType.PinCategory="delegate",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(MemberParent=Class'"/Script/UMG.UserWidget"',MemberName="Construct"),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=F7A4FD1A4CA5E769D43CB88C4B6C3851,PinName="then",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,LinkedTo=(K2Node_DynamicCast_0 A66FCF6B4C604F9D38F11EA4AA517441,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
End Object
Begin Object Class=/Script/BlueprintGraph.K2Node_CallFunction Name="K2Node_CallFunction_0"
bIsPureFunc=True
FunctionReference=(MemberParent=Class'"/Script/Engine.GameplayStatics"',MemberName="GetPlayerPawn")
NodePosX=560
NodePosY=272
NodeGuid=DC8EF067484F0D7D6C6926AC71A41156
CustomProperties Pin (PinId=413C6C0347AF340E6EA6379535D587B9,PinName="self",PinFriendlyName=NSLOCTEXT("K2Node", "Target", "Target"),PinToolTip="目标\nGameplay静态 对象引用",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.GameplayStatics"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,DefaultObject="/Script/Engine.Default__GameplayStatics",PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=770BEBF04E7B193464D49889283D49A3,PinName="WorldContextObject",PinToolTip="World Context Object\n{类别}",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=True,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PersistentGuid=00000000000000000000000000000000,bHidden=True,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=46345CB948D781358B222994D4CEAC23,PinName="PlayerIndex",PinToolTip="Player Index\n整数",PinType.PinCategory="int",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,DefaultValue="0",AutogeneratedDefaultValue="0",PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=2DC527BA4C556E33D522FB9D8BC900A4,PinName="ReturnValue",PinToolTip="Return Value\nPawn 对象引用\n\n返回指定玩家索引处的玩家pawn",Direction="EGPD_Output",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/Engine.Pawn"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,LinkedTo=(K2Node_DynamicCast_0 34973D504ADDA4109277CF8DEFDD55CF,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
End Object
Begin Object Class=/Script/BlueprintGraph.K2Node_DynamicCast Name="K2Node_DynamicCast_0"
TargetType=Class'"/Script/SurkeaUE.SurCharacter"'
NodePosX=848
NodePosY=192
NodeGuid=35014CBC4EA6220FD4E9F3B5492EC26A
CustomProperties Pin (PinId=A66FCF6B4C604F9D38F11EA4AA517441,PinName="execute",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,LinkedTo=(K2Node_Event_1 F7A4FD1A4CA5E769D43CB88C4B6C3851,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=8DCD68144E55A779A556AEA4BA0D005D,PinName="then",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,LinkedTo=(K2Node_AssignDelegate_0 F27DED894F115C8724352180EB1738F4,),PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=B5F1A5824461D7D3271F918AFE65A58E,PinName="CastFailed",Direction="EGPD_Output",PinType.PinCategory="exec",PinType.PinSubCategory="",PinType.PinSubCategoryObject=None,PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference=False,PinType.bIsConst=False,PinType.bIsWeakPointer=False,PinType.bIsUObjectWrapper=False,PersistentGuid=00000000000000000000000000000000,bHidden=False,bNotConnectable=False,bDefaultValueIsReadOnly=False,bDefaultValueIsIgnored=False,bAdvancedView=False,bOrphanedPin=False,)
CustomProperties Pin (PinId=34973D504ADDA4109277CF8DEFDD55CF,PinName="Object",PinType.PinCategory="object",PinType.PinSubCategory="",PinType.PinSubCategoryObject=Class'"/Script/CoreUObject.Object"',PinType.PinSubCategoryMemberReference=(),PinType.PinValueType=(),PinType.ContainerType=None,PinType.bIsReference