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

文章目录


重构伤害类型

添加Tag

cpp 复制代码
	// 伤害类型
	// 物理伤害
	CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(DamageType_AttackDamage)
	// 魔法伤害
	CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(DamageType_MagicDamage)
	// 真实伤害
	CRUNCH_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(DamageType_TrueDamage)
cpp 复制代码
	UE_DEFINE_GAMEPLAY_TAG_COMMENT(DamageType_AttackDamage, "DamageType.AttackDamage", "物理伤害")
	UE_DEFINE_GAMEPLAY_TAG_COMMENT(DamageType_MagicDamage, "DamageType.MagicDamage", "魔法伤害")
	UE_DEFINE_GAMEPLAY_TAG_COMMENT(DamageType_TrueDamage, "DamageType.TrueDamage", "真实伤害")

CGameplayAbilityTypes中重构伤害类型

cpp 复制代码
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, meta = (Categories = "DamageType"))
	TMap<FGameplayTag,FDamageDefinition> DamageTypeDefinitions;

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

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

重构伤害应用

修改一下CGameplayAbility中的伤害配置

cpp 复制代码
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);
	// 创建效果Spec句柄,指定效果类、能力等级和上下文
	FGameplayEffectSpecHandle EffectSpecHandle = ASC->MakeOutgoingSpec(Damage.DamageEffect, Level, ContextHandle);
	
	// 配置伤害
	// MakeDamage(Damage, Level);
	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;
			}
		}
		UAbilitySystemBlueprintLibrary::AssignTagSetByCallerMagnitude(EffectSpecHandle, TypePair.Key, TotalModifier);
	}
	// 在目标上应用游戏效果规范
	ApplyGameplayEffectSpecToTarget(GetCurrentAbilitySpecHandle(),
									GetCurrentActorInfo(),
									GetCurrentActivationInfo(),
									EffectSpecHandle,
									UAbilitySystemBlueprintLibrary::AbilityTargetDataFromActor(TargetActor));
}

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

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

cpp 复制代码
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;
	
};
cpp 复制代码
	// 设置效果属性
	void SetEffectProperties(const FGameplayEffectModCallbackData& Data, FEffectProperties& Props) const;
	// 伤害处理函数
	void Damage(const FEffectProperties& Props, EDamageType Type, const float Damage);
cpp 复制代码
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::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, FGameplayTag DamageType, 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);
	}
	// TODO:添加第四个参数,用来传递伤害的类型
	ShowFloatingText(Props,NewDamage, bCriticalHit, DamageType);
}

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

cpp 复制代码
	static void ShowFloatingText(const FEffectProperties& Props, float Damage, bool IsCriticalHitE, FGameplayTag DamageType);

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

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中修改一下输出数字的函数

cpp 复制代码
	UFUNCTION(Client, Reliable)
	void ShowDamageNumber(float DamageAmount, ACharacter* TargetCharacter, bool bCriticalHit, FGameplayTag DamageType);
cpp 复制代码
void ACPlayerController::ShowDamageNumber_Implementation(float DamageAmount, ACharacter* TargetCharacter, bool bCriticalHit, FGameplayTag DamageType)
{
	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(); // 注册组件
	}
	//修改~Begin
	// 新增随机偏移
	FVector BaseOffset(0, 0, 200);
	float RandomX = FMath::FRandRange(-100.0f, 100.0f);  // X轴随机偏移
	float RandomY = FMath::FRandRange(-100.0f, 100.0f);  // Y轴随机偏移
	float RandomZ = FMath::FRandRange(-20.0f, 50.0f);  // Z轴微调
	//修改~End
	// TODO:添加各种伤害类型的伤害数字特效
	// 设置显示参数
	FNumberPopRequest NumberPopRequest;
	NumberPopRequest.WorldLocation = TargetCharacter->GetActorLocation() + BaseOffset + FVector(RandomX, RandomY, RandomZ) ;
	// NumberPopRequest.WorldLocation = TargetCharacter->GetActorLocation() + FVector(0, 0, 200);
	NumberPopRequest.bIsCriticalDamage = bCriticalHit;
	NumberPopRequest.NumberToDisplay = DamageAmount;
    
	DamageText->AddNumberPop(NumberPopRequest, DamageType);
}

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

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

cpp 复制代码
// 幻雨喜欢小猫咪


#include "GAS/Executions/ECC_AttackDamage.h"

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

struct FDamageStatics
{
	// 护甲穿透
	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(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().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 = Spec.GetSetByCallerMagnitude(TGameplayTags::DamageType_AttackDamage, false, -1.f);

	// 物理伤害的处理
	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 = Spec.GetSetByCallerMagnitude(TGameplayTags::DamageType_MagicDamage, false, -1.f);
	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 = Spec.GetSetByCallerMagnitude(TGameplayTags::DamageType_TrueDamage, false, -1.f);
	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就好了,一种伤害一个组件

我试着操作了一手(不建议这么搞,因为我感觉很抽象)

cpp 复制代码
#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 新的数字弹出请求数据
	 * @param DamageType 伤害类型
	 */
	UFUNCTION(BlueprintCallable, Category = Foo)
	void AddNumberPop(const FNumberPopRequest& NewRequest, FGameplayTag DamageType);

	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;
};
cpp 复制代码
#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, FGameplayTag DamageType)
{
	if (DamageType.MatchesTagExact(TGameplayTags::DamageType_AttackDamage))
	{
		AddAttackNumber(NewRequest);
	}else if (DamageType.MatchesTagExact(TGameplayTags::DamageType_MagicDamage))
	{
		AddMagicNumber(NewRequest);
	}else if (DamageType.MatchesTagExact(TGameplayTags::DamageType_TrueDamage))
	{
		AddTrueNumber(NewRequest);
	}
}

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

对于这几个添加数字的特效的函数也可以添加类型,具体操作我也不会特效,就将就这么放着

相关推荐
AI视觉网奇1 天前
ue 蓝图动画学习笔记
笔记·学习·ue5
陈友松2 天前
UE5 文件传输插件:支持任意类型,任意大小(超过2G)的文件上传和下载
ue5
AI视觉网奇2 天前
CBAudio2Face
ue5·audio2face
AI视觉网奇2 天前
ue 操作 metahuman
ue5
AI视觉网奇2 天前
ue python脚本 获取资产
笔记·ue5
AI视觉网奇2 天前
audio2face docker方式
docker·ue5
会思考的猴子3 天前
UE5 笔记二 GameplayAbilitySystem Dash(冲刺)
笔记·ue5
AI视觉网奇3 天前
audio2face ue插件形式实战笔记
笔记·ue5
nutriu4 天前
从UE5.6DNA 导出指定LOD层级的ARkit52个表情或者Metahuman263个表情教程 #BlendShapeExporter
ue5·数字人·arkit·blendshape·虚拟角色·meta human·dna
AI视觉网奇4 天前
nvcr.io 登录方法
docker·ue5