斯坦福UE4 + C++课学习记录 13:UMG-血量条

一、创建血量属性

  1. Unreal Motion Graphics (UMG)是 UE中用于创建用户界面 (UI) 的工具。它可以实现如下复杂功能:
    (1)动画:UMG 支持为控件添加动画。可以在 Widget Blueprint中创建动画,并在蓝图脚本中控制动画的播放。
    (2)绑定数据:可以将控件的属性绑定到变量或函数,实现动态更新 UI。比如本节中,将进度条的值绑定到角色的健康值。
    (3)事件处理:UMG 提供了丰富的事件处理功能。可以为按钮点击、鼠标悬停等事件添加处理函数,实现交互逻辑。
    (4)自定义控件:可以创建自定义的Widge类,封装复杂的 UI 逻辑,并在其他地方复用。
  2. UMGHUD,在UE4中的HUD主要是指
    (1)UE3那里继承来的Head-Up Display功能,在UE4中主要是用来Debug
    (2)向画布上添加图形图元的功能,表示文字的功能,还有简单的HitBox处理,用来检测鼠标等
    但是由于这些功能UMG都可以干,所以UE4的HUD功能也不是需要一定要掌握了
  3. UMG是对Slate进行了扩展方便在游戏中使用的框架,Slate是不依靠某个平台的UI框架,UE4的Editor和游戏中的UI都是使用Slate构建的。
    (1)继承了UObject使得Blueprint也可以方便使用
    (2)方便在UE4的Editor上确认渲染结果
    (3)简单的制作UI动画
  4. Widget 层次结构
  • UMG 的WidgetBlueprintSlate控件的结合。每个UMG Widget都有一个对应的Slate Widget。UMG 通过将高层次的蓝图控件映射到Slate控件来实现 UI。
  • UUserWidget:UMG 中所有Widget的基类。
  • SUserWidget:与UUserWidget对应的Slate控件。
  1. 事件驱动系统
  • UMG 和 Slate 使用事件驱动系统来处理用户输入事件。常见的事件包括:
    ①点击事件:如按钮的点击事件。
    ②悬停事件:鼠标悬停在控件上时触发的事件。
    ③键盘事件:处理键盘输入的事件。
  • 事件的处理依赖于DelegateBinding系统。UMG 中的事件通常是通过BlueprintC++中的Delegate绑定来处理的。
  1. 数据绑定属性系统
  • UMG 支持将控件的属性绑定到数据源。数据绑定使得 UI 能够自动更新,以反映数据的变化。常见的数据绑定机制包括:
    Property Binding:通过 UMG 的绑定功能,将控件属性绑定到类的属性或函数。
    Delegate Binding:通过 Delegate 绑定,处理属性变化时的自定义逻辑。
  1. UMG的三种控件:
  • UWidget : 所有 UMG 控件的公共基类,不提供增加子节点功能
  • UPanelWidget : 提供了增加子节点功能,可以有多个子节点
  • UContentWidget : 继承于UPanelWidget,是 UPanelWidget的一种特例,只能有一个子节点

(1)不能有子节点的控件:这个类别的控件的公共基类都是UWidget,每个 UMG 控件,都持有一个 Slate控件的智能指针
(2)可以增加子节点的控件

  • 有一个子节点的控件继承的父类依次是 UComponentWidget->UPanelWidget,父类 UComponentWidget构造函数中设置了不允许多个孩子标记位:(源码如下)
UContentWidget::UContentWidget(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    bCanHaveMultipleChildren = false;
}
  • 公共基类UPanelWidget定义了AddChild函数,可以增加对应的子节点:(源码如下)
UPanelSlot* UPanelWidget::AddChild(UWidget* Content)
{
    if ( Content == nullptr )
    {
        return nullptr;  //表示无法添加空控件
    }
    
	//检查是否可以有多个子控件
    if ( !bCanHaveMultipleChildren && GetChildrenCount() > 0 )
    {
        return nullptr;
    }

	//将 Content 从其当前父控件中移除。这一步确保控件没有其他父控件,并准备将其添加到新的 UPanelWidget 中。
    Content->RemoveFromParent();
	
	//初始化新对象的标志。
	//默认情况下,使用RF_Transactional,这意味着该对象的更改是可撤销的。
	//如果UPanelWidget有RF_Transient标志,则新对象也设置这个标志,表示它在运行时创建,并且不会保存到磁盘。
    EObjectFlags NewObjectFlags = RF_Transactional;
    if (HasAnyFlags(RF_Transient))
    {
        NewObjectFlags |= RF_Transient;
    }

	//创建新的 PanelSlot 对象
    UPanelSlot* PanelSlot = NewObject<UPanelSlot>(this, GetSlotClass(), NAME_None, NewObjectFlags);  //GetSlotClass()是UPanelSlot的类类型NAME_None表示没有指定名称,新对象的标志是之前设置的NewObjectFlags。
	
	//关联控件和PanelSlot
    PanelSlot->Content = Content;
    PanelSlot->Parent = this;

	//设置控件的 Slot
	//将Content的Slot属性设置为新创建的PanelSlot。这一步建立了控件与其布局信息之间的双向链接。
    Content->Slot = PanelSlot;

	//将PanelSlot添加到Slots数组
	//Slots数组维护所有子控件的布局信息
    Slots.Add(PanelSlot);
	
	//触发Slot添加事件。OnSlotAdded方法是一个虚函数,可以在子类中重写,以处理Slot添加后的特定逻辑。
    OnSlotAdded(PanelSlot);

	//使布局和易变性失效
	//这将强制重建控件树和重新计算布局
    InvalidateLayoutAndVolatility();

    return PanelSlot;
}
  1. UMG中的Slot
    (1)Slot是一种用于管理控件布局和对齐的机制,UMG 中的每个Widget都有一个Slot,用于确定其在父控件中的布局规则。
    (2)主要 Slot 类型及其属性:

UCanvasPanelSlot

  • 用于UCanvasPanel,提供绝对位置和大小。
    属性包括Position, Size, Anchors, Offsets等。

UHorizontalBoxSlot

  • 用于UHorizontalBox,提供水平布局
    属性包括Padding, Size, HorizontalAlignment, VerticalAlignment等。

UVerticalBoxSlot

  • 用于UVerticalBox,提供垂直布局
    属性包括Padding, Size, HorizontalAlignment, VerticalAlignment等。
    (3)SPanelSCompoundWidget可以作为父节点,控件之间的父子关系是依赖Slot实现的。父控件引用SlotSlot引用子控件并且保留子控件相对于父控件的布局信息。
  1. Slate控件类:
    *Slate中除了SWidget之外有三个基础类,其他控件都是继承者三个基类。
    SPanel: 有多个子节点
    SLeafWidget: 没有子节点
    SCompoundWidget: 可以有一个子节点

  2. UCanvasPanel控件树:
    在这里插入图片描述

  • UCanvasPanel是 UMG 中的高层次控件,底层的实现是SConstraintCanvas,它处理实际的布局和渲染逻辑,UCanvasPanel将子控件添加到 SConstraintCanvas中,并通过UCanvasPanelSlot设置布局属性。
  • UCanvasPanelSlotUCanvasPanel中每个子控件的Slot类。它存储子控件的布局信息,比如位置、大小、锚点等。每个添加到UCanvasPanel的子控件都会有一个对应的UCanvasPanelSlot
  • SConstraintCanvas使用SConstraintCanvas::FSlot来存储子控件的布局信息。SConstraintCanvas::FSlot继承自FSlot,并添加了特定于SConstraintCanvas的布局属性。
  • UCanvasPanel增加一个UCanvasPanelSlot,其SConstraintCanvas引用也相应的添加一个FSlot(SConstraintCanvas::FSlot),且UCanvasPanelSlot保存FSlot的引用。
  • 当修改UCanvasPanelSlot的属性时,通用引用也修改了SConstraintCanvas::FSlot对应的属性。
void UCanvasPanelSlot::SetOffsets(FMargin InOffset)
{
	LayoutData.Offsets = InOffset;
	if ( Slot )
	{
		Slot->Offset(InOffset);
	}
}
  1. 为角色添加属性,如果直接在角色类中定义一个Health变量,这种方式的弊端显而易见:随着项目的扩展、游戏不断更新迭代新版本,角色的属性可能达到几十上百,若是加上角色的技能、特性、天赋或其他各种角色相关内容,角色类的内容将膨胀到极难维护。
  • 在UE中解决这个问题的办法很简单,可以使用UE中的ActorComponent类,让整个类以组件的形式被SCharacter拥有。因此,我们从ActorComponent创建角色的SAttributeComponent类,用于实现角色的各种属性。

  • SAttributeComponent.h

#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SurAttributeComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SURKEAUE_API USurAttributeComponent : public UActorComponent
{
	GENERATED_BODY()

protected:

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Attributes")
	float Health;

public:
	USurAttributeComponent();

	UFUNCTION(BlueprintCallable, Category="Attributes")
	bool ApplyHealthChange(float Delta);

};
  • SAttributeComponent.h
#include "SurAttributeComponent.h"

USurAttributeComponent::USurAttributeComponent()
{
   Health = 100;

}

bool USurAttributeComponent::ApplyHealthChange(float Delta)
{
   Health += Delta;
   // 用于判断操作是否成功完成,如角色死亡、特殊机制等情况可能失败
   // 目前先返回true
   return true;
}
  1. 然后使用如下方法在SCharacter中声明组件,并在.cpp中创建相应实例:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
USurAttributeComponent* AttributeComp; 

二、应用血量更改

  1. SMagicProjectile中为球体组件绑定一个OnComponentBeginOverlap事件,旨在当魔法粒子与物体重叠时触发。比起使用阻挡来触发,使用重叠可以通过判定重叠对象来更方便的忽略友军伤害,并且让魔法粒子直接穿过友军继续在场景中计算(记得对应的Projectile碰撞设置也要进行更改)。
  2. 一个小技巧:在为创建绑定事件的函数时,我们可以在VS中利用右键“转到定义”去找到该事件默认的输入,找到参数数量最多的一个函数重载,然后复制粘贴除第一个参数(UE函数的签名)外的其他参数,去掉逗号作为绑定函数的参数。
  3. 在函数中实现减少血量的代码
//SurMagicProjectile.h

UFUNCTION()
void OnActorOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

//SurMagicProjectile.cpp

SphereComp->OnComponentBeginOverlap.AddDynamic(this, &ASurMagicProjectile::OnActorOverlap);

void ASurMagicProjectile::OnActorOverlap(...)
{
	if (OtherActor) {
		//获得AttributeComp
		USurAttributeComponent* AttributeComp = Cast<USurAttributeComponent>(OtherActor->GetComponentByClass(USurAttributeComponent::StaticClass()));
		// 再次判空,可能碰到的是墙壁、箱子等没有血量的物体
		if (AttributeComp) {
			// 魔法粒子造成20血量伤害
			AttributeComp->ApplyHealthChange(-20.0f);
			// 一旦造成伤害就销毁,避免穿过角色继续计算
			Destroy();
		}
	}
} 
  1. Player中利用蓝图实现在屏幕上打印血量的字符串,以让我们实时了解角色的血量。这个蓝图实现很简单,唯一注意的是设置显示时长为0关闭Print to Log,这样屏幕上只会出现一个数字(事件Tick会在每一帧都调用),且不会在Log中输出大量无用信息:
    在这里插入图片描述
  2. 利用蓝图系统快速实现一个攻击玩家的敌人 (箭头)
    (1) 从Actor派生一个名为ProjectileCanon的蓝图类,为其添加Utility下的箭头组件并设置为根,并设置颜色(UE中用红绿蓝表示XYZ轴,箭头默认的红色与X轴相同容易混乱),去掉“渲染”属性中的“游戏中隐藏”。
    (2)在事件图表中从“开始运行”节点添加循环计时器,并添加名为OnTimerElapsed的自定义事件,设置该事件为朝箭头方向发射魔法粒子;同时,在Tick节点后实现在每一帧将箭头指向玩家,方向向量由两个Actor的坐标相减得到。

三、血量UI

  1. 首先在Content文件夹下创建UI文件夹,用于存放所有UI相关的资产。在UI文件夹中创建“用户界面” -> “控件蓝图”,命名为PlayerHealth_Widget。双击打开UMG的操作界面
  2. 在左侧“控制板”中的“通用”分别添加一个进度条和文本,在“层级”中选择任意组件,右键添加水平框,并将另外一个拖入其中,这样水平框会为我们管理两个元素的水平对齐。
  3. 然后在进度条右侧的“细节”面板中找到“插槽”,选择“尺寸”为填充,拉动水平框的整体长度就可以控制血条的长度。将水平框的“锚点”选择为左上角,设置位置XY值,完成后就做好了简单的血条UI。
  4. Player的蓝图中选择“事件开始运行”,并添加Create Widget节点,选择PlayerHealth_Widget,然后将其添加到视口,这样,在我们运行关卡时UI就会显示在屏幕上。
  5. 此时UI的内容还是固定的,UI需要和数据进行绑定后,才能实时根据数据进行显示。选择文本块,点击 “内容” -> 文本-> 绑定 -> 创建绑定,UE会在蓝图中创建相应函数,我们将其重命名为GetHealthText。添加“获取拥有玩家PawnGet Owning Player Pawn)” -> 按类获取组件Get Component By Class)-> 选择classSAttributeComponet-> GetHealth -> 连接到“返回节点”的return value引脚;然后在“获取拥有玩家Pawn”后添加Is Valid判空。编译保存运行,可以看到数字能够实时显示血量了。
  • 24
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值