UE5多人MOBA+GAS 番外篇:同时造成多种类型伤害

发布于:2025-08-01 ⋅ 阅读:(20) ⋅ 点赞:(0)


重构伤害类型

CGameplayAbilityTypes中重构伤害类型

UENUM(BlueprintType)
enum class EDamageType : uint8
{
	PhysicalDamage					UMETA(DisplayName="物理伤害"),  // 物理伤害
	MagicDamage						UMETA(DisplayName="魔法伤害"),  // 魔法伤害
	TrueDamage						UMETA(DisplayName="真实伤害"),  // 真实伤害
};

// 
USTRUCT(BlueprintType)
struct FDamageDefinition
{
	GENERATED_BODY()
public:
	FDamageDefinition();
	// 基础伤害
	UPROPERTY(EditAnywhere)
	FScalableFloat BaseDamage;
	// 属性的百分比伤害加成
	UPROPERTY(EditAnywhere)
	TMap<FGameplayAttribute, float> AttributeDamageModifiers;
};
// 伤害效果定义
USTRUCT(BlueprintType)
struct FGenericDamageEffectDef
{
	GENERATED_BODY()
public:
	FGenericDamageEffectDef();

	// 伤害类型
	UPROPERTY(EditAnywhere)
	TSubclassOf<UGameplayEffect> DamageEffect;
	
	// 伤害类型
	UPROPERTY(EditAnywhere)
	TMap<EDamageType,FDamageDefinition> DamageTypeDefinitions;

	// 力的大小
	UPROPERTY(EditAnywhere)
	FVector PushVelocity;
};
FDamageDefinition::FDamageDefinition()
	: BaseDamage{0.f}
{
}

FGenericDamageEffectDef::FGenericDamageEffectDef()
	:DamageEffect{nullptr},
	PushVelocity{0.f}
{
}

重构伤害应用

修改一下CGameplayAbility中的伤害配置

void UCGameplayAbility::ApplyDamage(AActor* TargetActor,const FGenericDamageEffectDef& Damage, int Level)
{
	const UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo();
	AActor* AvatarActor				   = GetAvatarActorFromActorInfo();
	// 创建效果上下文, 设置能力 、源对象 和 施加者
	FGameplayEffectContextHandle ContextHandle = ASC->MakeEffectContext();
	ContextHandle.SetAbility(this);
	ContextHandle.AddSourceObject(AvatarActor);
	ContextHandle.AddInstigator(AvatarActor, AvatarActor);
	// 配置伤害,这个也可以拆开来
	MakeDamage(Damage, Level);
	// 创建效果Spec句柄,指定效果类、能力等级和上下文
	FGameplayEffectSpecHandle EffectSpecHandle = ASC->MakeOutgoingSpec(Damage.DamageEffect, Level, ContextHandle);
	// 在目标上应用游戏效果规范
	ApplyGameplayEffectSpecToTarget(GetCurrentAbilitySpecHandle(),
									GetCurrentActorInfo(),
									GetCurrentActivationInfo(),
									EffectSpecHandle,
									UAbilitySystemBlueprintLibrary::AbilityTargetDataFromActor(TargetActor));
}

void UCGameplayAbility::MakeDamage(const FGenericDamageEffectDef& Damage, int Level)
{
	// 通通置为0
	float BaseAttackDamage = 0.f;
	float BaseMagicDamage = 0.f;
	float BaseTrueDamage = 0.f;
	for (const auto& TypePair : Damage.DamageTypeDefinitions)
	{
		float TotalModifier = TypePair.Value.BaseDamage.GetValueAtLevel(Level);
		for (const auto& Modifier : TypePair.Value.AttributeDamageModifiers)
		{
			bool bFound ;
			float AttributeValue = GetAbilitySystemComponentFromActorInfo()->GetGameplayAttributeValue(Modifier.Key, bFound);
			if (bFound)
			{
				TotalModifier += AttributeValue * Modifier.Value / 100.0f;
			}
		}
		switch (TypePair.Key)
		{
			case EDamageType::PhysicalDamage :
				BaseAttackDamage = TotalModifier;
				break;
			case EDamageType::MagicDamage :
				BaseMagicDamage = TotalModifier;
				break;
			case EDamageType::TrueDamage :
				BaseTrueDamage = TotalModifier;
				break;
			default:
				break;
		}
	}
	GetAbilitySystemComponentFromActorInfo()->ApplyModToAttribute(UCAttributeSet::GetBaseAttackDamageAttribute(), EGameplayModOp::Override, BaseAttackDamage);
	GetAbilitySystemComponentFromActorInfo()->ApplyModToAttribute(UCAttributeSet::GetBaseMagicDamageAttribute(), EGameplayModOp::Override, BaseMagicDamage);
	GetAbilitySystemComponentFromActorInfo()->ApplyModToAttribute(UCAttributeSet::GetBaseTrueDamageAttribute(), EGameplayModOp::Override, BaseTrueDamage);
}

添加新的属性以及接收属性的修改

修改一下属性CAttributeSet,为其添加一些新的东西

USTRUCT()
struct FEffectProperties
{
	GENERATED_BODY()

	FEffectProperties(){}

	UPROPERTY()
	FGameplayEffectContextHandle EffectContextHandle;
	
	UPROPERTY()
	UAbilitySystemComponent* SourceASC = nullptr;

	UPROPERTY()
	AActor* SourceAvatarActor = nullptr;

	UPROPERTY()
	AController* SourceController = nullptr;

	UPROPERTY()
	ACharacter* SourceCharacter = nullptr;

	UPROPERTY()
	UAbilitySystemComponent* TargetASC = nullptr;

	UPROPERTY()
	AActor* TargetAvatarActor = nullptr;

	UPROPERTY()
	AController* TargetController = nullptr;

	UPROPERTY()
	ACharacter* TargetCharacter = nullptr;
	
};
	// 传值物理的基础伤害
	UPROPERTY(ReplicatedUsing = OnRep_BaseAttackDamage)
	FGameplayAttributeData BaseAttackDamage;
	ATTRIBUTE_ACCESSORS(UCAttributeSet, BaseAttackDamage)
	// 魔法的基础伤害
	UPROPERTY(ReplicatedUsing = OnRep_BaseMagicDamage)
	FGameplayAttributeData BaseMagicDamage;
	ATTRIBUTE_ACCESSORS(UCAttributeSet, BaseMagicDamage)
	// 真伤的基础伤害
	UPROPERTY(ReplicatedUsing = OnRep_BaseTrueDamage)
	FGameplayAttributeData BaseTrueDamage;
	ATTRIBUTE_ACCESSORS(UCAttributeSet, BaseTrueDamage)
	
	UFUNCTION()
	void OnRep_BaseAttackDamage(const FGameplayAttributeData& OldBaseAttackDamage);
	UFUNCTION()
	void OnRep_BaseMagicDamage(const FGameplayAttributeData& OldBaseMagicDamage);
	UFUNCTION()
	void OnRep_BaseTrueDamage(const FGameplayAttributeData& OldBaseTrueDamage);

	// 设置效果属性
	void SetEffectProperties(const FGameplayEffectModCallbackData& Data, FEffectProperties& Props) const;
	// 伤害处理函数
	void Damage(const FEffectProperties& Props, EDamageType Type, const float Damage);

网络复制中添加一下新的变量
在这里插入图片描述

void UCAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
	FEffectProperties Props;
	SetEffectProperties(Data, Props);
	if (Data.EvaluatedData.Attribute == GetHealthAttribute())
	{
		SetHealth(FMath::Clamp(GetHealth(), 0, GetMaxHealth()));
		SetCachedHealthPercent(GetHealth()/GetMaxHealth());
	}
	if (Data.EvaluatedData.Attribute == GetManaAttribute())
	{
		SetMana(FMath::Clamp(GetMana(), 0, GetMaxMana()));
		SetCachedManaPercent(GetMana()/GetMaxMana());
	}
	
	// 物理伤害
	if (Data.EvaluatedData.Attribute == GetAttackDamageAttribute())
	{
		float NewDamage = GetAttackDamage();
		SetAttackDamage(0.f);
		if (NewDamage > 0.f)
		{
			Damage(Props, EDamageType::PhysicalDamage, NewDamage);
		}
	}
	
	// 魔法伤害
	if (Data.EvaluatedData.Attribute == GetMagicDamageAttribute())
	{
		float NewDamage = GetMagicDamage();
		SetMagicDamage(0.f);
		if (NewDamage > 0.f)
		{
			UE_LOG(LogTemp, Warning, TEXT("魔法伤害: %f"), NewDamage)
			Damage(Props,EDamageType::MagicDamage, NewDamage);
		}
	}
	
	// 真实伤害
	if (Data.EvaluatedData.Attribute == GetTrueDamageAttribute())
	{
		float NewDamage = GetTrueDamage();
		SetTrueDamage(0.f);
		if (NewDamage > 0.f)
		{
			UE_LOG(LogTemp, Warning, TEXT("真实伤害: %f"), NewDamage)
			Damage(Props,EDamageType::TrueDamage, NewDamage);
		}
	}
}

void UCAttributeSet::OnRep_BaseAttackDamage(const FGameplayAttributeData& OldBaseAttackDamage)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, BaseAttackDamage, OldBaseAttackDamage);
}

void UCAttributeSet::OnRep_BaseMagicDamage(const FGameplayAttributeData& OldBaseMagicDamage)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, BaseMagicDamage, OldBaseMagicDamage);
}

void UCAttributeSet::OnRep_BaseTrueDamage(const FGameplayAttributeData& OldBaseTrueDamage)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, BaseTrueDamage, OldBaseTrueDamage);
}

void UCAttributeSet::SetEffectProperties(const FGameplayEffectModCallbackData& Data, FEffectProperties& Props) const
{
	//Source 效果的所有者   Target 效果应用的目标
	Props.EffectContextHandle = Data.EffectSpec.GetContext();
	Props.SourceASC = Props.EffectContextHandle.GetOriginalInstigatorAbilitySystemComponent();

	//获取效果所有者的相关对象
	if (IsValid(Props.SourceASC) && Props.SourceASC->AbilityActorInfo.IsValid() && Props.SourceASC->AbilityActorInfo->AvatarActor.IsValid())
	{
		Props.SourceAvatarActor = Props.SourceASC->AbilityActorInfo->AvatarActor.Get();
		Props.SourceController = Props.SourceASC->AbilityActorInfo->PlayerController.Get();
		if (Props.SourceAvatarActor != nullptr && Props.SourceController == nullptr)
		{
			if (const APawn* Pawn = Cast<APawn>(Props.SourceAvatarActor))
			{
				Props.SourceController = Pawn->Controller;
			}
		}
		if (Props.SourceController)
		{
			Props.SourceCharacter = Cast<ACharacter>(Props.SourceController->GetPawn());
		}
	}
	if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid())
	{
		Props.TargetAvatarActor = Data.Target.AbilityActorInfo->AvatarActor.Get();
		Props.TargetController = Data.Target.AbilityActorInfo->PlayerController.Get();
		Props.TargetCharacter = Cast<ACharacter>(Props.TargetAvatarActor);
		Props.TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Props.TargetAvatarActor);
	}
}

void UCAttributeSet::Damage(const FEffectProperties& Props, EDamageType Type, const float Damage)
{
	bool bCriticalHit = false;
	float NewDamage = Damage;
	if (Props.SourceASC)
	{
		bool bFound = false;
		const float EffectiveCriticalHitChance = Props.SourceASC->GetGameplayAttributeValue(UCHeroAttributeSet::GetCriticalStrikeChanceAttribute(), bFound);
		if (bFound)
		{
			bFound = false;
			bCriticalHit = FMath::RandRange(1, 100) < EffectiveCriticalHitChance;
			if (bCriticalHit)
			{
				const float CriticalStrikeDamage = Props.SourceASC->GetGameplayAttributeValue(UCHeroAttributeSet::GetCriticalStrikeDamageAttribute(), bFound);
				if (bFound)
				{
					NewDamage *= (1.f + CriticalStrikeDamage / 100.f);
					// UE_LOG(LogTemp, Warning, TEXT("暴击"))
				}
			}
		}
	}
			
	const float NewHealth = GetHealth() - NewDamage;
	SetHealth(FMath::Clamp(NewHealth, 0.f, GetMaxHealth()));
	// UE_LOG(LogTemp, Log, TEXT("NewDamage: %f"), NewDamage)
			
	// 如果生命小于等于0触发死亡
	if (NewHealth <= 0.f)
	{
		// 触发死亡被动
		OnDeadAbility(Props);
	}

	ShowFloatingText(Props,NewDamage, bCriticalHit, Type);
}

我还修改了一下死亡的传递值以及伤害的发送,为此,游戏控制器也要修改

	static void ShowFloatingText(const FEffectProperties& Props, float Damage, bool IsCriticalHitE, EDamageType Type);

	// 用于激活角色死亡被动的函数
	void OnDeadAbility(const FEffectProperties& Props);
void UCAttributeSet::ShowFloatingText(const FEffectProperties& Props, const float Damage, bool IsCriticalHit, EDamageType Type)
{
	// 从技能释放者身上获取PC并显示伤害数字
	if(ACPlayerController* PC = Cast<ACPlayerController>(Props.SourceCharacter->Controller))
	{
		PC->ShowDamageNumber(Damage, Props.TargetCharacter, IsCriticalHit, Type); //调用显示伤害数字
	}
	// 从目标身上获取PC并显示伤害数字
	if(ACPlayerController* PC = Cast<ACPlayerController>(Props.TargetCharacter->Controller))
	{
		PC->ShowDamageNumber(Damage, Props.TargetCharacter, IsCriticalHit, Type); //调用显示伤害数字
	}
}

void UCAttributeSet::OnDeadAbility(const FEffectProperties& Props)
{
	FGameplayEventData DeadAbilityEventData;
	if (Props.SourceAvatarActor)
	{
		// UE_LOG(LogTemp, Warning, TEXT("Dead:%s"), *GetOwningActor()->GetName())
		DeadAbilityEventData.Target = Props.SourceAvatarActor;
	}
	
	UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(GetOwningActor(), 
		TGameplayTags::Stats_Dead, 
		DeadAbilityEventData);
}

到游戏控制器CPlayerController中修改一下输出数字的函数

	UFUNCTION(Client, Reliable)
	void ShowDamageNumber(float DamageAmount, ACharacter* TargetCharacter, bool bCriticalHit, EDamageType Type);
void ACPlayerController::ShowDamageNumber_Implementation(float DamageAmount, ACharacter* TargetCharacter, bool bCriticalHit, EDamageType Type)
{
	if (!IsValid(TargetCharacter) || !NumberPopComponentClass || !IsLocalController())
		return;

	// 获取目标Actor上的现有组件
	UNumberPopComponent_NiagaraText* DamageText = TargetCharacter->GetComponentByClass<UNumberPopComponent_NiagaraText>();

	// 不存在则创建并附加
	if (!DamageText)
	{
		DamageText = NewObject<UNumberPopComponent_NiagaraText>(TargetCharacter, NumberPopComponentClass);
		if (!DamageText)
		{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
			UE_LOG(LogTemp, Error, TEXT("Niagara组件创建失败,内存不足或配置错误"));
#endif
			return;
		}
        
		DamageText->RegisterComponent(); // 注册组件
	}

	// TODO:添加各种伤害类型的伤害数字特效
	// 设置显示参数
	FNumberPopRequest NumberPopRequest;
	NumberPopRequest.WorldLocation = TargetCharacter->GetActorLocation() + FVector(0, 0, 200);
	NumberPopRequest.bIsCriticalDamage = bCriticalHit;
	NumberPopRequest.NumberToDisplay = DamageAmount;
    
	DamageText->AddNumberPop(NumberPopRequest);
}

修改ECC让其适用于多种伤害类型

ECC_AttackDamage中进行一些伤害计算的调整

// 幻雨喜欢小猫咪


#include "GAS/Executions/ECC_AttackDamage.h"

#include "GAS/Core/CAttributeSet.h"
#include "GAS/Core/CHeroAttributeSet.h"

struct FDamageStatics
{
	// FGameplayEffectAttributeCaptureDefinition
	// 物理基础伤害
	DECLARE_ATTRIBUTE_CAPTUREDEF(BaseAttackDamage);
	// 魔法基础伤害
	DECLARE_ATTRIBUTE_CAPTUREDEF(BaseMagicDamage);
	// 真实基础伤害
	DECLARE_ATTRIBUTE_CAPTUREDEF(BaseTrueDamage);
	
	// 护甲穿透
	DECLARE_ATTRIBUTE_CAPTUREDEF(ArmorPenetration);
	// 护甲穿透百分比
	DECLARE_ATTRIBUTE_CAPTUREDEF(ArmorPenetrationPercent);

	// 法术穿透
	DECLARE_ATTRIBUTE_CAPTUREDEF(MagicPenetration);
	// 法术穿透百分比
	DECLARE_ATTRIBUTE_CAPTUREDEF(MagicPenetrationPercent);
	
	// 伤害加深
	DECLARE_ATTRIBUTE_CAPTUREDEF(DamageAmplification);
	// 敌方的物理防御
	DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
	// 敌方的法术抗性
	DECLARE_ATTRIBUTE_CAPTUREDEF(MagicResistance);
	// 伤害减免
	DECLARE_ATTRIBUTE_CAPTUREDEF(DamageReduction);

	FDamageStatics()
	{
	 	// 参数:1.属性集 2.属性名 3.目标还是自身 4.是否设置快照(true为创建时获取,false为应用时获取)
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCAttributeSet, BaseAttackDamage, Source, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCAttributeSet, BaseMagicDamage, Source, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCAttributeSet, BaseTrueDamage, Source, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCHeroAttributeSet, ArmorPenetration, Source, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCHeroAttributeSet, ArmorPenetrationPercent, Source, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCHeroAttributeSet, MagicPenetration, Source, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCHeroAttributeSet, MagicPenetrationPercent, Source, false);

		DEFINE_ATTRIBUTE_CAPTUREDEF(UCHeroAttributeSet, DamageAmplification, Source, false);
		
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCAttributeSet, Armor, Target, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCAttributeSet, MagicResistance, Target, false);
		DEFINE_ATTRIBUTE_CAPTUREDEF(UCHeroAttributeSet, DamageReduction, Target, false);
	 }
};
// 静态数据访问函数(单例模式)
static FDamageStatics& DamageStatics()
{
	static FDamageStatics Statics;
	return Statics;
}

UECC_AttackDamage::UECC_AttackDamage()
{
	// 将属性添加到捕获列表中
	RelevantAttributesToCapture.Add(DamageStatics().BaseAttackDamageDef);
	RelevantAttributesToCapture.Add(DamageStatics().BaseMagicDamageDef);
	RelevantAttributesToCapture.Add(DamageStatics().BaseTrueDamageDef);
	
	RelevantAttributesToCapture.Add(DamageStatics().ArmorPenetrationDef);
	RelevantAttributesToCapture.Add(DamageStatics().ArmorPenetrationPercentDef);
	RelevantAttributesToCapture.Add(DamageStatics().MagicPenetrationDef);
	RelevantAttributesToCapture.Add(DamageStatics().MagicPenetrationPercentDef);

	RelevantAttributesToCapture.Add(DamageStatics().DamageAmplificationDef);
	
	RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
	RelevantAttributesToCapture.Add(DamageStatics().MagicResistanceDef);
	RelevantAttributesToCapture.Add(DamageStatics().DamageReductionDef);
}

void UECC_AttackDamage::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams,
	FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
	// 获取游戏效果规范和上下文
	const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
	FGameplayEffectContextHandle EffectContextHandle = Spec.GetContext();

	// 获取来源和目标标签
	const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
	const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();

	// 初始化评估参数
	FAggregatorEvaluateParameters EvaluateParameters;
	EvaluateParameters.SourceTags = SourceTags;
	EvaluateParameters.TargetTags = TargetTags;

	// 获取伤害加深
	float DamageAmp = 0.0f;
	ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DamageAmplificationDef, EvaluateParameters, DamageAmp);
	// 获取敌方的伤害减免
	float DamageReduction = 0.0f;
	ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().DamageReductionDef, EvaluateParameters, DamageReduction);
	
	// 计算基础物理伤害值
	float BaseAttackDamage = 0.0f;
	ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
		DamageStatics().BaseAttackDamageDef, 
		EvaluateParameters, 
		BaseAttackDamage
	);

	// 物理伤害的处理
	if (BaseAttackDamage > 0.0f)
	{
		// 获取护甲穿透百分比
		float ArmorPenetrationPercent = 0.0f;
		ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorPenetrationPercentDef, EvaluateParameters, ArmorPenetrationPercent);
		// 获取护甲穿透
		float ArmorPenetration = 0.0f;
		ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorPenetrationDef, EvaluateParameters, ArmorPenetration);
		// 获取目标护甲
		float TargetArmor = 0.0f;
		ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorDef, EvaluateParameters, TargetArmor);
		// 1. 处理固定护甲穿透
		TargetArmor = FMath::Max(0.0f, TargetArmor - ArmorPenetration);
		// 2. 处理百分比护甲穿透
		TargetArmor = FMath::Max(0.0f, TargetArmor * (1.0f - FMath::Min(ArmorPenetrationPercent, 100.0f) / 100.0f));
		// 3. 计算护甲减免(计算出来的是免伤率)
		float ArmorReduction = TargetArmor / (TargetArmor + 100.0f);
		BaseAttackDamage *= (1.0f - FMath::Min(ArmorReduction / 100.0f + DamageReduction/100.0f, 1.0f));
		// 4. 应用伤害加深(百分比提升)
		BaseAttackDamage *= (1.0f + DamageAmp / 100.0f);
		// 5. 输出到AttackDamage属性
		if (BaseAttackDamage > 0.0f)
		{
			// 添加输出修饰符
			OutExecutionOutput.AddOutputModifier(
				FGameplayModifierEvaluatedData(
				UCAttributeSet::GetAttackDamageAttribute(), //获取到伤害属性
				EGameplayModOp::Override, 
				BaseAttackDamage	//伤害
				));
		}
	}

	// 计算基础法术伤害值
	float BaseMagicDamage = 0.0f;
	ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
		DamageStatics().BaseMagicDamageDef, 
		EvaluateParameters, 
		BaseMagicDamage
	);
	if (BaseMagicDamage > 0)
	{
		// 获取法术穿透百分比
		float MagicPenetrationPercent = 0.0f;
		ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
			DamageStatics().MagicPenetrationPercentDef,
			EvaluateParameters, MagicPenetrationPercent);
		// 获取法术穿透
		float MagicPenetration = 0.0f;
		ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
			DamageStatics().MagicPenetrationDef,
			EvaluateParameters, MagicPenetration);
		// 获取目标法抗
		float TargetMagicResistance = 0.0f;
		ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
			DamageStatics().MagicResistanceDef, EvaluateParameters, TargetMagicResistance);
		// 1. 处理固定法术穿透
		TargetMagicResistance = FMath::Max(0.0f, TargetMagicResistance - MagicPenetration);
		// 2. 处理百分比法术穿透
		TargetMagicResistance = FMath::Max(0.0f, TargetMagicResistance * (1.0f - FMath::Min(MagicPenetrationPercent, 100.0f) / 100.0f));
		// 3. 计算法抗减免(计算出来的是免伤率)
		float MagicResistanceReduction = TargetMagicResistance / (TargetMagicResistance + 100.0f);
		BaseMagicDamage *= (1.0f - FMath::Min(MagicResistanceReduction / 100.0f + DamageReduction/100.0f, 1.0f));
		// 4. 应用伤害加深(百分比提升)
		BaseMagicDamage *= (1.0f + DamageAmp / 100.0f);
		OutExecutionOutput.AddOutputModifier(
			FGameplayModifierEvaluatedData(
			UCAttributeSet::GetMagicDamageAttribute(), //获取到伤害属性
			EGameplayModOp::Override, 
			BaseMagicDamage	//伤害
			));
	}
	
	// 计算基础真实伤害值
	float BaseTrueDamage = 0.0f;
	ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
		DamageStatics().BaseTrueDamageDef, 
		EvaluateParameters, 
		BaseTrueDamage
	);
	if (BaseTrueDamage > 0.0f)
	{
		// 计算伤害减免
		BaseTrueDamage *= (1.0f - FMath::Min(DamageReduction/100.0f, 1.0f));
		// 应用伤害加深(百分比提升)
		BaseTrueDamage *= (1.0f + DamageAmp / 100.0f);
		
		OutExecutionOutput.AddOutputModifier(
			FGameplayModifierEvaluatedData(
			UCAttributeSet::GetTrueDamageAttribute(), //获取到伤害属性
			EGameplayModOp::Override, 
			BaseTrueDamage	//伤害
			));
	}
}

伤害的配置
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
伤害多个好像会被夺舍
在这里插入图片描述
或许多弄几个特效组件UNiagaraComponent就好了,一种伤害一个组件
在这里插入图片描述
我试着操作了一手(不建议这么搞,因为我感觉很抽象)
在这里插入图片描述

// 幻雨喜欢小猫咪

#pragma once

#include "CoreMinimal.h"
#include "Components/ControllerComponent.h"
#include "GAS/Core/CGameplayAbilityTypes.h"
#include "NumberPopComponent_NiagaraText.generated.h"


// 定义一个结构体,表示一个数字弹出请求的数据
USTRUCT(BlueprintType)
struct FNumberPopRequest
{
	GENERATED_BODY()

	// 弹出数字的位置(世界坐标)
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lyra|Number Pops")
	FVector WorldLocation;

	// 要显示的数字
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lyra|Number Pops")
	int32 NumberToDisplay = 0;

	// 是否是“致命”伤害(@TODO: 应该使用标签来代替)
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lyra|Number Pops")
	bool bIsCriticalDamage = false;

	// 构造函数,初始化默认值
	FNumberPopRequest()
		: WorldLocation(ForceInitToZero)
	{
	}
};

class UNiagaraSystem;
class UNiagaraComponent;

/**
 * 
 */
UCLASS(Blueprintable)
class UNumberPopComponent_NiagaraText : public UControllerComponent
{
	GENERATED_BODY()
public:

	UNumberPopComponent_NiagaraText(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
	/** 
	 * 添加一个数字弹出到列表中以进行可视化展示
	 * @param NewRequest 新的数字弹出请求数据
	 */
	UFUNCTION(BlueprintCallable, Category = Foo)
	void AddNumberPop(const FNumberPopRequest& NewRequest, EDamageType Type);

	void AddAttackNumber(const FNumberPopRequest& NewRequest);
	void AddMagicNumber(const FNumberPopRequest& NewRequest);
	void AddTrueNumber(const FNumberPopRequest& NewRequest);
	UPROPERTY(EditDefaultsOnly, Category="DamagePop")
	FName NiagaraArrayName;

	UPROPERTY(EditDefaultsOnly, Category="DamagePop")
	TObjectPtr<UNiagaraSystem> TextNiagara;
protected:
	
	TArray<int32> DamageNumberArray;
	
	UPROPERTY(EditDefaultsOnly, Category = "Number Pop|Style")
	TObjectPtr<UNiagaraComponent> NiagaraComp;
	UPROPERTY(EditDefaultsOnly, Category = "Number Pop|Style")
	TObjectPtr<UNiagaraComponent> MagicNiagaraComp;
	UPROPERTY(EditDefaultsOnly, Category = "Number Pop|Style")
	TObjectPtr<UNiagaraComponent> TrueNiagaraComp;
};
// 幻雨喜欢小猫咪


#include "Player/NumberPopComponent_NiagaraText.h"
#include "NiagaraComponent.h"
#include "NiagaraDataInterfaceArrayFunctionLibrary.h"

UNumberPopComponent_NiagaraText::UNumberPopComponent_NiagaraText(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SetIsReplicatedByDefault(true);
	// bReplicates = true;
}

void UNumberPopComponent_NiagaraText::AddNumberPop(const FNumberPopRequest& NewRequest, EDamageType Type)
{
	switch (Type)
	{
		case EDamageType::PhysicalDamage:
		{
			AddAttackNumber(NewRequest);
			break;
		}
		case EDamageType::MagicDamage:
		{
			AddMagicNumber(NewRequest);
			break;
		}
		case EDamageType::TrueDamage:
		{
			AddTrueNumber(NewRequest);
			break;
		}
		default:
		{
			break;
		}
	}
	
}

void UNumberPopComponent_NiagaraText::AddAttackNumber(const FNumberPopRequest& NewRequest)
{
	int32 LocalDamage = NewRequest.NumberToDisplay;

	//Change Damage to negative to differentiate Critial vs Normal hit
	// 如果是致命伤害,则将数值设为负数以区分普通伤害
	if (NewRequest.bIsCriticalDamage)
	{
		LocalDamage *= -1;
	}

	// 如果没有 Niagara 组件,则创建一个
	if (!NiagaraComp)
	{
		NiagaraComp = NewObject<UNiagaraComponent>(GetOwner());
		if (TextNiagara != nullptr)
		{
			NiagaraComp->SetAsset(TextNiagara);			// 设置 Niagara 资源
			NiagaraComp->bAutoActivate = false;				// 不自动激活
		}
		NiagaraComp->SetupAttachment(nullptr);      // 不附加到任何物体
		check(NiagaraComp);
		NiagaraComp->RegisterComponent();					// 注册组件以便更新和渲染
		// NiagaraComp->SetReplicate(true);
	}

	NiagaraComp->Activate(false);                     // 手动激活 Niagara 粒子效果
	NiagaraComp->SetWorldLocation(NewRequest.WorldLocation); // 设置弹出位置
	
	UE_LOG(LogTemp, Log, TEXT("DamageHit location : %s"), *(NewRequest.WorldLocation.ToString()));
	//Add Damage information to the current Niagara list - Damage informations are packed inside a FVector4 where XYZ = Position, W = Damage
	// 获取 Niagara 数组中的 FVector4 列表(XYZ 表示位置,W 表示伤害值)
	TArray<FVector4> DamageList = UNiagaraDataInterfaceArrayFunctionLibrary::GetNiagaraArrayVector4(NiagaraComp, NiagaraArrayName);

	// 添加新伤害信息到数组中
	DamageList.Add(FVector4(
		NewRequest.WorldLocation.X,
		NewRequest.WorldLocation.Y,
		NewRequest.WorldLocation.Z,
		LocalDamage));

	// 将更新后的数组写回 Niagara 组件UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayVector4(NiagaraComp, NiagaraArrayName, DamageList);
	UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayVector4(NiagaraComp, NiagaraArrayName, DamageList);
}

void UNumberPopComponent_NiagaraText::AddMagicNumber(const FNumberPopRequest& NewRequest)
{
	int32 LocalDamage = NewRequest.NumberToDisplay;

	//Change Damage to negative to differentiate Critial vs Normal hit
	// 如果是致命伤害,则将数值设为负数以区分普通伤害
	if (NewRequest.bIsCriticalDamage)
	{
		LocalDamage *= -1;
	}

	// 如果没有 Niagara 组件,则创建一个
	if (!MagicNiagaraComp)
	{
		MagicNiagaraComp = NewObject<UNiagaraComponent>(GetOwner());
		if (TextNiagara != nullptr)
		{
			MagicNiagaraComp->SetAsset(TextNiagara);			// 设置 Niagara 资源
			MagicNiagaraComp->bAutoActivate = false;				// 不自动激活
		}
		MagicNiagaraComp->SetupAttachment(nullptr);      // 不附加到任何物体
		check(MagicNiagaraComp);
		MagicNiagaraComp->RegisterComponent();					// 注册组件以便更新和渲染
		// NiagaraComp->SetReplicate(true);
	}

	MagicNiagaraComp->Activate(false);                     // 手动激活 Niagara 粒子效果
	MagicNiagaraComp->SetWorldLocation(NewRequest.WorldLocation); // 设置弹出位置
	
	UE_LOG(LogTemp, Log, TEXT("DamageHit location : %s"), *(NewRequest.WorldLocation.ToString()));
	//Add Damage information to the current Niagara list - Damage informations are packed inside a FVector4 where XYZ = Position, W = Damage
	// 获取 Niagara 数组中的 FVector4 列表(XYZ 表示位置,W 表示伤害值)
	TArray<FVector4> DamageList = UNiagaraDataInterfaceArrayFunctionLibrary::GetNiagaraArrayVector4(MagicNiagaraComp, NiagaraArrayName);

	// 添加新伤害信息到数组中
	DamageList.Add(FVector4(
		NewRequest.WorldLocation.X,
		NewRequest.WorldLocation.Y,
		NewRequest.WorldLocation.Z,
		LocalDamage));

	// 将更新后的数组写回 Niagara 组件UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayVector4(NiagaraComp, NiagaraArrayName, DamageList);
	UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayVector4(MagicNiagaraComp, NiagaraArrayName, DamageList);
}

void UNumberPopComponent_NiagaraText::AddTrueNumber(const FNumberPopRequest& NewRequest)
{
	int32 LocalDamage = NewRequest.NumberToDisplay;
	// 如果是致命伤害,则将数值设为负数以区分普通伤害
	if (NewRequest.bIsCriticalDamage)
	{
		LocalDamage *= -1;
	}

	// 如果没有 Niagara 组件,则创建一个
	if (!TrueNiagaraComp)
	{
		TrueNiagaraComp = NewObject<UNiagaraComponent>(GetOwner());
		if (TextNiagara != nullptr)
		{
			TrueNiagaraComp->SetAsset(TextNiagara);			// 设置 Niagara 资源
			TrueNiagaraComp->bAutoActivate = false;				// 不自动激活
		}
		TrueNiagaraComp->SetupAttachment(nullptr);      // 不附加到任何物体
		
		check(TrueNiagaraComp);
		TrueNiagaraComp->RegisterComponent();					// 注册组件以便更新和渲染
	}

	TrueNiagaraComp->Activate(false);                     // 手动激活 Niagara 粒子效果
	TrueNiagaraComp->SetWorldLocation(NewRequest.WorldLocation); // 设置弹出位置
	
	UE_LOG(LogTemp, Log, TEXT("DamageHit location : %s"), *(NewRequest.WorldLocation.ToString()));
	//Add Damage information to the current Niagara list - Damage informations are packed inside a FVector4 where XYZ = Position, W = Damage
	// 获取 Niagara 数组中的 FVector4 列表(XYZ 表示位置,W 表示伤害值)
	TArray<FVector4> DamageList = UNiagaraDataInterfaceArrayFunctionLibrary::GetNiagaraArrayVector4(TrueNiagaraComp, NiagaraArrayName);

	// 添加新伤害信息到数组中
	DamageList.Add(FVector4(
		NewRequest.WorldLocation.X,
		NewRequest.WorldLocation.Y,
		NewRequest.WorldLocation.Z,
		LocalDamage));

	// 将更新后的数组写回 Niagara 组件UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayVector4(NiagaraComp, NiagaraArrayName, DamageList);
	UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayVector4(TrueNiagaraComp, NiagaraArrayName, DamageList);
}

对于这几个添加数字的特效的函数也可以添加类型,具体操作我也不会特效,就将就这么放着
在这里插入图片描述