自学内容网 自学内容网

82. UE5 RPG 实现角色升级系统(下)

书接上回,在上一篇博客里,我们实现了角色升级的基础的功能。给敌人增加的经验奖励配置,并且在敌人死亡时,能够将经验通过事件传递给击杀者,玩家定义了被动技能,在被动技能中接收传递的事件,通过SetByCaller的GE应用给自身。并在AttributeSet中打印出获得的经验。
我们还修改了PlayerState,在内部增加了对经验和等级添改查的,并创建对应的委托,在UI的Controller里面,实现了对委托的监听,并实现回调函数,通过经验获取等级和升级进度广播给UI表现出来。

创建玩家接口类

有了之前制作的内容,我们接下来,将实现玩家在AS里获取到经验后,设置的PlayerState上,这里防止耦合度太高,我们选择创建一个新的接口实现此功能。
新添加一个接口类
在这里插入图片描述
作为玩家专用的命名为PlayerInterface ,除了它我们还有CombateInterface(战斗接口)EnemyInterface(敌人接口)
在这里插入图片描述
在接口类里,我们增加一个增加经验的函数

class RPG_API IPlayerInterface
{
GENERATED_BODY()

// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:

UFUNCTION(BlueprintNativeEvent)
void AddToXP(int32 InXP); //增加经验
};

在玩家角色类继承此接口

class RPG_API ARPGHero : public ARPGCharacter, public IPlayerInterface
{
GENERATED_BODY()

并覆写此函数

/* IPlayerInterface战斗接口 */
virtual void AddToXP_Implementation(int32 InXP) override;
/* IPlayerInterface战斗接口 结束 */

在实现这里,获取到PlayerState,并调用PlayerState身上的增加经验的函数

void ARPGHero::AddToXP_Implementation(int32 InXP)
{
ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
check(PlayerStateBase); //检测是否有效,无限会暂停游
PlayerStateBase->AddToXP(InXP);
}

最后在AS里,我们通过调用此函数实现经验增长

if(Data.EvaluatedData.Attribute == GetIncomingXPAttribute())
{
const float LocalIncomingXP = GetIncomingXP();
SetIncomingXP(0);
// UE_LOG(LogRPG, Log, TEXT("获取传入经验值:%f"), LocalIncomingXP);

//将经验应用给自身
if(Props.SourceCharacter->Implements<UPlayerInterface>())
{
IPlayerInterface::Execute_AddToXP(Props.SourceCharacter, LocalIncomingXP);
}
}

在UI上实现对经验变动监听

现在,玩家角色可以获得经验,并通过接口设置给PlayerState,然后广播给UI的控制器了。我们要先将之前创建的升级数据设置给PlayerState
在这里插入图片描述
在WBP_Overlay里面,给经验条设置控制器
在这里插入图片描述
在经验条的事件中,设置控制器回调中,绑定对经验变动的监听,在变动时,获取经验条的百分比
在这里插入图片描述
接下来可以测试了,发现上一篇文章里面,在最后类型转换时,需要提前转换将一个数值转换为浮点数类型 ,浮点数/整型 结果就可以是浮点型,要不然,整型除以整型结果还是整型,获取的结果为0
在这里插入图片描述
接着,你就会发现经验条随着怪物死亡,增长了一丝
在这里插入图片描述
然后测试杀死两只,给的经验是否一致
在这里插入图片描述

重构战斗接口中的获取等级函数

我们想将之前书写的获取等级的函数修改成可以在蓝图定义的,达到结构统一,所以,需要额外修改一些内容

UFUNCTION(BlueprintNativeEvent)
int32 GetPlayerLevel(); //获取玩家等级

修改后编译会发现一些报错
在这里插入图片描述
按照之前方式,将其名称后续加上_Implementation即可

/* ICombatInterface战斗接口 */
virtual int32 GetPlayerLevel_Implementation() override;
/* ICombatInterface战斗接口 结束 */

接下来编译,会发现还有一些额外报错,这是因为调用的地方没有修改
在这里插入图片描述
我们可以通过双击shift键,打开搜索,找到调用的地方依次修改。
在这里插入图片描述
找到需要修改的地方,将代码修改成以下类型。注意:Implements后面的类型是以U开头的接口,它是UE增加的,而调用的时候是I开头是c++内置的接口调用方式。

//从战斗接口获取到角色的等级
int32 CharacterLevel = 1;
if(ASC->GetAvatarActor()->Implements<UCombatInterface>())
{
CharacterLevel = ICombatInterface::Execute_GetPlayerLevel(ASC->GetAvatarActor());
}

增加角色接口函数

为了实现接下来角色升级的功能,我们扩展角色接口的函数

class RPG_API IPlayerInterface
{
GENERATED_BODY()

// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:

UFUNCTION(BlueprintNativeEvent)
int32 FindLevelForXP(int32 InXP) const; //根据经验获取等级

UFUNCTION(BlueprintNativeEvent)
int32 GetXP() const; //获取当前经验值

UFUNCTION(BlueprintNativeEvent)
int32 GetAttributePointsReward(int32 Level) const; //获取属性点奖励

UFUNCTION(BlueprintNativeEvent)
int32 GetSpellPointsReward(int32 Level) const; //获取技能点奖励

UFUNCTION(BlueprintNativeEvent)
void AddToXP(int32 InXP); //增加经验

UFUNCTION(BlueprintNativeEvent)
void AddToPlayerLevel(int32 InPlayerLevel); //增加等级

UFUNCTION(BlueprintNativeEvent)
void AddToAttributePoints(int32 InAttributePoints); //增加属性点

UFUNCTION(BlueprintNativeEvent)
void AddToSpellPoints(int32 InSpellPoints); //增加技能点

UFUNCTION(BlueprintNativeEvent)
void LevelUp(); //升级
};

接下来,我们在继承接口的玩家角色类里面覆写函数

/* IPlayerInterface战斗接口 */
virtual void AddToXP_Implementation(int32 InXP) override;
virtual void LevelUp_Implementation() override;
virtual int32 GetXP_Implementation() const override;
virtual int32 FindLevelForXP_Implementation(int32 InXP) const override;
virtual int32 GetAttributePointsReward_Implementation(int32 Level) const override;
virtual int32 GetSpellPointsReward_Implementation(int32 Level) const override;
virtual void AddToPlayerLevel_Implementation(int32 InPlayerLevel) override;
virtual void AddToAttributePoints_Implementation(int32 InAttributePoints) override;
virtual void AddToSpellPoints_Implementation(int32 InSpellPoints) override;
/* IPlayerInterface战斗接口 结束 */

除了已经实现的添加经验的函数,我们将其它函数依次实现。

首先是升级函数,这个函数主要用于提供角色升级时,播放一下升级表现效果,因为当前类是在服务器运行的,我们要实现一个在每个客户端都可以运行的效果,就需要增加一个可以在多端运行的函数,这个在稍微讲解。

接下来是获取当前角色经验值

int32 ARPGHero::GetXP_Implementation() const
{
const ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
check(PlayerStateBase); //检测是否有效,无限会暂停游戏
return PlayerStateBase->GetXP();
}

通过经验值获取当前等级

int32 ARPGHero::FindLevelForXP_Implementation(const int32 InXP) const
{
const ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
check(PlayerStateBase); //检测是否有效,无限会暂停游戏
return PlayerStateBase->LevelUpInfo->FindLevelForXP(InXP);
}

获取当前等级奖级的属性点

int32 ARPGHero::GetAttributePointsReward_Implementation(const int32 Level) const
{
const ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
check(PlayerStateBase); //检测是否有效,无限会暂停游戏
return PlayerStateBase->LevelUpInfo->LevelUpInformation[Level].AttributePointAward;
}

获取当前等级奖励的技能点

int32 ARPGHero::GetSpellPointsReward_Implementation(const int32 Level) const
{
const ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
check(PlayerStateBase); //检测是否有效,无限会暂停游戏
return PlayerStateBase->LevelUpInfo->LevelUpInformation[Level].SpellPointAward;
}

设置当前角色升级,主要是修改角色的数值

void ARPGHero::AddToPlayerLevel_Implementation(int32 InPlayerLevel)
{
ARPGPlayerState* PlayerStateBase = GetPlayerState<ARPGPlayerState>();
check(PlayerStateBase); //检测是否有效,无限会暂停游戏
PlayerStateBase->AddToLevel(InPlayerLevel);
}

还有在后续章节中添加的属性修改功能函数,为角色增加属性点和技能点

void ARPGHero::AddToAttributePoints_Implementation(int32 InAttributePoints)
{
//TODO:实现增加属性点
}

void ARPGHero::AddToSpellPoints_Implementation(int32 InSpellPoints)
{
//TODO:实现增加技能点
}

实现等级升级

接下来,我们在AS里面获得经验后,处理升级相关逻辑。
首先判断是否升级,逻辑是当前经验+获得的经验是否能够升级,获取到增加经验后的等级和原来的等级是否一致,如果增加了,意味着角色等级得到了提升。

//获取获得经验后的新等级
const int32 CurrentLevel = ICombatInterface::Execute_GetPlayerLevel(Props.SourceCharacter);
const int32 NewLevel = IPlayerInterface::Execute_FindLevelForXP(Props.SourceCharacter, CurrentXP + LocalIncomingXP);
const int32 NumLevelUps = NewLevel - CurrentLevel; //查看等级是否有变化
if(NumLevelUps > 0)
{
...
}

在升级逻辑里面,我们首先获取当前等级提供的属性点和技能点的奖励

//获取升级提供的技能点和属性点
int32 AttributePointsReward = IPlayerInterface::Execute_GetAttributePointsReward(Props.SourceCharacter, CurrentLevel);
int32 SpellPointsReward = IPlayerInterface::Execute_GetSpellPointsReward(Props.SourceCharacter, CurrentLevel);

然后提升角色等级,并应用奖励

//提升等级,增加角色技能点和属性点
IPlayerInterface::Execute_AddToPlayerLevel(Props.SourceCharacter, NumLevelUps);
IPlayerInterface::Execute_AddToAttributePoints(Props.SourceCharacter, AttributePointsReward);
IPlayerInterface::Execute_AddToSpellPoints(Props.SourceCharacter, SpellPointsReward);

调用升级表现函数

IPlayerInterface::Execute_LevelUp(Props.SourceCharacter); //升级

升级时,将血量和蓝量填满

//将血量和蓝量填充满
SetHealth(GetMaxHealth());
SetMana(GetMana());

下面列出来完整代码

if(Data.EvaluatedData.Attribute == GetIncomingXPAttribute())
{
const float LocalIncomingXP = GetIncomingXP();
SetIncomingXP(0);
// UE_LOG(LogRPG, Log, TEXT("获取传入经验值:%f"), LocalIncomingXP);

if(Props.SourceCharacter->Implements<UPlayerInterface>() && Props.SourceCharacter->Implements<UCombatInterface>())
{
//获取角色当前等级和经验
const int32 CurrentLevel = ICombatInterface::Execute_GetPlayerLevel(Props.SourceCharacter);
const int32 CurrentXP = IPlayerInterface::Execute_GetXP(Props.SourceCharacter);

//获取获得经验后的新等级
const int32 NewLevel = IPlayerInterface::Execute_FindLevelForXP(Props.SourceCharacter, CurrentXP + LocalIncomingXP);
const int32 NumLevelUps = NewLevel - CurrentLevel; //查看等级是否有变化
if(NumLevelUps > 0)
{
//获取升级提供的技能点和属性点
int32 AttributePointsReward = IPlayerInterface::Execute_GetAttributePointsReward(Props.SourceCharacter, CurrentLevel);
int32 SpellPointsReward = IPlayerInterface::Execute_GetSpellPointsReward(Props.SourceCharacter, CurrentLevel);

//提升等级,增加角色技能点和属性点
IPlayerInterface::Execute_AddToPlayerLevel(Props.SourceCharacter, NumLevelUps);
IPlayerInterface::Execute_AddToAttributePoints(Props.SourceCharacter, AttributePointsReward);
IPlayerInterface::Execute_AddToSpellPoints(Props.SourceCharacter, SpellPointsReward);

IPlayerInterface::Execute_LevelUp(Props.SourceCharacter); //升级

//将血量和蓝量填充满
SetHealth(GetMaxHealth());
SetMana(GetMana());
}

//将经验应用给自身,通过事件传递,在玩家角色被动技能GA_ListenForEvents里接收
IPlayerInterface::Execute_AddToXP(Props.SourceCharacter, LocalIncomingXP);
}
}

在ui上面显示等级

我们现在角色有了等级,需要在UI上面表现出来。
我们首先处理回调,之前实现了经验的表现,我们在PlayerState类里面设置和升级时,会调用委托广播
在这里插入图片描述
那么,我们在UI Controller里面添加一个委托,用于广播给UI监听,等级为整型参数

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerStateChangedSignature, int32, NewValue); //当玩家状态该表回调类型

接着创建对应类型的变量

UPROPERTY(BlueprintAssignable, Category="GAS|Level")
FOnPlayerStateChangedSignature OnPlayerLevelChangeDelegate; //等级变动回调

绑定一个匿名函数,用于监听PlayerState的等级回调

//绑定等级相关回调
RPGPlayerState->OnLevelChangedDelegate.AddLambda([this](int32 NewLevel)
{
OnPlayerLevelChangeDelegate.Broadcast(NewLevel);
});

接下来,我们基于WBP_SpellGlobe复制一份,创建一个用于显示等级的ui
在这里插入图片描述
将使用图标设置的部分删除,我们不需要在等级显示UI里面设置图标
在这里插入图片描述
在设置控制器回调里面监听等级委托,回调触发修改等级显示文字
在这里插入图片描述
为了方便设置,我们可以修改对应怪物提供的经验值
在这里插入图片描述
将其拖入到WBP_Overlay上面,并设置对应控制器
在这里插入图片描述
接下来,我们就可以运行查看效果
在这里插入图片描述

创建角色图标

接下来,我们想在左上角显示对应角色的图标,这里使用WBP_GlobeProgressbar作为模版复制一份
在这里插入图片描述
命名为WBP_PictureFrame
在这里插入图片描述
移动到对应的文件夹中
在这里插入图片描述
删除一些无用的函数
在这里插入图片描述
增加一个用于显示头像的节点
在这里插入图片描述
设置对应头像
在这里插入图片描述
在WBP_Overlay里面应用,并设置好位置
在这里插入图片描述

设置升级效果

接下来,我们将上面略过的等级升级效果函数实现一下,我们需要增加一个粒子特效在升级时播放特效。由于特效是平面播放,它会跟随角色的角度,有时候看到是一个竖条的,所以我们还需要将相机在代码里创建,方便后续获取相机的旋转。
我们首先在代码里创建相机和弹簧臂

UPROPERTY(VisibleAnywhere)
TObjectPtr<UCameraComponent> TopDownCameraComponent; //相机组件

UPROPERTY(VisibleAnywhere)
TObjectPtr<USpringArmComponent> CameraBoom; //弹簧臂组件

在初始化时创建它们

//设置相机
CameraBoom = CreateDefaultSubobject<USpringArmComponent>("CameraBoom");
CameraBoom->SetupAttachment(GetRootComponent());
CameraBoom->SetUsingAbsoluteRotation(true);
CameraBoom->bDoCollisionTest = false;

TopDownCameraComponent = CreateDefaultSubobject<UCameraComponent>("TopDownCameraComponent");
TopDownCameraComponent->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
TopDownCameraComponent->bUsePawnControlRotation = false;

然后创建一个NiagaraComponent,用于设置使用的粒子特效,它需要在蓝图中设置使用的粒子资源

UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
TObjectPtr<UNiagaraComponent> LevelUpNiagaraComponent; //升级特效组件

在构造函数中初始化,并将自动播放关闭,我们在角色升级的时候让其播放

//设置升级特效组件
LevelUpNiagaraComponent = CreateDefaultSubobject<UNiagaraComponent>("LevelUpNiagaraComponent");
LevelUpNiagaraComponent->SetupAttachment(GetRootComponent()); //设置附加组件
LevelUpNiagaraComponent->bAutoActivate = false; //设置不自动激活

我们还需要创建一个支持多客户端调用的函数,它将在每个客户端都实现调用,这样,在每个客户端都能看到同样的效果

UFUNCTION(NetMulticast, Reliable)
void MulticastLevelUpParticles() const; //在多人游戏,每个客户端上播放升级特效

在实现这里,我们将通过相机和粒子位置的朝向,注意,这个朝向是世界坐标系的,然后通过朝向获取旋转,设置给粒子

void ARPGHero::MulticastLevelUpParticles_Implementation() const
{
if(IsValid(LevelUpNiagaraComponent))
{
const FVector CameraLocation = TopDownCameraComponent->GetComponentLocation();
const FVector NiagaraSystemLocation = LevelUpNiagaraComponent->GetComponentLocation();
const FRotator TopCameraRotation = (CameraLocation - NiagaraSystemLocation).Rotation(); //获取相机位置和离职特效的朝向
LevelUpNiagaraComponent->SetWorldRotation(TopCameraRotation); //设置粒子的转向
LevelUpNiagaraComponent->Activate(true); //激活特效
}
}

并在AS调用函数时,调用此函数,因为AS只在服务器运行,再调用此函数,实现了每个客户端的运行

void ARPGHero::LevelUp_Implementation()
{
MulticastLevelUpParticles(); //调用播放升级特效
}

接着编译打开UE,设置粒子特效
在这里插入图片描述
并将之前在蓝图中创建的相机和弹簧臂删除掉,将之前的碰撞盒移动到代码创建的弹簧臂上面
在这里插入图片描述
删除蓝图创建的相机和弹簧臂
在这里插入图片描述
运行查看效果。
在这里插入图片描述

创建升级消息提示

接下来,我们优化效果,在升级时提供消息提示UI,并播放音效。
在这里插入图片描述
父类选择我们自定义的RPGUserWidget
在这里插入图片描述

命名为WBP_LevelUpMessage 为升级提示
在这里插入图片描述
我们在里面添加一个覆层,作为全屏显示,并在覆层下面添加一个包裹框,文字超出范围后会自动换行
在这里插入图片描述
为了防止文字在最顶部显示,我们在上面添加一个间隔区,用来填充空间,宽度设置的尽量大一些,防止文字在间隔区后面显示。
在这里插入图片描述
增加一个文本,设置好样式,让其可以占用一整行
在这里插入图片描述
下面添加一个水平框,框内设置两个文本,显示等级和实际等级数字
在这里插入图片描述
水平框设置对其和填充空间以及强制换新行,文本向左向右对其,这样,就可以居中显示
在这里插入图片描述
实际效果如下
在这里插入图片描述
我们也可以用上下设置垂直框,这样更方便设置
在这里插入图片描述
接着,我们要在WBP_Overlay里面在设置了控制器后,监听等级的变动,在监听到等级变化后,将现存的等级提示删除,然后接着创建一个新的控件,并将目标等级设置,并添加到视口。这样可以防止连续升级时,多层覆盖的问题。
在这里插入图片描述
运行查看效果。
在这里插入图片描述
接下来,我们优化一下,制作一个动画
在这里插入图片描述
在事件开始后,播放音效和动画,并延迟三秒将UI销毁
在这里插入图片描述
接下来查看最终效果
在这里插入图片描述


原文地址:https://blog.csdn.net/qq_30100043/article/details/140517277

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!