45. UE5 RPG 使用元属性(Meta Attributes)以及使用Set by Caller修改伤害

在RPG游戏中,我们是不会直接修改生命值的属性,是因为在修改角色属性时,需要获取角色的属性并进行复杂的计算,所以,我们正常情况下使用元属性(Meta Attributes)作为计算的中间的媒。在服务器上先将属性计算到元属性上面,然后再通过元属性的数值去修改实际属性。

那元属性和普通的属性有什么区别?

普通的Gameplay Attribute:它是经常被复制的,在服务器上面修改,然后复制到每个客户端上面。

元属性:它不会被复制,只做为一个临时缓冲占位符,只在服务器上面计算,然后使用元属性修改实际需求修改的属性,这样就可以避免每个端需要复杂的计算。

比如,我们将实现一个名为IncomingDamage的元属性,然后在属性集里的PostGameplayEffectExecute获取角色属性(是否被格挡,减伤等等)来计算出对角色造成的最终伤害,然后将最终伤害去修改角色的血量值,而这个值再复制给客户端,将大大减少计算量。

所以,这些计算伤害的元属性将有传入的GE去计算,然后属性集去执行所需的计算,并播放对应的效果(比如显示不同颜色的伤害数字)并对目标应用buff(如果有)。

接下来,我们将从一个简单的版本开始实现,然后逐渐让功能符合游戏需求。

创建元属性

在测试使用了GE里面,我们是直接修改了血量值,我们需要创建一个元属性作为中间媒介。

首先,我们需要先在属性集(AttributeAet)里添加一个属性,用于作为临时占位符使用。原属性不需要赋值,所以不需要赋值函数,但是属性对应的宏还是需要的生成获取和设置的函数使用。

cpp 复制代码
	UPROPERTY(BlueprintReadOnly, Category="Meta Attributes")
	FGameplayAttributeData IncomingDamage; //处理传入的伤害
	ATTRIBUTE_ACCESSORS(UAttributeSetBase, IncomingDamage);

接下来,在PostGameplayEffectExecute函数里面,查看修改的是否为元属性

cpp 复制代码
if(Data.EvaluatedData.Attribute == GetIncomingDamageAttribute())

然后我们获取到元属性的值备用,并将属性集上的值设置为0,等待下一次设置。

cpp 复制代码
const float LocalIncomingDamage = GetIncomingDamage();
SetIncomingDamage(0.f);

接下来,判断值是否大于0,如果大于0则传入了伤害

cpp 复制代码
if(LocalIncomingDamage > 0.f)

然后,我们先实现最基础的修改血量,作为基础模板,并且还判断角色是否存活,条件就是血量不为0则为存活,这样基础测试代码就实现了。

cpp 复制代码
	if(Data.EvaluatedData.Attribute == GetIncomingDamageAttribute())
	{
		const float LocalIncomingDamage = GetIncomingDamage();
		SetIncomingDamage(0.f);
		if(LocalIncomingDamage > 0.f)
		{
			const float NewHealth = GetHealth() - LocalIncomingDamage;
			SetHealth(FMath::Clamp(NewHealth, 0.f, GetMaxHealth()));

			const bool bFatal = NewHealth <= 0.f; //血量小于等于0时,角色将会死亡
		}
	}

编译代码,打开UE,在GE里面修改的为IncomingDamage,并设置为25

就可以运行测试效果

使用Set by Caller来实现伤害数值的传递

制作到这里,朋友们会有疑问,我们实现GE里面的伤害都是通过固定的数值来造成的伤害,但是如果我们需要通过角色的属性计算呢,该如何实现?

这就需要将属性值修改的Set by Caller去实现

接下来我们将一步步实现这个。

要使用这种方式去设置,我们需要使用一个标签作为Key,在我们之前c++里添加一个标签

cpp 复制代码
FGameplayTag Damage; //伤害 标签

然后初始化,添加到标签管理器,并缓存下来变量

cpp 复制代码
	GameplayTags.Damage = UGameplayTagsManager::Get()
	.AddNativeGameplayTag(
		FName("Damage"),
		FString("伤害标签")
		);

创建完成这个,我们需要在之前的火球术技能类里面,在生成GESpec时,使用标签设置。

首先获取到标签的单例

cpp 复制代码
const FMyGameplayTags GameplayTags = FMyGameplayTags::Get(); //获取标签单例

然后使用函数库的方法设置,这里有两种方式,一种是FName,另一种是使用标签,对应在GE上获取的两种,由于Name类型容易写错,所以个人推荐使用标签。最后的值则是我们需要对目标造成的伤害,这里方便测试,我们直接写了固定数值,如果这个数值能够起作用,证明我们接下来可以在代码里面计算当前技能造成的伤害。

cpp 复制代码
//UAbilitySystemBlueprintLibrary::AssignSetByCallerMagnitude() //使用DataName设置
UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, GameplayTags.Damage, 50.f);

然后编译代码,打开GE,将修改数值类型设置为Set by Caller,并在下面设置Tag为Damage,这样,GE将从标签获取对应的数值

在技能上通过技能等级设置伤害

我们实现了通过代码里面去设置技能造成的伤害,接下来,我们将实现增加配置项在技能上实现对伤害的设置,并且还使用到了曲线表格去编辑伤害。

首先我们在技能基类上面添加一个属性,虽然不是所有的属性都会用到,但是它占用的内存不大,所以我们直接写到基类,需要就用。

cpp 复制代码
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")
	FScalableFloat Damage;

编译代码打开UE,可以查看到属性面板

所以,我们需要设置它的曲线表格来实现通过等级设置技能伤害,首先创建一个JSON文件,用来设置属性

然后导入到UE,生成曲线表格

设置为曲线表格

然后可以稍微修改一下表格,让曲线更陡一些

然后将属性缩放设置为1,选择我们创建的表格,然后设置好使用哪一行

有了表格以后,我们就需要实现如何通过属性获取到数值。如果你需要获取到整数类型的,我们可以通过Damage.AsInteger(GetAbilityLevel())来获取从表格转换的整数数值。

这里我们使用Damage.GetValueAtLevel获取浮点数来设置,并且将数值打印到窗口

cpp 复制代码
//创建一个GE的实例,并设置给投射物
const UAbilitySystemComponent* SourceASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetAvatarActorFromActorInfo());
const FGameplayEffectSpecHandle SpecHandle = SourceASC->MakeOutgoingSpec(DamageEffectClass, GetAbilityLevel(), SourceASC->MakeEffectContext());

const FMyGameplayTags GameplayTags = FMyGameplayTags::Get(); //获取标签单例
const float ScaledDamage = Damage.GetValueAtLevel(GetAbilityLevel()); //根据等级获取技能伤害
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, FString::Printf(TEXT("火球术伤害:%f"), ScaledDamage));
//UAbilitySystemBlueprintLibrary::AssignSetByCallerMagnitude() //使用DataName设置
UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, GameplayTags.Damage, ScaledDamage);

Projectile->DamageEffectHandle = SpecHandle;

编译并运行项目测试

在技能等级没有修改时,值为5,没问题

由于我们现在还没有实现设置技能等级,所以这里在代码里修改查看20级的数值,是否为我们设置的伤害值

cpp 复制代码
const float ScaledDamage = Damage.GetValueAtLevel(GetAbilityLevel() + 19);

查看效果,没问题

相关推荐
星火撩猿11 小时前
常见游戏引擎介绍与对比
unity·ue5·游戏引擎·godot
清流君13 小时前
【MySQL】数据库 Navicat 可视化工具与 MySQL 命令行基本操作
数据库·人工智能·笔记·mysql·ue5·数字孪生
Involuter1 天前
UE5 Assimp 自用
ue5
电子云与长程纠缠1 天前
Unreal Niagara制作SubUV贴图翻页动画
学习·ue5·编辑器·贴图·niagara
子燕若水1 天前
“Daz to Unreal”将 G8 角色(包括表情)从 daz3d 导入到 UE5。在 UE5 中,我发现使用某个表情并与闭眼混合后,上眼睑出现了问题
3d·ue5
半天法师2 天前
UE5.2+VarjoXR3,Lumen、GI、Nanite无效的两种解决方案
ue5·xr·vr
ue星空2 天前
UE5摄像机画面没有填充满屏幕有黑边
ue5
李詹3 天前
游戏开发核心技术解析——从引擎架构到攻防体系的完整技能树
架构·ue5·游戏引擎·游戏程序·3dsmax·虚幻
子燕若水3 天前
UE5的 Modify Curve 蓝图节点
ue5
人宅4 天前
UE5有些场景的导航生成失败解决方法
ue5