UE5多人MOBA+GAS 番外篇:移植Lyra的伤害特效(没用GameplayCue,因为我失败了┭┮﹏┭┮)

文章目录


Lyra里面的样子和需要的东西

学习lyra的伤害奶瓜,奶瓜有关的组件都在这里

迁移一下奶瓜的资产

Lyra中GC的一些详细样子


其中他的暴击是通过tag的判断的

开始编写需要的组件内容

直接搜索这个类会发现没有

需要开启ModularGamePlay插件才可以

一搜就出来了

直接一步到位,因为那个Mesh的lyra里面并没有实现,没得抄,索性直接NumberPopComponent_NiagaraText

在cs文件中添加三个模块

在父类中最主要的核心就是这个结构体

但是在接收方有两个对这个数字奶瓜来说是没用的

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

#pragma once

#include "CoreMinimal.h"
#include "Components/ControllerComponent.h"
#include "NumberPopComponent_NiagaraText.generated.h"


// 定义一个结构体,表示一个数字弹出请求的数据
USTRUCT(BlueprintType)
struct FLyraNumberPopRequest
{
	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;

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

class UNiagaraSystem;
class UNiagaraComponent;

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

	UNumberPopComponent_NiagaraText(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

	/** 
	 * 添加一个数字弹出到列表中以进行可视化展示
	 * @param NewRequest 新的数字弹出请求数据
	 */
	UFUNCTION(BlueprintCallable, Category = Foo)
	void AddNumberPop(const FLyraNumberPopRequest& 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;
};
cpp 复制代码
// 幻雨喜欢小猫咪


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

UNumberPopComponent_NiagaraText::UNumberPopComponent_NiagaraText(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	
}

void UNumberPopComponent_NiagaraText::AddNumberPop(const FLyraNumberPopRequest& 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->Activate(false);                     // 手动激活 Niagara 粒子效果
	NiagaraComp->SetWorldLocation(NewRequest.WorldLocation); // 设置弹出位置


	// UE_LOG(LogLyra, 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);
}

让特效显露而出

角色控制器中添加(具体是不是要这么操作,我也不懂呢,我GameplayCue成为失败的man了,现在用了Aura学的)(我又觉得内存会有问题一测果然,我就想到了,小兵对象池的内存管理,但是只是在这种双方都是对象池管理的情况下比较友好,要是对方会死掉呢,如此一来我又得添加一个委托,当对方销毁的适合移除对象池)

cpp 复制代码
public:
	UFUNCTION(Client, Reliable)
	void ShowDamageNumber(float DamageAmount, AActor* TargetActor, bool bCriticalHit);

private:
	UPROPERTY(EditAnywhere, Category = "Components")
	TSubclassOf<UNumberPopComponent_NiagaraText> NumberPopComponentClass;
	// 对象池管理
	UPROPERTY()
	TArray<TObjectPtr<UNumberPopComponent_NiagaraText>> ActiveNumberPops;
	UFUNCTION()
	void HandleTargetActorDestroyed(AActor* DestroyedActor);
cpp 复制代码
void ACPlayerController::ShowDamageNumber_Implementation(float DamageAmount, AActor* TargetActor, bool bCriticalHit)
{
	if (!IsValid(TargetActor) || !NumberPopComponentClass || !IsLocalController())
		return;

	UNumberPopComponent_NiagaraText* DamageText = nullptr;
    
	// 查找可复用组件
	for (UNumberPopComponent_NiagaraText* Pop : ActiveNumberPops)
	{
		if (Pop && Pop->GetOwner() == TargetActor)
		{
			DamageText = Pop;
			break;
		}
	}

	// 创建新组件或复用现有组件
	if (!DamageText)
	{
		DamageText = NewObject<UNumberPopComponent_NiagaraText>(TargetActor, NumberPopComponentClass);
		if (!DamageText)
		{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
			UE_LOG(LogTemp, Error, TEXT("Niagara组件创建失败,内存不足或配置错误"));
#endif
			return;
		}
		DamageText->RegisterComponent();
		ActiveNumberPops.Add(DamageText);
		TargetActor->OnDestroyed.AddDynamic(this, &ACPlayerController::HandleTargetActorDestroyed);
	}
	else if (!DamageText->IsRegistered())
	{
		DamageText->RegisterComponent();
	}

	// 设置显示参数
	FNumberPopRequest NumberPopRequest;
	NumberPopRequest.WorldLocation = TargetActor->GetActorLocation();
	NumberPopRequest.WorldLocation.Z += 200.f;
	NumberPopRequest.bIsCriticalDamage = bCriticalHit;
	NumberPopRequest.NumberToDisplay = DamageAmount;
    
	DamageText->AddNumberPop(NumberPopRequest);
}
void ACPlayerController::HandleTargetActorDestroyed(AActor* DestroyedActor)
{
	// 遍历所有活跃的 Niagara 组件
	for (int32 i = ActiveNumberPops.Num() - 1; i >= 0; i--)
	{
		UNumberPopComponent_NiagaraText* PopComponent = ActiveNumberPops[i];
		if (PopComponent && PopComponent->GetOwner() == DestroyedActor)
		{
			// 清理组件
			PopComponent->UnregisterComponent(); // 解除注册
			PopComponent->MarkAsGarbage();       // 标记为垃圾回收
			ActiveNumberPops.RemoveAt(i);        // 从容器中移除
		}
	}
}

属性中添加一个函数,用来调用通过玩家控制器调用输出

cpp 复制代码
static void ShowFloatingText(AActor* TargetActor, float Damage, bool IsCriticalHit);
cpp 复制代码
void UCAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
	// 伤害
	if (Data.EvaluatedData.Attribute == GetAttackDamageAttribute())
	{
		float NewDamage = GetAttackDamage();
		SetAttackDamage(0.f);
		bool bCriticalHit = false;
		UAbilitySystemComponent* SourceASC = Data.EffectSpec.GetContext().GetOriginalInstigatorAbilitySystemComponent();
		if (NewDamage > 0.f)
		{
			if (SourceASC)
			{
				bool bFound = false;
				const float EffectiveCriticalHitChance = SourceASC->GetGameplayAttributeValue(UCHeroAttributeSet::GetCriticalStrikeChanceAttribute(), bFound);
				if (bFound)
				{
					bFound = false;
					bCriticalHit = FMath::RandRange(1, 100) < EffectiveCriticalHitChance;
					if (bCriticalHit)
					{
						const float CriticalStrikeDamage = 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)
			// 弄出数字
			if (AActor* TargetActor = Data.Target.AbilityActorInfo->AvatarActor.Get())
			{
				ShowFloatingText(TargetActor,NewDamage, bCriticalHit);
			}
		}
	}
}

void UCAttributeSet::ShowFloatingText(AActor* TargetActor, const float Damage, bool IsCriticalHit)
{
	for (int32 i = 0; ;++i)
	{
		if (ACPlayerController* PC = Cast<ACPlayerController>(UGameplayStatics::GetPlayerController(TargetActor,i)))
		{
			PC->ShowDamageNumber(Damage, TargetActor, IsCriticalHit); //调用显示伤害数字
		}else
		{
			break;
		}
	}
}

创建一个蓝图版本的NumberPopComponent_NiagaraText

大功告成

客户端运行,也是双方都能看见特效了

对象池管理前,明显看到帧率从60掉到了40左右(如果可以的话选择销毁这个组件也是可以的,就是我懒罢了)

对象池管理后(我把蓝耗改成了1,所有我轰这群小兵轰了这么多次)

主播主播对象池还是太吃操作了,有没有别的方法,有的有的

既然这个是组件,我又觉得对象池这个操作还是有点抽象,这内存有点不该放在每个玩家控制器里,太浪费内存了。

这是lyra在GC里面的操作,他是属于直接获取这个组件,那像这样的操作,我们在cpp里操作更是轻而易举呢,我们直接操作

cpp 复制代码
void ACPlayerController::ShowDamageNumber_Implementation(float DamageAmount, AActor* TargetActor, bool bCriticalHit)
{
	if (!IsValid(TargetActor) || !NumberPopComponentClass || !IsLocalController())
		return;

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

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

	// 设置显示参数
	FNumberPopRequest NumberPopRequest;
	NumberPopRequest.WorldLocation = TargetActor->GetActorLocation() + FVector(0, 0, 200);
	NumberPopRequest.bIsCriticalDamage = bCriticalHit;
	NumberPopRequest.NumberToDisplay = DamageAmount;
    
	DamageText->AddNumberPop(NumberPopRequest);
}

操作完也是蛮ok的

如果有路过的大佬又更优解,教教┭┮﹏┭┮

相关推荐
程序员JerrySUN3 小时前
Valgrind Memcheck 全解析教程:6个程序说明基础内存错误
android·java·linux·运维·开发语言·学习
经典19924 小时前
mysql 性能优化之Explain讲解
android·mysql·性能优化
Kiri霧6 小时前
Kotlin集合与空值
android·开发语言·kotlin
Glacien7 小时前
compose动画从底层基础到顶层高级应用(三)核心API之--Transition
android
亿刀7 小时前
为什么要学习Flutter编译过程
android·flutter
suqingxiao8 小时前
android虚拟机(AVD)报错The emulator process for AVD xxx has terminated
android
whysqwhw8 小时前
OkHttp Cookie 处理机制全解析
android
Evan_ZGYF丶8 小时前
【RK3576】【Android14】ADB工具说明与使用
android·驱动开发·android14·rk3576
狂浪天涯9 小时前
Android 16 显示系统 | 从View 到屏幕系列 - 4 | GraphicBuffer & Gralloc
android·操作系统