54. UE5 RPG 增加伤害类型

在正常的RPG游戏中,都存在一个类别就是属性伤害,比如,在一个游戏里面有一个火属性的技能,它造成的伤害就是火属性类型的,并且它还有可能有附加伤害,比如给予目标一个灼烧效果,每秒造成多少的火属性伤害。目标角色会有一个火属性伤害抵抗,根据百分比减少伤害。
比如英雄联盟技能伤害区分物理伤害和魔法伤害,有些技能能够造成两种伤害,比如卡特和永恩。
接下来,我们为游戏的伤害增加属性。

增加新的技能类

首先,我们基于自定义的技能类,创建一个派生的子类,专门用于处理具有伤害的技能。基类作为所有技能的基类,有可能技能时回血的,那么它的伤害相应的属性是使用不到的,属于浪费。
在这里插入图片描述
取名为RPGDamageGameplayAbility 专门用于处理伤害的技能的基类
在这里插入图片描述
记得将投掷技能释放物的类继承修改掉
在这里插入图片描述
将设置伤害的GE配置项从UProjectileSpell里面移到伤害类,并将基类上的伤害设置删除掉,为了能够让技能实现多种类型的伤害,我们通过一个Map来创建一个配置,可以在Map中设置多种伤害类型,并设置对应需造成的伤害。
在这里插入图片描述

增加对应的属性抗性标签

既然要增加伤害的类型,我们则需要增加对应的伤害类型以及类型抗性
打开我们之前在C++创建标签的文件RPGGameplayTags.h,先增加伤害类型

	FGameplayTag Damage; //伤害 标签
	FGameplayTag Damage_Fire; //火属性伤害 标签
	FGameplayTag Damage_Lightning; //雷属性伤害 标签
	FGameplayTag Damage_Arcane; //魔法伤害 标签
	FGameplayTag Damage_Physical; //物理伤害 标签

在cpp文件中,增加标签注册

	GameplayTags.Damage = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Damage"),
			FString("伤害标签")
			);
	
	GameplayTags.Damage_Fire = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Damage.Fire"),
			FString("火属性伤害")
		);
	
	GameplayTags.Damage_Lightning = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Damage.Lightning"),
			FString("雷属性伤害")
		);
	
	GameplayTags.Damage_Arcane = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Damage.Arcane"),
			FString("魔法伤害")
		);
	
	GameplayTags.Damage_Physical = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Damage.Physical"),
			FString("物理伤害")
		);

然后增加抗性标签

	//属性伤害抗性
	FGameplayTag Attributes_Resistance_Fire; //火属性伤害抵抗 标签
	FGameplayTag Attributes_Resistance_Lightning; //雷属性伤害抵抗 标签
	FGameplayTag Attributes_Resistance_Arcane; //魔法伤害抵抗 标签
	FGameplayTag Attributes_Resistance_Physical; //物理伤害抵抗 标签

并注册

	/* 属性抗性标签 */
	
	GameplayTags.Attributes_Resistance_Fire = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Resistance.Fire"),
			FString("火属性抗性")
			);
	
	GameplayTags.Attributes_Resistance_Lightning = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Resistance.Lightning"),
			FString("雷属性抗性")
			);
	
	GameplayTags.Attributes_Resistance_Arcane = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Resistance.Arcane"),
			FString("魔法伤害抗性")
			);
	
	GameplayTags.Attributes_Resistance_Physical = UGameplayTagsManager::Get()
		.AddNativeGameplayTag(
			FName("Resistance.Physical"),
			FString("物理伤害抗性")
		);

设置完成伤害类型和对应的抗性标签后,我们还需要将其对应起来,我们增加一个Map标签

TMap<FGameplayTag, FGameplayTag> DamageTypesToResistance; //属性伤害标签对应属性抵抗标签

并在注册完成后,将其一一对应起来

	/* 将属性和抗性标签对应 */
	GameplayTags.DamageTypesToResistance.Add(GameplayTags.Damage_Fire, GameplayTags.Attributes_Resistance_Fire);
	GameplayTags.DamageTypesToResistance.Add(GameplayTags.Damage_Lightning, GameplayTags.Attributes_Resistance_Lightning);
	GameplayTags.DamageTypesToResistance.Add(GameplayTags.Damage_Arcane, GameplayTags.Attributes_Resistance_Arcane);
	GameplayTags.DamageTypesToResistance.Add(GameplayTags.Damage_Physical, GameplayTags.Attributes_Resistance_Physical);

这样,就完成了标签的添加。

增加抗性属性

标签添加完成,我们还需要在AttributeSet里面增加对应的抗性属性,用于记录当前角色抗性值,如果想完整的查看,请查看此文章 20. UE5 RPG创建次级属性并实现设置,下面我们快速实现抗性属性的添加。

	/*
	 * 属性伤害抗性
	*/

	UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_FireResistance, Category="Resistance Attributes")
	FGameplayAttributeData FireResistance; // 火属性抗性
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, FireResistance);

	UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_LightningResistance, Category="Resistance Attributes")
	FGameplayAttributeData LightningResistance; // 雷属性抗性
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, LightningResistance);

	UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_ArcaneResistance, Category="Resistance Attributes")
	FGameplayAttributeData ArcaneResistance; // 魔法抗性
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, ArcaneResistance);

	UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_PhysicalResistance, Category="Resistance Attributes")
	FGameplayAttributeData PhysicalResistance; // 物理抗性
	ATTRIBUTE_ACCESSORS(URPGAttributeSet, PhysicalResistance);

然后增加对应的复制函数

	UFUNCTION()
	void OnRep_FireResistance(const FGameplayAttributeData& OldFireResistance) const;

	UFUNCTION()
	void OnRep_LightningResistance(const FGameplayAttributeData& OldLightningResistance) const;

	UFUNCTION()
	void OnRep_ArcaneResistance(const FGameplayAttributeData& OldArcaneResistance) const;

	UFUNCTION()
	void OnRep_PhysicalResistance(const FGameplayAttributeData& OldPhysicalResistance) const;

在cpp中实现复制函数

void URPGAttributeSet::OnRep_FireResistance(const FGameplayAttributeData& OldFireResistance) const
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, FireResistance, OldFireResistance);
}

void URPGAttributeSet::OnRep_LightningResistance(const FGameplayAttributeData& OldLightningResistance) const
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, LightningResistance, OldLightningResistance);
}

void URPGAttributeSet::OnRep_ArcaneResistance(const FGameplayAttributeData& OldArcaneResistance) const
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, ArcaneResistance, OldArcaneResistance);
}

void URPGAttributeSet::OnRep_PhysicalResistance(const FGameplayAttributeData& OldPhysicalResistance) const
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(URPGAttributeSet, PhysicalResistance, OldPhysicalResistance);
}

在同步时,设置同步

void URPGAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	...
	//抗性属性
	DOREPLIFETIME_CONDITION_NOTIFY(URPGAttributeSet, FireResistance, COND_None, REPNOTIFY_Always);
	DOREPLIFETIME_CONDITION_NOTIFY(URPGAttributeSet, LightningResistance, COND_None, REPNOTIFY_Always);
	DOREPLIFETIME_CONDITION_NOTIFY(URPGAttributeSet, ArcaneResistance, COND_None, REPNOTIFY_Always);
	DOREPLIFETIME_CONDITION_NOTIFY(URPGAttributeSet, PhysicalResistance, COND_None, REPNOTIFY_Always);
	...
}

在初始化时,将标签和属性对应

URPGAttributeSet::URPGAttributeSet()
{
	const FRPGGameplayTags& GameplayTags = FRPGGameplayTags::Get();

	//------------------------------------------------属性标签和属性关联------------------------------------------------

	...
	
	/*
	 * Resistance Attribute
	 */
	TagsToAttributes.Add(GameplayTags.Attributes_Resistance_Fire, GetFireResistanceAttribute);
	TagsToAttributes.Add(GameplayTags.Attributes_Resistance_Lightning, GetLightningResistanceAttribute);
	TagsToAttributes.Add(GameplayTags.Attributes_Resistance_Arcane, GetArcaneResistanceAttribute);
	TagsToAttributes.Add(GameplayTags.Attributes_Resistance_Physical, GetPhysicalResistanceAttribute);
}

这样就完成的属性标签的创建。

在UI上面显示抗性值

接下来,我们要在UI面板显示角色的对不同类型的伤害的抗性值。如果查看UI面板的具体实现,看我之前的文章:23. UE5 RPG制作属性面板(一)
首先,我们在设置角色次级属性的GE里面增加对应的计算
在这里插入图片描述
这里我设置的火属性抗性和雷属性抗性都是基于加的抗性点来实现
在这里插入图片描述
而魔法抗性和物理抗性则基于智力和护甲值来设置,这里我也是随便填写的数值用于测试,就不截全了。在实际项目中,这个计算通常需要策划的配合。
在这里插入图片描述
我们还需要在DA_AttributeInfo文件中增加对应在UI上显示的标签对应的名称和描述,它是被设置在BP_AttributeMenuController上面的,实现这个也是方便属性列表设置时,我们只需要在列表上面设置标签名称,函数会帮我们实现下面的工作。
在这里插入图片描述
然后就是打开UI用户空间,在属性列表增加四个子项,用于显示抗性数值。
在这里插入图片描述
通过我们之前的设置,现在只需要修改标签,其它的工作就不用做了
在这里插入图片描述
接着运行项目查看UI上面是否正确的显示了对应的属性
在这里插入图片描述

实现伤害类型的计算

首先我们要重新设置技能伤害,并设置类型,比如我们创建的火属性类型技能,伤害是火属性的,我们设置火属性伤害标签,并设置上对应的伤害
在这里插入图片描述
接下来,就需要去代码里将逻辑重新修改掉。
在生成发射物的技能类里,我们创建GE的地方,将属性的伤害通过SetByCaller方式设置给GE
我们可以遍历将在技能列表上设置的所有的属性类型和伤害设置过去。

//设置技能伤害 SetByCaller获取 通过Tag
const FRPGGameplayTags GameplayTags = FRPGGameplayTags::Get(); //获取标签单例
for(auto& Pair : DamageTypes)
{
	const float ScaledDamage = Pair.Value.GetValueAtLevel(GetAbilityLevel()); //根据等级获取技能伤害
	UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(SpecHandle, Pair.Key, ScaledDamage);
}

设置属性伤害就算完成了,接下来我们转到ExecCalc_Damage.cpp文件里,增加对目标的属性获取
首先在静态实例类里面增加对抗性属性设置

struct SDamageStatics
{
	...
	
	DECLARE_ATTRIBUTE_CAPTUREDEF(FireResistance);
	DECLARE_ATTRIBUTE_CAPTUREDEF(LightningResistance);
	DECLARE_ATTRIBUTE_CAPTUREDEF(ArcaneResistance);
	DECLARE_ATTRIBUTE_CAPTUREDEF(PhysicalResistance);
	
	SDamageStatics()
	{
		//参数:1.属性集 2.属性名 3.目标还是自身 4.是否设置快照(true为创建时获取,false为应用时获取)
		...
		
		DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, FireResistance, Target, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, LightningResistance, Target, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, ArcaneResistance, Target, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(URPGAttributeSet, PhysicalResistance, Target, false);
	}
};

然后在构造时,将参数添加到捕获列表中

UExecCalc_Damage::UExecCalc_Damage()
{
	//添加监听
	...
	
	RelevantAttributesToCapture.Add(DamageStatics().FireResistanceDef);
	RelevantAttributesToCapture.Add(DamageStatics().LightningResistanceDef);
	RelevantAttributesToCapture.Add(DamageStatics().ArcaneResistanceDef);
	RelevantAttributesToCapture.Add(DamageStatics().PhysicalResistanceDef);
}

我本来想把Map写到静态实例类里,发现在代码的运行过程中,单例实例创建时,标签还没有设置成功,这就导致在单例实例里面无法获取到标签,所以,我转到了Execute_Implementation函数中,创建Map的原因是,我们这样就不需要在for循环中进行二次遍历了,只需要通过Map获取对应的Value即可。

	//存储标签和属性快照对应的Map
	TMap<FGameplayTag, FGameplayEffectAttributeCaptureDefinition> TagsToCaptureDefs;
	//添加标签和属性快照对应的数据
	TagsToCaptureDefs.Add(GameplayTags.Attributes_Resistance_Fire, DamageStatics().FireResistanceDef);
	TagsToCaptureDefs.Add(GameplayTags.Attributes_Resistance_Lightning, DamageStatics().LightningResistanceDef);
	TagsToCaptureDefs.Add(GameplayTags.Attributes_Resistance_Arcane, DamageStatics().ArcaneResistanceDef);
	TagsToCaptureDefs.Add(GameplayTags.Attributes_Resistance_Physical, DamageStatics().PhysicalResistanceDef);

由于我们在函数内无法获取技能设置了几种伤害类型和伤害,所以,我们要把所有的类型都遍历到,所以,我们从标签单例中获取到伤害类型和抵抗类型的对照标签Map,并将其遍历

for(const TTuple<FGameplayTag, FGameplayTag>& Pair : GameplayTags.DamageTypesToResistance)

然后通过抗性类型的标签去获取快照引用

const FGameplayTag DamageType = Pair.Key;
const FGameplayTag ResistanceType = Pair.Value;
//检查对应的属性快照是否设置,防止报错
checkf(TagsToCaptureDefs.Contains(ResistanceType), TEXT("在ExecCalc_Damage中,无法获取到Tag[%s]对应的属性快照"), *ResistanceType.ToString());
//通过抗性标签获取到属性快照
const FGameplayEffectAttributeCaptureDefinition CaptureDef = TagsToCaptureDefs[ResistanceType];

获取到快照了,我们可以通过快照去获取目标的抗性值,这里限制到0-100是防止预算出错,超一百了,会出现攻击负值的情况,应用到角色那就是加血了。

//获取抗性值
float Resistance = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(CaptureDef, EvaluationParameters, Resistance);
Resistance = FMath::Clamp(Resistance, 0.f, 100.f); //将抗住限制在0到100

接着通过SetByCaller获取到伤害类型的伤害,如果没有设置,获取的值将为0

		//通过Tag获取对应伤害类型的值,如果没设置SetByCaller将获取0
		float DamageTypeValue = Spec.GetSetByCallerMagnitude(DamageType);

最后,计算出能够造成的伤害,并将伤害叠加做后续计算

		//通过抗性计算出能够对角色造成的伤害值
		DamageTypeValue *= (100.f - Resistance) / 100.f;
		//将每种属性伤害值合并进行后续计算
		Damage += DamageTypeValue;

计算完成以后,我们可以进行debug测试,这是火球术,火属性伤害,本来能造成的伤害为5,受到抵抗后,伤害减少了百分之11.5,最总火球术造成的伤害为4.425
在这里插入图片描述

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-05-26 00:50:45       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-26 00:50:45       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-26 00:50:45       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-26 00:50:45       18 阅读

热门阅读

  1. PHP开发安全:专家级代码审计策略与方法

    2024-05-26 00:50:45       10 阅读
  2. Flutter 中的 ExpandIcon 小部件:全面指南

    2024-05-26 00:50:45       10 阅读
  3. Python项目开发实战:五子棋游戏(案例教程)

    2024-05-26 00:50:45       10 阅读
  4. QGraphicsView中鼠标位置图像缩放时不变

    2024-05-26 00:50:45       11 阅读
  5. 【Spark】加大hive表在HDFS存的每个文件的大小

    2024-05-26 00:50:45       9 阅读
  6. Python案例题目,入门小白题

    2024-05-26 00:50:45       11 阅读
  7. HTML5 Canvas图形绘制技术应用

    2024-05-26 00:50:45       8 阅读
  8. 链表相交-力扣

    2024-05-26 00:50:45       10 阅读
  9. RabbitMQ01-liunx下安装及用户权限分配

    2024-05-26 00:50:45       8 阅读