UE5多人MOBA+GAS 番外篇:移植Lyra的伤害特效(没用GameplayCue,因为我失败了┭┮﹏┭┮)
文章目录
- Lyra里面的样子和需要的东西
- 开始编写需要的组件内容
- 让特效显露而出
- 大功告成
Lyra里面的样子和需要的东西
学习lyra的伤害奶瓜,奶瓜有关的组件都在这里
迁移一下奶瓜的资产
Lyra中GC的一些详细样子
其中他的暴击是通过tag的判断的
开始编写需要的组件内容
直接搜索这个类会发现没有
需要开启ModularGamePlay
插件才可以
一搜就出来了
直接一步到位,因为那个Mesh的lyra里面并没有实现,没得抄,索性直接NumberPopComponent_NiagaraText
在cs文件中添加三个模块
在父类中最主要的核心就是这个结构体
但是在接收方有两个对这个数字奶瓜来说是没用的
// 幻雨喜欢小猫咪#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;
};
// 幻雨喜欢小猫咪#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学的)(我又觉得内存会有问题一测果然,我就想到了,小兵对象池的内存管理,但是只是在这种双方都是对象池管理的情况下比较友好,要是对方会死掉呢,如此一来我又得添加一个委托,当对方销毁的适合移除对象池)
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);
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组件创建失败,内存不足或配置错误"));
#endifreturn;}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); // 从容器中移除}}
}
属性中添加一个函数,用来调用通过玩家控制器调用输出
static void ShowFloatingText(AActor* TargetActor, float Damage, bool IsCriticalHit);
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里操作更是轻而易举呢,我们直接操作
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组件创建失败,内存不足或配置错误"));
#endifreturn;}DamageText->RegisterComponent(); // 注册组件}// 设置显示参数FNumberPopRequest NumberPopRequest;NumberPopRequest.WorldLocation = TargetActor->GetActorLocation() + FVector(0, 0, 200);NumberPopRequest.bIsCriticalDamage = bCriticalHit;NumberPopRequest.NumberToDisplay = DamageAmount;DamageText->AddNumberPop(NumberPopRequest);
}
操作完也是蛮ok的
如果有路过的大佬又更优解,教教┭┮﹏┭┮