今天要做的是关于UI界面的一些功能,显示生命值、体力条、金币数量,并将这些UI与人物的属性连接起来,动态设置
UI设置–生命值、体力条、整体界面和金币数量
生命值
右键内容浏览器–>Edit Utilities–>Editor Widget创建UI界面–>名称为WB_Health
1)在panel面板中选取一个HorizontalBox水平框和一个ProgressBar进度条,水平框中物体都会水平摆放,竖直框中物体都会竖直摆放。
将竖直框放入水平框之下,在Slot的Size中Fill填充,使进度条填充水平框。
2)我们先将Progress中的percent调成0.2,方便我们添加主体颜色和背景颜色进行对比。
主体颜色:Appearance–>Fill Color–>红色(1,0,0)
背景颜色:Style–>Background–>Tint着色–>最后一行A设置0.2
绑定蓝图事件
Progress中的percent使用Bind,绑定蓝图事件
其中先选中进度条,并在编辑界面右上角设置为变量,这样就可以在蓝图里使用
Get Owning Player Pawn–>CastTo Man–>将Man提升为变量RefMan
因为Man变量的属性都在玩家控制的Pawn之中,因此将Pawn类型强制转换为Man
Get Percent 0–>IsValid?return value:CastTo Man?return value:return value(原始设置的值);
这里用了C++的问号表达式来描述过程:
获取百分比值时先判断值是否有效,如果有效,则返回百分比;若无效,则转化类型
转换类型若若成功则,返回百分比;若失败则返回默认值
return value的值为从Man中读取出的生命值/最大生命值,即百分比
return value(原始设置的值)是进度条的初始设定值
体力条
复制生命值蓝图,命名WB_Stamina,颜色设置为黄色(1,1,0)
蓝图事件与生命值基本一致,只需将其中生命值的获取改为体力条
金币数量
1)金币需要在panel面板中选取一个HorizontalBox水平框,其附件选取Text文字框以及一个SizeBox尺寸盒子并Fill填充,尺寸盒子可以保持长宽比
尺寸盒子附件添加一个图片框用并放一个金币图片(PS可以用魔术橡皮擦去除白色背景,但需到保存为png文件),Text文字框将text框居中
金币图片在Size Box–>Child Layout中Min Aspect Ratio和Max Aspect Ratio设置为1,锁定长宽比
绑定蓝图事件
生命值和体力条返回的是浮点类型,金币数量返回的是文本型,但整体逻辑是一样的
另外,将Text文字框提升为变量,
这里return value的值为从Man中读取出硬币数量
return value(原始设置的值)是Text文字框的初始设定值
整体界面
右键内容浏览器–>Edit Utilities–>Editor Widget创建UI界面–>名称为WB_HUD
1)在panel面板中选取一个竖直框和两个水平框,两个水平框为附件;
两个水平框同时Fill,分成了上下两部分,上方Size设置为0.15,上小下大,上面用于放生命值、体力条和金币数量状态显示的地方
2)在上面水平框内加入一个竖直框和两个水平框并Fill填充,第一个竖直框放生命值和体力条,最后一个放金币显示,第一个Size 0.4,最后一个0.3
在第一个水平框中加入两个竖直盒子Fill填充,padding 设置空隙10,从Uesr Created中拖拽出生命值和体力条放入
程序设置
UI显示设置
1)这样设置完并不能直接在UI中显示,需要在Bulid.cs中PublicDependencyModuleNames加入UMG模块
2)另外需要创建一个继承玩家控制器的自定义控制器类MainPlayerController,这样我们能够制定我们自定义的HUD用户控件。
public:
//UUserWidget子类的一个类型声明,对象为用户控件
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSubclassOf<UUserWidget> HUDAsset;
//抬头显示器,一种用户控件
UUserWidget *HUD;
3)函数实现
添加头文件:#include “Blueprint/UserWidget.h” 用于用户控制
void AMainPlayerController::BeginPlay()
{
Super::BeginPlay();
if (HUDAsset)
{
//用HUDAsset这个类去生成一个实例HUD
HUD = CreateWidget<UUserWidget>(this, HUDAsset);
}
if (HUD)
{
//将实例显示到屏幕上
HUD->AddToViewport();
}
}
这样在游戏开始阶段,使用该控制器就会将我们选取的用户控制器实例,投射到屏幕上。
本文中的例子就是创建蓝图BP_PlayerController继承于MainPlayerController,在HUDAsset中选取WB_HUD,在世界选项中选BP_PlayerController作为控制器
生命值等与变量关联实时变化
人物相关属性初始化
生命值等属性于任务属性,因为写在人物的父类Man中 添加生命值、体力条、金币数量和施加伤害、死亡和拾取函数
//生命值和最大生命值
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Health;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float MaxHealth;
//体力条和最大体力条
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Stamina;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
float MaxStamina;
//金币数量
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 Coins;
//施加伤害函数
void SufferDamage(float Damage);
//死亡事件
void Die();
//拾取函数
void Pickup(EPickupType Type, uint32 Count);
另外需要枚举定义金币类型(目前只有一种),给Pickup函数使用
enum EPickupType
{
PT_Coin,
};
施加伤害以及拾取函数的实现
传入参数demage,每次调用SufferDamage,生命值减少demage
void AMan::SufferDamage(float Damage)
{
float Tmp = Health - Damage;
//试算一下伤害后的生命值,并约束到0到MaxHealth之间
Tmp = FMath::Clamp(Tmp, 0.f, MaxHealth);
Health = Tmp;
if (Health <= 0)
{
Die();
}
}
void AMan::Die()
{
}
传入参数EPickupType,每次调用根据金币的类型,设置Count,增加UI金币显示数量
void AMan::Pickup(EPickupType Type, uint32 Count)
{
//目前只有PT_Coin一种类型
switch (Type)
{
case PT_Coin:
Coins += Count;
break;
default:
break;
}
}
Explosive Item实现爆炸施加伤害
爆炸物是我们自定义Item的一个子类,在爆炸物Explosive.h中加入构造函数和伤害值
public:
AExplosive();
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Damage;
Explosive.cpp 加入头文件#include “Man.h” 构造函数中设置伤害为10, 碰撞函数中,获取触发对象转换为Man类型,调用SufferDamage(Damage)对实例造成伤害
AExplosive::AExplosive()
{
Damage = 10;
}
void AExplosive::OnOverlapBegin_Item(UPrimitiveComponent * OverlappedComponent, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
Super::OnOverlapBegin_Item(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
UE_LOG(LogTemp, Warning, TEXT("%s"), *FString(__FUNCTION__));
if (OtherActor)
{
//拿到伤害对象这个实例
AMan * Man = Cast<AMan>(OtherActor);
if (Man)
{
//对实例造成伤害
Man->SufferDamage(Damage);
}
}
}
Pickup Item拾取金币
添加头文件#include “Man.h”
碰撞函数中,获取触发对象转换为Man类型,碰到PT_Coin,则调用Pickup函数增加数量
void APickup::OnOverlapBegin_Item(UPrimitiveComponent * OverlappedComponent, AActor * OtherActor, UPrimitiveComponent * OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult & SweepResult)
{
Super::OnOverlapBegin_Item(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);
UE_LOG(LogTemp, Warning, TEXT("%s"), *FString(__FUNCTION__));
if (OtherActor)
{
AMan * Man = Cast<AMan>(OtherActor);
if (Man)
{
Man->Pickup(PT_Coin, 1);
}
}
}
体力条变化与状态机修改
体力条变化与动画相关,这里单独拿出来写
相关变量初始化
体力条相关变量初始化,
状态变量、设置状态函数:用于设置状态的变化
普通奔跑速度和冲刺速度:根据不同的状态变换速度
冲刺状态开始和结束的函数
IsExhausted判断体力是否耗尽
另外,在轴映射处将Shift绑定Sprint
public:
//定义当前状态变量
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
EStatus CurrentStatus;
//设置当前状态
void SetStatus(EStatus Status);
//奔跑速度
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float RunSpeed;
//冲刺速度
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float SprintSpeed;
//开始冲刺
UFUNCTION()
void BeginSprint();
//结束冲刺
UFUNCTION()
void EndSprint();
UFUNCTION()
void StaminaChange(float DeltaTime);
//表示是否体力耗尽
UPROPERTY(EditAnywhere, BlueprintReadOnly)
bool IsExhausted;
//体力变化速度
UPROPERTY(EditAnywhere, BlueprintReadOnly)
float StaminaDrainRate;
枚举定义运动状态,根据状态关联体力条
UENUM(BlueprintType)
enum class EStatus :uint8
{
//定义两种状态,一种普通状态,一种冲刺状态
ES_Normal UMETA(DisplayName = "Normal"),
ES_Sprint UMETA(DisplayName = "Sprint"),
};
函数实现
变量设置初值
当前状态为ES_Normal,设置默认速度为600;
设置普通奔跑速度600和冲刺速度1000;
IsExhausted初始为false;
体力变化速度为30(正常来讲体力增加和减少应该是两个速度);
CurrentStatus = EStatus::ES_Normal;
GetCharacterMovement()->MaxWalkSpeed = 600;
RunSpeed = 600;
SprintSpeed = 1000;
IsExhausted = false;
StaminaDrainRate = 30;
状态设置函数,普通奔跑设置速度为RunSpeed 冲刺时设置速度为SprintSpeed
void AMan::SetStatus(EStatus Status)
{
CurrentStatus = Status;
switch (CurrentStatus)
{
case EStatus::ES_Normal:
GetCharacterMovement()->MaxWalkSpeed = RunSpeed;
break;
case EStatus::ES_Sprint:
GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
break;
default:
break;
}
}
体力条变化,平常状态下体力增加,小于最大体力值时,显示当前体力;大于最大体力值时,显示最大体力值
冲刺状态下体力减少,大于最小体力值时,显示当前体力;体力小于0时,IsExhausted为true,设置为普通状态恢复体力
case后面带个{},不然可能会出现错误;该函数放入tick中,每帧调用计算
void AMan::StaminaChange(float DeltaTime)
{
float Delta = StaminaDrainRate * DeltaTime;
switch (CurrentStatus)
{
//如果是平常体力
case EStatus::ES_Normal:
{
float Tmp = Stamina + Delta;
if (Tmp < MaxStamina)
{
Stamina = Tmp;
}
else
{
Stamina = MaxStamina;
IsExhausted = false;
}
break;
}
case EStatus::ES_Sprint:
{
float Tmp = Stamina - Delta;
if (Tmp > 0)
{
Stamina = Tmp;
}
else
{
Stamina = 0;
IsExhausted = true;
SetStatus(EStatus::ES_Normal);
}
break;
}
default:
break;
}
}
// Called every frame
void AMan::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
StaminaChange(DeltaTime);
}
SetupPlayerInputComponent绑定BeginSprint()和EndSprint()
如果体力没耗尽,按下shift变为冲刺状态,松开变为普通
void AMan::BeginSprint()
{
if (!IsExhausted)
{
SetStatus(EStatus::ES_Sprint);
}
}
void AMan::EndSprint()
{
if (!IsExhausted)
{
SetStatus(EStatus::ES_Normal);
}
}
// Called to bind functionality to input
void AMan::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
//开始结束冲刺
PlayerInputComponent->BindAction("Sprint", IE_Pressed, this, &AMan::BeginSprint);
PlayerInputComponent->BindAction("Sprint", IE_Released, this, &AMan::EndSprint);
}
状态机修改
因为涉及修改动画,并且我们需要Man的变量来作为动画转换的变量 因此,在我们创建的动画蓝图类MainAnimInstance.h中需要加入Man的引用,
//引入Man变量,用于与动画状态关联
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Movement)
class AMan *Man;
MainAnimInstance.cpp中添加头文件#include “Man.h” 并将获取的pawn转换为Man,以使用Man中变量
void UMainAnimInstance::NativeInitializeAnimation()
{
//获取pawn的拥有者
if (!Pawn)
{
Pawn = TryGetPawnOwner();
}
Man = Cast<AMan>(Pawn);
}
我们将之前做的混合动画1D的范围改到1000,分格改成5格
之前我们做的混合动画
0的地方放站立动画,600的地方放普通速度移动,1000的地方放快速运动,这样就初步设置了与Man中状态变量相关的动画
但仅仅这样设置是不够的,仅这样设置,当按下shift的时候没有动画。
因此,将动画蓝图中状态机里加入一个冲刺状态
idle/walk/run状态 Man–>Get Status == Sprint?进入冲刺状态,播放冲刺动画:idle/walk/run状态
Sprint状态 Man–>Get Status == Normal?进入idle/walk/run状态,播放动画:Sprint状态
这样,当按下shift的时候就是Sprint状态,就可以播放相关动画
体力耗尽,体力条颜色修改
Apperence中的Fill Color and Opacity使用Bind,绑定蓝图事件;
整体逻辑与之前一样;
Get Fill Color and Opacity 0–>IsValid?return value:CastTo Man?return value:return value(原始设置的值);
return value的值使用Select Actor,以Man中的IsExhausted为依据,若为false则为黄色(1,1,0),若为true,则为灰色(0.5,0.5,0.5);
return value(原始设置的值)是颜色的初始设定值