当前位置: 首页 > news >正文

83. UE5 RPG 实现属性值的设置

在前面,我们实现了角色升级相关的功能,在PlayerState上记录了角色的等级和经验值,并在变动时,通过委托广播的形式向外广播,然后在UI上,通过监听委托的变动,进行修改等级和经验值。
在这一篇里,我们实现一下对属性值的操作,原理和等的差不多,但是编写死不一样。属性值是在属性面板上的属性,它有一个专用的属性面板控制器。
我们的控制器的基类为RPGWidgetController.h,从基类分了两个子类,OverlayWidgetController.h一直显示在战斗界面的覆层的控制器,用于更新血量蓝量,技能,经验等级等。AttributeMenuWidgetController.h为专门为属性面板使用的控制器,我们在属性里设置了一个数组,用于对应属性标签和属性的对应,并在属性修改后,对其进行广播,用于属性面板的更新,虽然此内容与本篇无关,作为记录。
我们接下来,会在此控制器增加属性点变动的委托。

在PlayerState增加属性点记录

和等级相同,我们先增加对属性点的参数。

	UPROPERTY(VisibleAnywhere, ReplicatedUsing=OnRep_AttributePoints)int32 AttributePoints = 0;

以及属性点的变动委托

FOnPlayerStateChanged OnAttributePointsChangedDelegate; //属性点数变动委托

从服务器到客户端的同步函数

	UFUNCTION()void OnRep_AttributePoints(int32 OldAttributePoints) const; //服务器出现更改自动同步到本地函数 属性点

实现

void ARPGPlayerState::OnRep_AttributePoints(int32 OldAttributePoints) const
{OnAttributePointsChangedDelegate.Broadcast(AttributePoints);
}

接着增加属性点的相关函数

	//属性点FORCEINLINE int32 GetAttributePoints() const {return AttributePoints;} //获取角色当前属性点void AddToAttributePoints(int32 InAttributePoints); //增加属性点void SetAttributePoints(int32 InAttributePoints); //设置当前属性点

在设置后广播委托

void ARPGPlayerState::AddToAttributePoints(const int32 InAttributePoints)
{AttributePoints += InAttributePoints;OnAttributePointsChangedDelegate.Broadcast(AttributePoints);
}void ARPGPlayerState::SetAttributePoints(const int32 InAttributePoints)
{AttributePoints = InAttributePoints;OnAttributePointsChangedDelegate.Broadcast(AttributePoints);
}

控制器添加委托

为了方便增加玩家状态的委托,我们将之前放在OverlayWidgetController.h覆层控制器里面的委托,移到基类RPGWidgetController.h,这样它的子类都可以使用这个委托类型,防止重复定义。

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

接下来,我们就可以在属性面板控制器AttributeMenuWidgetController.h里面声明属性点委托

	UPROPERTY(BlueprintAssignable, Category="GAS|Attributes")FOnPlayerStateChangedSignature AttributePointsChangedDelegate; //监听属性点的变化委托

在绑定委托这里,我们实现对PlayerState里的委托绑定匿名函数,用于UI使用

	ARPGPlayerState* RPGPlayerState = CastChecked<ARPGPlayerState>(PlayerState);//绑定PlayerState的属性点委托RPGPlayerState->OnAttributePointsChangedDelegate.AddLambda([this](const int32 Points){AttributePointsChangedDelegate.Broadcast(Points);});

在初始化这里,我们也需要广播一次,在面板打开时,显示正确的属性点

	const ARPGPlayerState* RPGPlayerState = CastChecked<ARPGPlayerState>(PlayerState);AttributePointsChangedDelegate.Broadcast(RPGPlayerState->GetAttributePoints());

修改一次升多个等级

我们还未对一次提升两级或者三级时对角色的属性点奖励和技能点奖励进行设置,实现起来简单,我们只需要对提升的等级进行遍历,将所有的奖励应用即可。
在AS里面,我们在升级时增加遍历,将已经升到的等级的奖励应用

//获取获得经验后的新等级const int32 NewLevel = IPlayerInterface::Execute_FindLevelForXP(Props.SourceCharacter, CurrentXP + LocalIncomingXP);const int32 NumLevelUps = NewLevel - CurrentLevel; //查看等级是否有变化if(NumLevelUps > 0){//如果连升多级,我们通过for循环获取每个等级的奖励for(int32 i = CurrentLevel; i < NewLevel; i++){//获取升级提供的技能点和属性点const int32 AttributePointsReward = IPlayerInterface::Execute_GetAttributePointsReward(Props.SourceCharacter, i);const int32 SpellPointsReward = IPlayerInterface::Execute_GetSpellPointsReward(Props.SourceCharacter, i);//增加角色技能点和属性点IPlayerInterface::Execute_AddToAttributePoints(Props.SourceCharacter, AttributePointsReward);IPlayerInterface::Execute_AddToSpellPoints(Props.SourceCharacter, SpellPointsReward);}//提升等级IPlayerInterface::Execute_AddToPlayerLevel(Props.SourceCharacter, NumLevelUps);//播放升级效果IPlayerInterface::Execute_LevelUp(Props.SourceCharacter);//将血量和蓝量填充满SetHealth(GetMaxHealth());SetMana(GetMaxMana());}

在UI实现对属性点的监听

我们之前创建的属性面板,以及对应的部件无法实现当前功能
在这里插入图片描述
所以,我们要为属性点创建一个专用的控制面板
在这里插入图片描述
在控件里面,我们需要通过蓝图修改属性名称,直接写上即可
在这里插入图片描述
然后修改监听的委托,修改为监听属性点的变动委托
在这里插入图片描述
现在我们有了属性点的值,接着要在玩家角色类里,将之前未完成的函数编写完成

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

测试,在角色等级提升时,角色属性点是否会自动修改。
在这里插入图片描述

设置加点按钮的开启关闭

接下来,我们实现一下对加点按钮的开启和关闭的设置。我们想在有可分配的属性点时,加点按钮可以点击,而在可分配属性点数为0时,我们将其设置为不可点击。
我们在WBP_TextValueButtonRow里增加一个函数,用于设置按钮点击是否开启
在这里插入图片描述
然后在WBP_AttributeMenu里增加一个函数,传入当前可分配的属性点数,根据属性点数设置是个按钮是否需要开启
在这里插入图片描述
接着,我们重写事件,将获取到的将控制器存储到变量,并监听属性点变动回调,调用设置按钮函数,并在最后初始化。
在这里插入图片描述
这个获取方式是我们写的蓝图函数库的方法,它们设置的位置是我们自定义的HUD类上,并存储一份单例
在这里插入图片描述
接着运行测试,在可分配属性点为0时,无法点击
在这里插入图片描述
当拥有了可分配属性点,增加按钮变为可点击状态
在这里插入图片描述

实现属性加点功能

有了可分配属性,接下来我们将实现属性加点的功能。
我们将通过点击按钮触发事件,然后调用控制器内的函数,将加点属性的标签传递,接着在ASC里,通过发送事件,将需要增加的属性和之前增长经验的方式增加,并在内部处理可分配的属性值。
首先,我们在AttributeMenuWidgetController.h增加一个函数,用于增加事件触发后的调用函数

	UFUNCTION(BlueprintCallable, Category="GAS|Attributes")void UpgradeAttribute(const FGameplayTag& AttributeTag); //升级属性

然后在自定义的ASC中增加两个函数,它们其实是为了实现一个功能,但是对于属性操作只需要在服务器去做修改即可,所以,我们额外增加了一个只有服务器运行的函数

	void UpgradeAttribute(const FGameplayTag& AttributeTag); //升级属性UFUNCTION(Server, Reliable)void ServerUpgradeAttribute(const FGameplayTag& AttributeTag); //服务器升级属性函数

我们需要能够在ASC里面去获取当前角色是否拥有可分配的属性点数,在角色接口里增加一个获取的函数

	UFUNCTION(BlueprintNativeEvent)int32 GetAttributePoints() const; //获取可分配属性点数

在角色基类里覆写它

virtual int32 GetSpellPoints_Implementation() const override;

实现此函数,从PlayerState里获取即可

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

有了属性点数的获取,我们就可以实现ASC中创建的升级属性的函数,如果角色拥有可分配的点数,那么我们将让服务器执行发送事件,我们之前创建了被动技能接收属性相关的标签事件,我们将需要提升的属性标签和提升数量传递过去,并通过接口,减少相应的数量。

void URPGAbilitySystemComponent::UpgradeAttribute(const FGameplayTag& AttributeTag)
{//判断Avatar是否基础角色接口if(GetAvatarActor()->Implements<UPlayerInterface>()){//判断是否用于可分配点数if(IPlayerInterface::Execute_GetAttributePoints(GetAvatarActor()) > 0){ServerUpgradeAttribute(AttributeTag); //调用服务器升级属性}}
}void URPGAbilitySystemComponent::ServerUpgradeAttribute_Implementation(const FGameplayTag& AttributeTag)
{FGameplayEventData Payload; //创建一个事件数据Payload.EventTag = AttributeTag;Payload.EventMagnitude = 1.f;//向自身发送事件,通过被动技能接收属性加点UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(GetAvatarActor(), AttributeTag, Payload);//判断Avatar是否基础角色接口if(GetAvatarActor()->Implements<UPlayerInterface>()){IPlayerInterface::Execute_AddToAttributePoints(GetAvatarActor(), -1); //减少一点可分配属性点}
}

最后,我们在控制器里,调用ASC对应的函数

void UAttributeMenuWidgetController::UpgradeAttribute(const FGameplayTag& AttributeTag)
{URPGAbilitySystemComponent* ASC = CastChecked<URPGAbilitySystemComponent>(AbilitySystemComponent);ASC->UpgradeAttribute(AttributeTag);
}

接着编译代码,打开被动技能应用的GE,我们之前在里面增加了经验的设置
在这里插入图片描述
由于设置对应的属性是通过标签设置的,我们接着增加四个主要属性的修改,如果没有设置对应的属性,那么传递的值将默认为0
在这里插入图片描述
在这里插入图片描述
只要事件传递过来,带有标签和数值,GE会通过SetbyCaller被设置。

实现按钮的点击事件

最后,我们想给点击事件绑定回调,在回调里调用控制器的升级属性函数,并将属性标签传递,我们在里面已经监听对应标签的属性变动的回调了,标签是设置在UI上的,已经有了标签,后续就是实现点击回调调用函数即可。
在这里插入图片描述
我们首先在WBP_TextValueRow蓝图,将控制器切换并设置为变量,这样方便后续和子蓝图的使用
在这里插入图片描述
然后在WBP_AttributePointsRow里,对按钮的点击回调进行绑定,调用属性面板控制器的升级属性
在这里插入图片描述
这样,我们实现了整个逻辑,现在就可以去测试

在这里插入图片描述

修复无法满血的bug

出现这个bug的原因是因为在我们设置满血的时候,最大血量或者最大蓝量的属性还没有应用。解决这个bug需要在属性变动的回调里面修改,我们可以根据比例将属性值进行提升,或者设置一个变量,在属性变动时,进行修改。
如果按比例修改,我们可以按照官方的RPG进行修改,这里设置一下设置变量修改。
我们创建两个变量,在升级时设置将其设置为true

//将血量和蓝量填充满, 我们将设置变量
bFillHealth = true;
bFillMana = true;

覆写PostAttributeChange,它会在属性变动时触发

virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override; //属性变动后回调函数

在里面如果最大血量或者蓝量变动,并且变量为true,那么我们将蓝量或者血量填满

void URPGAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{Super::PostAttributeChange(Attribute, OldValue, NewValue);if(Attribute == GetMaxHealthAttribute() && bFillHealth){SetHealth(GetMaxHealth());bFillHealth = false;}if(Attribute == GetMaxManaAttribute() && bFillMana){SetMana(GetMaxMana());bFillMana = false;}
}

接下来介绍另一种方式,我是从动作RPG官方案例里得到的灵感,这种方式可以不但可以恢复满血,而且还可以实现如果血上限提高,当前血量按比例提高的效果。如果你制作的游戏有限时的血上限提升的效果,那么,可以使用这种方式。
我们知道设置血量和蓝量时,是当前属性没有设置,导致血量无法填满,但是我们在PostAttributeChange可以获得变动前和变动后的值,我们可以按比例增加血量和蓝量。
所以,我们还是按照之前的方式设置血量和蓝量

//将血量和蓝量填充满, 我们将设置变量
SetHealth(GetMaxHealth());
SetMana(GetMaxMana());

然后修改PostAttributeChange函数,按照比例增加它的血量

void URPGAttributeSet::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{Super::PostAttributeChange(Attribute, OldValue, NewValue);if (!FMath::IsNearlyEqual(OldValue, NewValue)) //判断值是否产生了变化{if (Attribute == GetMaxHealthAttribute()){const float CurrentValue = GetHealth(); //获取当前的实际值const float NewDelta = (OldValue > 0.f) ? (CurrentValue * NewValue / OldValue) - CurrentValue : NewValue; //获取到最大值变动后,按比列修改后的值SetHealth(NewDelta + CurrentValue);}else if (Attribute == GetMaxManaAttribute()){const float CurrentValue = GetMana(); //获取当前的实际值const float NewDelta = (OldValue > 0.f) ? (CurrentValue * NewValue / OldValue) - CurrentValue : NewValue; //获取到最大值变动后,按比列修改后的值SetMana(NewDelta + CurrentValue);}}
}

如果需要是短时间修改血上限,在血量上限buff到时间后,恢复默认时,将血量不再按比例下降,我们可以限制一下值变动的值即可

NewDelta = FMath::Max(NewDelta, 0.f);

修复被动技能应用报错

我们在被动技能应用的GE里面添加了多个SetbyCaller,但是每次只设置一个SetbyCaller,其它未设置的,就会出现报错
在这里插入图片描述
原因:主要是因为在GA里面应用GE时,由于没有默认值造成的,所以我们需要为其设置默认值,那么修改GA的蓝图,在创建时设置默认值即可。
我们设置一个标签,设置所有的需要设置默认值的标签,和GE对应
在这里插入图片描述

设置上当前GE会修改的内容
在这里插入图片描述
我们接着在GA里添加一个函数,用于创建GE,然后遍历数组设置默认值
在这里插入图片描述
在外部,我们还是通过接收事件去覆盖
在这里插入图片描述
这样就完成了报错的修改。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 前端获取blob文件格式的两种格式
  • 【Qt】QLCDNumber和QProgressBar
  • 基于PSO粒子群优化的GroupCNN分组卷积网络时间序列预测算法matlab仿真
  • JRT多列唯一取数据黑科技
  • Golang学习笔记20240725,Go语言基础语法
  • kafka rocketmq rabbitmq相同差异点
  • AI学习指南机器学习篇-SOM在数据聚类和可视化中的应用
  • Maven 的模块化开发示例
  • Spring循环依赖详解
  • MacOS解决安装pycurl的问题 no such file or directory: ‘/usr/lib/libcurl.@libext@‘
  • 基于DPU与SmartNic的云原生SDN解决方案
  • springboot 之 使用easyexcel导出数据到多个sheet,动态表头,自动计算列宽
  • Docker核心技术:Docker原理之Cgroups
  • 全年销售7亿块,巧克力企业如何通过相邻业务打造极致产品力?
  • LCD 横屏切换为竖屏-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板
  • [译] 理解数组在 PHP 内部的实现(给PHP开发者的PHP源码-第四部分)
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • Git同步原始仓库到Fork仓库中
  • iOS小技巧之UIImagePickerController实现头像选择
  • Laravel5.4 Queues队列学习
  • Node + FFmpeg 实现Canvas动画导出视频
  • opencv python Meanshift 和 Camshift
  • Python学习之路13-记分
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • Vim Clutch | 面向脚踏板编程……
  • Webpack 4 学习01(基础配置)
  • 猴子数据域名防封接口降低小说被封的风险
  • 使用权重正则化较少模型过拟合
  • 通过npm或yarn自动生成vue组件
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • ​探讨元宇宙和VR虚拟现实之间的区别​
  • #数学建模# 线性规划问题的Matlab求解
  • (js)循环条件满足时终止循环
  • (不用互三)AI绘画工具应该如何选择
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (算法)N皇后问题
  • (一) storm的集群安装与配置
  • ****三次握手和四次挥手
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • .Family_物联网
  • .NET CORE 第一节 创建基本的 asp.net core
  • .net MVC中使用angularJs刷新页面数据列表
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .net获取当前url各种属性(文件名、参数、域名 等)的方法
  • .NET开发者必备的11款免费工具
  • .net用HTML开发怎么调试,如何使用ASP.NET MVC在调试中查看控制器生成的html?
  • @JSONField或@JsonProperty注解使用
  • [ HTML + CSS + Javascript ] 复盘尝试制作 2048 小游戏时遇到的问题
  • [ Linux Audio 篇 ] 音频开发入门基础知识
  • [ACM] hdu 1201 18岁生日
  • [Angular 基础] - 指令(directives)
  • [C#7] 1.Tuples(元组)
  • [Flutter]WindowsPlatform上运行遇到的问题总结
  • [hdu 1247]Hat’s Words [Trie 图]
  • [HNOI2008]Cards