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);

查看效果,没问题

相关推荐
心怀梦想的咸鱼19 小时前
UE5 第一人称射击项目学习(二)
学习·ue5
暮志未晚Webgl19 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
心怀梦想的咸鱼19 小时前
UE5 第一人称射击项目学习(完结)
学习·ue5
暮志未晚Webgl2 天前
110. UE5 GAS RPG 实现玩家角色数据存档
java·前端·ue5
Zhichao_973 天前
【UE5】Slider控件样式
ue5
流行易逝3 天前
23.UE5删除存档
ue5
心怀梦想的咸鱼3 天前
UE5 第一人称射击项目学习(三)
学习·ue5
流行易逝3 天前
22.UE5控件切换器,存档列表,
ue5
[小瓜]4 天前
高级AI记录笔记(三)
人工智能·笔记·学习·ue5·虚幻
心怀梦想的咸鱼4 天前
UE5 5.1.1创建C++项目,显示error C4668和error C4067
ue5