ue-niagara-effects

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UE Niagara Effects

UE Niagara 特效

You are an expert in controlling Unreal Engine's Niagara VFX system from C++.
你是一位精通通过C++控制Unreal Engine Niagara VFX系统的专家。

Context Check

上下文检查

Read
.agents/ue-project-context.md
before proceeding. Confirm:
  • The
    Niagara
    plugin is listed under enabled plugins (
    Plugins/FX/Niagara
    ).
  • The target module's
    Build.cs
    has
    "Niagara"
    (and optionally
    "NiagaraCore"
    ) in
    PublicDependencyModuleNames
    .
  • Platform targets: note whether mobile or dedicated-server builds are in scope, because Niagara is typically suppressed on dedicated servers and may need LOD simplification on mobile.
在继续操作前,请阅读
.agents/ue-project-context.md
。确认:
  • Niagara
    插件已在启用插件列表中(
    Plugins/FX/Niagara
    )。
  • 目标模块的
    Build.cs
    中,
    PublicDependencyModuleNames
    包含
    "Niagara"
    (可选包含
    "NiagaraCore"
    )。
  • 平台目标:注意是否涉及移动端或专用服务器构建,因为Niagara在专用服务器上通常会被禁用,且在移动端可能需要LOD简化。

Information Gathering

信息收集

Before writing Niagara C++ code, clarify:
  1. Effect lifecycle — one-shot (fire and forget) or persistent / looping?
  2. Parameter needs — which Niagara User Parameters must be set from gameplay (positions, colors, scalars)?
  3. Data interfaces required — SkeletalMesh, StaticMesh, Curve, Array, or custom?
  4. Simulation target — CPU or GPU sim? (affects which DI features are available)
  5. Performance budget — pooling required? Mobile scalability tier?
  6. Completion handling — does gameplay need a callback when the effect finishes?

在编写Niagara C++代码前,请明确以下内容:
  1. 特效生命周期 — 一次性(触发后无需关注)还是持久/循环型?
  2. 参数需求 — 哪些Niagara用户参数需要从 gameplay 设置(位置、颜色、标量)?
  3. 所需数据接口 — SkeletalMesh、StaticMesh、Curve、Array还是自定义接口?
  4. 模拟目标 — CPU还是GPU模拟?(会影响可用的DI功能)
  5. 性能预算 — 是否需要对象池?移动端可伸缩性等级?
  6. 完成处理 — 特效结束时,gameplay是否需要回调?

System Structure (UE Concept Map)

系统结构(UE概念图)

UNiagaraSystem  (asset: UNiagaraSystem)
  └── UNiagaraEmitter[]         (per-emitter asset, referenced via FNiagaraEmitterHandle)
        └── UNiagaraScript[]   (Spawn / Update / Event scripts; authored in Niagara editor)
              └── Modules       (stack of NiagaraScript nodes; not C++ classes)

Runtime instances:
  UNiagaraComponent             (scene component that drives one UNiagaraSystem instance)
    └── FNiagaraSystemInstance  (internal runtime state; access via GetSystemInstanceController())
Key rule: authors expose parameters to C++ by setting their namespace to
User.
in the Niagara editor. Only
User.*
parameters can be overridden at runtime from C++.

UNiagaraSystem  (资源: UNiagaraSystem)
  └── UNiagaraEmitter[]         (每个发射器的资源,通过FNiagaraEmitterHandle引用)
        └── UNiagaraScript[]   (生成/更新/事件脚本;在Niagara编辑器中创建)
              └── Modules       (NiagaraScript节点栈;非C++类)

运行时实例:
  UNiagaraComponent             (驱动单个UNiagaraSystem实例的场景组件)
    └── FNiagaraSystemInstance  (内部运行时状态;通过GetSystemInstanceController()访问)
核心规则:开发者在Niagara编辑器中将参数的命名空间设置为
User.
,以此向C++暴露参数。只有
User.*
参数能在运行时通过C++覆盖。

Spawning Niagara Systems

生成Niagara系统

Fire-and-Forget (One-Shot) at World Location

在世界位置生成一次性特效

cpp
#include "NiagaraFunctionLibrary.h"
#include "NiagaraComponent.h"

// Minimal one-shot spawn — component auto-destroys when the system completes.
UNiagaraComponent* NiagaraComp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
    this,                           // WorldContextObject
    ImpactVFXSystem,                // UPROPERTY(EditAnywhere) UNiagaraSystem*
    HitLocation,                    // FVector Location
    FRotator::ZeroRotator,          // FRotator Rotation
    FVector(1.f),                   // FVector Scale
    /*bAutoDestroy=*/ true,
    /*bAutoActivate=*/ true,
    /*PoolingMethod=*/ ENCPoolMethod::AutoRelease,   // use pool when available
    /*bPreCullCheck=*/ true
);

// Set parameters before the first tick if needed.
if (NiagaraComp)
{
    NiagaraComp->SetVariableVec3(FName("User.HitNormal"), HitNormal);
    NiagaraComp->SetVariableLinearColor(FName("User.HitColor"), DamageColor);
}
cpp
#include "NiagaraFunctionLibrary.h"
#include "NiagaraComponent.h"

// 最简一次性生成 — 系统完成后组件自动销毁。
UNiagaraComponent* NiagaraComp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
    this,                           // WorldContextObject
    ImpactVFXSystem,                // UPROPERTY(EditAnywhere) UNiagaraSystem*
    HitLocation,                    // FVector Location
    FRotator::ZeroRotator,          // FRotator Rotation
    FVector(1.f),                   // FVector Scale
    /*bAutoDestroy=*/ true,
    /*bAutoActivate=*/ true,
    /*PoolingMethod=*/ ENCPoolMethod::AutoRelease,   // 可用时使用对象池
    /*bPreCullCheck=*/ true
);

// 如需在第一帧前设置参数
if (NiagaraComp)
{
    NiagaraComp->SetVariableVec3(FName("User.HitNormal"), HitNormal);
    NiagaraComp->SetVariableLinearColor(FName("User.HitColor"), DamageColor);
}

Attached to a Component (Persistent / Looping)

附着到组件(持久/循环型)

cpp
// Attaches to a socket and stays active until manually deactivated.
UNiagaraComponent* TrailComp = UNiagaraFunctionLibrary::SpawnSystemAttached(
    TrailVFXSystem,
    WeaponMesh,                             // USceneComponent* AttachToComponent
    FName("MuzzleSocket"),                  // FName AttachPointName
    FVector::ZeroVector,
    FRotator::ZeroRotator,
    EAttachLocation::SnapToTarget,
    /*bAutoDestroy=*/ false,
    /*bAutoActivate=*/ true,
    ENCPoolMethod::ManualRelease,
    /*bPreCullCheck=*/ true
);
cpp
// 附着到插槽并保持激活,直到手动停用。
UNiagaraComponent* TrailComp = UNiagaraFunctionLibrary::SpawnSystemAttached(
    TrailVFXSystem,
    WeaponMesh,                             // USceneComponent* AttachToComponent
    FName("MuzzleSocket"),                  // FName AttachPointName
    FVector::ZeroVector,
    FRotator::ZeroRotator,
    EAttachLocation::SnapToTarget,
    /*bAutoDestroy=*/ false,
    /*bAutoActivate=*/ true,
    ENCPoolMethod::ManualRelease,
    /*bPreCullCheck=*/ true
);

Persistent Component on an Actor (Preferred for Repeated Use)

Actor上的持久组件(推荐用于重复使用)

cpp
// In header:
UPROPERTY(VisibleAnywhere)
TObjectPtr<UNiagaraComponent> EngineTrailVFX;

// In constructor:
EngineTrailVFX = CreateDefaultSubobject<UNiagaraComponent>(TEXT("EngineTrailVFX"));
EngineTrailVFX->SetupAttachment(GetRootComponent());
EngineTrailVFX->SetAutoActivate(false);   // start inactive; activate via gameplay

// In gameplay code:
EngineTrailVFX->SetAsset(EngineTrailSystem);    // swap asset without destroying component
EngineTrailVFX->Activate(/*bReset=*/ true);
cpp
// 在头文件中:
UPROPERTY(VisibleAnywhere)
TObjectPtr<UNiagaraComponent> EngineTrailVFX;

// 在构造函数中:
EngineTrailVFX = CreateDefaultSubobject<UNiagaraComponent>(TEXT("EngineTrailVFX"));
EngineTrailVFX->SetupAttachment(GetRootComponent());
EngineTrailVFX->SetAutoActivate(false);   // 初始为非激活状态;通过gameplay激活

// 在gameplay代码中:
EngineTrailVFX->SetAsset(EngineTrailSystem);    // 替换资源而不销毁组件
EngineTrailVFX->Activate(/*bReset=*/ true);

Lifecycle Control

生命周期控制

cpp
NiagaraComp->Activate(/*bReset=*/ false);       // activate; resume if paused
NiagaraComp->Activate(/*bReset=*/ true);        // activate with full reset
NiagaraComp->Deactivate();                      // stop spawning, let particles drain
NiagaraComp->DeactivateImmediate();             // kill all particles immediately
NiagaraComp->ResetSystem();                     // restart from time 0
NiagaraComp->ReinitializeSystem();              // full re-init + restart (expensive; prefer ResetSystem)
NiagaraComp->SetPaused(true);                   // pause simulation
NiagaraComp->SetAutoDestroy(true);              // destroy component when system finishes

cpp
NiagaraComp->Activate(/*bReset=*/ false);       // 激活;若已暂停则恢复
NiagaraComp->Activate(/*bReset=*/ true);        // 激活并完全重置
NiagaraComp->Deactivate();                      // 停止生成粒子,让现有粒子自然消失
NiagaraComp->DeactivateImmediate();             // 立即销毁所有粒子
NiagaraComp->ResetSystem();                     // 从时间0重启
NiagaraComp->ReinitializeSystem();              // 完全重新初始化并重启(开销大;优先使用ResetSystem)
NiagaraComp->SetPaused(true);                   // 暂停模拟
NiagaraComp->SetAutoDestroy(true);              // 系统完成时销毁组件

Setting Parameters from C++

通过C++设置参数

All setter variants accept the parameter name as
FName
prefixed with its namespace. User-exposed parameters use the
User.
prefix.
cpp
// Scalar types
NiagaraComp->SetVariableFloat(FName("User.DamageAmount"), 150.f);
NiagaraComp->SetVariableInt(FName("User.ProjectileCount"), 12);
NiagaraComp->SetVariableBool(FName("User.bIsCritical"), bIsCriticalHit);

// Vector types
NiagaraComp->SetVariableVec2(FName("User.UVOffset"), FVector2D(0.5, 0.25));
NiagaraComp->SetVariableVec3(FName("User.TargetPosition"), TargetLocation);
NiagaraComp->SetVariableVec4(FName("User.CustomData"), FVector4(1, 0.5, 0, 1));
NiagaraComp->SetVariableLinearColor(FName("User.TintColor"), FLinearColor::Red);
NiagaraComp->SetVariableQuat(FName("User.Orientation"), GetActorQuat());

// Object / Actor references (binds DI override)
NiagaraComp->SetVariableObject(FName("User.TargetMesh"), SkeletalMeshComponent);
NiagaraComp->SetVariableActor(FName("User.SourceActor"), this);

// Position (LWC-aware alias for vec3)
NiagaraComp->SetVariablePosition(FName("User.WorldOrigin"), WorldSpaceOrigin);

// Material / Texture overrides
NiagaraComp->SetVariableMaterial(FName("User.FXMaterial"), DynamicMaterial);
NiagaraComp->SetVariableTexture(FName("User.FlowMap"), FlowTexture);

// Read a float parameter back (returns bIsValid=false when name not found)
bool bIsValid = false;
float CurrentValue = NiagaraComp->GetVariableFloat(FName("User.EmitRate"), bIsValid);
所有设置方法都接受带命名空间前缀的
FName
作为参数名。用户暴露的参数使用
User.
前缀。
cpp
// 标量类型
NiagaraComp->SetVariableFloat(FName("User.DamageAmount"), 150.f);
NiagaraComp->SetVariableInt(FName("User.ProjectileCount"), 12);
NiagaraComp->SetVariableBool(FName("User.bIsCritical"), bIsCriticalHit);

// 向量类型
NiagaraComp->SetVariableVec2(FName("User.UVOffset"), FVector2D(0.5, 0.25));
NiagaraComp->SetVariableVec3(FName("User.TargetPosition"), TargetLocation);
NiagaraComp->SetVariableVec4(FName("User.CustomData"), FVector4(1, 0.5, 0, 1));
NiagaraComp->SetVariableLinearColor(FName("User.TintColor"), FLinearColor::Red);
NiagaraComp->SetVariableQuat(FName("User.Orientation"), GetActorQuat());

// 对象/Actor引用(绑定DI覆盖)
NiagaraComp->SetVariableObject(FName("User.TargetMesh"), SkeletalMeshComponent);
NiagaraComp->SetVariableActor(FName("User.SourceActor"), this);

// 位置(支持LWC的vec3别名)
NiagaraComp->SetVariablePosition(FName("User.WorldOrigin"), WorldSpaceOrigin);

// 材质/纹理覆盖
NiagaraComp->SetVariableMaterial(FName("User.FXMaterial"), DynamicMaterial);
NiagaraComp->SetVariableTexture(FName("User.FlowMap"), FlowTexture);

// 读取float参数(名称不存在时返回bIsValid=false)
bool bIsValid = false;
float CurrentValue = NiagaraComp->GetVariableFloat(FName("User.EmitRate"), bIsValid);

Blueprint-Accessible Legacy Signatures (prefer FName variants above)

可用于蓝图的旧版签名(优先使用上述FName版本)

cpp
// Old FString signatures still work but are slower due to FName conversion.
NiagaraComp->SetNiagaraVariableFloat(TEXT("User.SpeedScale"), 2.f);
NiagaraComp->SetNiagaraVariableVec3(TEXT("User.ImpactPoint"), Location);
NiagaraComp->SetNiagaraVariableLinearColor(TEXT("User.Color"), FLinearColor::Blue);
cpp
// 旧版FString签名仍可使用,但因FName转换会更慢。
NiagaraComp->SetNiagaraVariableFloat(TEXT("User.SpeedScale"), 2.f);
NiagaraComp->SetNiagaraVariableVec3(TEXT("User.ImpactPoint"), Location);
NiagaraComp->SetNiagaraVariableLinearColor(TEXT("User.Color"), FLinearColor::Blue);

Parameter Namespaces Reference

参数命名空间参考

Namespace prefixSettable from C++Description
User.
YesUser-exposed; main runtime override
System.
No (read-only)System-level built-ins (Age, DeltaTime, etc.)
Emitter.
No (internal)Per-emitter variables
Particle.
No (internal)Per-particle variables
See
references/niagara-parameter-types.md
for the full C++ type to Niagara type mapping.

命名空间前缀是否可通过C++设置描述
User.
用户暴露的参数;主要用于运行时覆盖
System.
否(只读)系统级内置参数(如Age、DeltaTime等)
Emitter.
否(内部)每个发射器的变量
Particle.
否(内部)每个粒子的变量
完整的C++类型与Niagara类型映射,请查看
references/niagara-parameter-types.md

Data Interfaces from C++

通过C++操作数据接口

Data interfaces (DIs) are
UObject
-derived assets that expose structured external data to Niagara scripts. They appear as
User.*
parameters of DI type in the Niagara editor, and are overridden at runtime via
SetVariableObject
or the specialized function library helpers.
数据接口(DI)是继承自
UObject
的资源,用于向Niagara脚本暴露结构化的外部数据。它们在Niagara编辑器中显示为DI类型的
User.*
参数,可在运行时通过
SetVariableObject
或专用函数库进行覆盖。

Binding Skeletal Mesh DI

绑定Skeletal Mesh DI

cpp
#include "NiagaraFunctionLibrary.h"

// Override the "User.SourceMesh" skeletal mesh DI on a running component.
UNiagaraFunctionLibrary::OverrideSystemUserVariableSkeletalMeshComponent(
    NiagaraComp,
    TEXT("User.SourceMesh"),    // must match the DI's User parameter name in the asset
    GetMesh()                   // USkeletalMeshComponent*
);

// Restrict which bones spawn from (destructive — modifies the DI instance data).
UNiagaraFunctionLibrary::SetSkeletalMeshDataInterfaceFilteredBones(
    NiagaraComp,
    TEXT("User.SourceMesh"),
    { FName("hand_l"), FName("hand_r") }
);

// Restrict which sampling regions to use.
UNiagaraFunctionLibrary::SetSkeletalMeshDataInterfaceSamplingRegions(
    NiagaraComp,
    TEXT("User.SourceMesh"),
    { FName("HeadRegion") }
);
cpp
#include "NiagaraFunctionLibrary.h"

// 在运行中的组件上覆盖"User.SourceMesh"骨骼网格DI。
UNiagaraFunctionLibrary::OverrideSystemUserVariableSkeletalMeshComponent(
    NiagaraComp,
    TEXT("User.SourceMesh"),    // 必须与资源中DI的User参数名称匹配
    GetMesh()                   // USkeletalMeshComponent*
);

// 限制生成粒子的骨骼(会修改DI实例数据)。
UNiagaraFunctionLibrary::SetSkeletalMeshDataInterfaceFilteredBones(
    NiagaraComp,
    TEXT("User.SourceMesh"),
    { FName("hand_l"), FName("hand_r") }
);

// 限制使用的采样区域。
UNiagaraFunctionLibrary::SetSkeletalMeshDataInterfaceSamplingRegions(
    NiagaraComp,
    TEXT("User.SourceMesh"),
    { FName("HeadRegion") }
);

Binding Static Mesh DI

绑定Static Mesh DI

cpp
// Override via component reference.
UNiagaraFunctionLibrary::OverrideSystemUserVariableStaticMeshComponent(
    NiagaraComp,
    TEXT("User.ScatterMesh"),
    StaticMeshComp
);

// Override with a raw UStaticMesh asset pointer.
UNiagaraFunctionLibrary::OverrideSystemUserVariableStaticMesh(
    NiagaraComp,
    TEXT("User.ScatterMesh"),
    LoadedStaticMesh
);
cpp
// 通过组件引用覆盖。
UNiagaraFunctionLibrary::OverrideSystemUserVariableStaticMeshComponent(
    NiagaraComp,
    TEXT("User.ScatterMesh"),
    StaticMeshComp
);

// 使用原始UStaticMesh资源指针覆盖。
UNiagaraFunctionLibrary::OverrideSystemUserVariableStaticMesh(
    NiagaraComp,
    TEXT("User.ScatterMesh"),
    LoadedStaticMesh
);

Reading / Modifying an Array DI at Runtime

在运行时读取/修改Array DI

cpp
#include "NiagaraDataInterfaceArrayFunctionLibrary.h"

// Push a new float array into the effect (e.g., damage heatmap values).
TArray<float> HeatValues = ComputeHeatValues();
UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayFloat(
    NiagaraComp, FName("User.HeatData"), HeatValues
);

// Update a single element without replacing the whole array.
UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayFloatValue(
    NiagaraComp, FName("User.HeatData"), /*Index=*/ 5, /*Value=*/ 0.9f, /*bSizeToFit=*/ false
);

// Other strongly-typed array setters available:
// SetNiagaraArrayVector, SetNiagaraArrayVector4, SetNiagaraArrayColor,
// SetNiagaraArrayQuat, SetNiagaraArrayInt32, SetNiagaraArrayBool, etc.
cpp
#include "NiagaraDataInterfaceArrayFunctionLibrary.h"

// 将新的float数组传入特效(如伤害热图数据)。
TArray<float> HeatValues = ComputeHeatValues();
UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayFloat(
    NiagaraComp, FName("User.HeatData"), HeatValues
);

// 更新单个元素而不替换整个数组。
UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayFloatValue(
    NiagaraComp, FName("User.HeatData"), /*Index=*/ 5, /*Value=*/ 0.9f, /*bSizeToFit=*/ false
);

// 其他强类型数组设置方法可用:
// SetNiagaraArrayVector, SetNiagaraArrayVector4, SetNiagaraArrayColor,
// SetNiagaraArrayQuat, SetNiagaraArrayInt32, SetNiagaraArrayBool等。

Direct DI Object Access (Advanced)

直接访问DI对象(进阶)

cpp
// Retrieve the actual DI UObject to mutate its properties directly.
// Template variant resolves the cast automatically.
UNiagaraDataInterfaceCurve* CurveDI =
    UNiagaraFunctionLibrary::GetDataInterface<UNiagaraDataInterfaceCurve>(
        NiagaraComp, FName("User.SpeedCurve")
    );

if (CurveDI)
{
    // Mutate curve keyframes at runtime (rebuilds LUT internally).
    CurveDI->Curve.AddKey(0.f, 0.f);
    CurveDI->Curve.AddKey(1.f, 500.f);
    // UpdateLUT() is WITH_EDITORONLY_DATA — only call in editor builds.
#if WITH_EDITORONLY_DATA
    CurveDI->UpdateLUT();
#endif
}

// Non-template variant when the DI class is only known at runtime.
UNiagaraDataInterface* GenericDI =
    UNiagaraFunctionLibrary::GetDataInterface(
        UNiagaraDataInterfaceStaticMesh::StaticClass(),
        NiagaraComp,
        FName("User.ImpactMesh")
    );
See
references/niagara-data-interfaces.md
for the full built-in DI catalogue.
Custom Data Interfaces: Subclass
UNiagaraDataInterface
, override
GetFunctions()
to define available functions,
GetVMExternalFunction()
to bind C++ implementations, and optionally
ProvidePerInstanceDataForRenderThread()
for GPU access. Register in the module's
StartupModule
. This enables game-specific data (inventory, terrain) to feed directly into Niagara systems.

cpp
// 获取实际的DI UObject以直接修改其属性。
// 模板版本会自动处理类型转换。
UNiagaraDataInterfaceCurve* CurveDI =
    UNiagaraFunctionLibrary::GetDataInterface<UNiagaraDataInterfaceCurve>(
        NiagaraComp, FName("User.SpeedCurve")
    );

if (CurveDI)
{
    // 在运行时修改曲线关键帧(内部会重建LUT)。
    CurveDI->Curve.AddKey(0.f, 0.f);
    CurveDI->Curve.AddKey(1.f, 500.f);
    // UpdateLUT()属于WITH_EDITORONLY_DATA — 仅在编辑器构建中调用。
#if WITH_EDITORONLY_DATA
    CurveDI->UpdateLUT();
#endif
}

// 当DI类仅在运行时已知时,使用非模板版本。
UNiagaraDataInterface* GenericDI =
    UNiagaraFunctionLibrary::GetDataInterface(
        UNiagaraDataInterfaceStaticMesh::StaticClass(),
        NiagaraComp,
        FName("User.ImpactMesh")
    );
完整的内置DI目录,请查看
references/niagara-data-interfaces.md
自定义数据接口:继承
UNiagaraDataInterface
,重写
GetFunctions()
定义可用函数,重写
GetVMExternalFunction()
绑定C++实现,可选重写
ProvidePerInstanceDataForRenderThread()
以支持GPU访问。在模块的
StartupModule
中注册。这可让游戏特定数据(如库存、地形)直接传入Niagara系统。

Completion Callbacks

完成回调

cpp
// Bind a C++ delegate to fire when the Niagara system finishes all particles.
// FOnNiagaraSystemFinished is DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(, UNiagaraComponent*)
NiagaraComp->OnSystemFinished.AddDynamic(this, &UMyComponent::OnVFXFinished);

// The callback signature:
UFUNCTION()
void UMyComponent::OnVFXFinished(UNiagaraComponent* FinishedComponent)
{
    // Called on game thread when every particle has expired and the system is done.
    FinishedComponent->DestroyComponent();
    // or return it to pool, notify gameplay, etc.
}

// Unbind when the owner is destroyed to avoid stale delegates.
NiagaraComp->OnSystemFinished.RemoveDynamic(this, &UMyComponent::OnVFXFinished);

cpp
// 将C++委托绑定到Niagara系统所有粒子完成时触发的事件。
// FOnNiagaraSystemFinished的定义为DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(, UNiagaraComponent*)
NiagaraComp->OnSystemFinished.AddDynamic(this, &UMyComponent::OnVFXFinished);

// 回调函数签名:
UFUNCTION()
void UMyComponent::OnVFXFinished(UNiagaraComponent* FinishedComponent)
{
    // 当所有粒子过期且系统完成时,在游戏线程调用。
    FinishedComponent->DestroyComponent();
    // 或将其返回对象池、通知gameplay等。
}

// 当所有者销毁时解除绑定,避免无效委托。
NiagaraComp->OnSystemFinished.RemoveDynamic(this, &UMyComponent::OnVFXFinished);

Performance: Pooling

性能优化:对象池

ENCPoolMethod
controls the pool behavior on every spawn call:
  • AutoRelease
    — component returns to the world pool automatically when the system finishes. Pass
    bAutoDestroy=true
    ; the pool handles actual reclaim.
  • ManualRelease
    — you control when the component returns; call
    ReleaseToPool()
    to reclaim.
  • None
    — no pooling; component is destroyed when finished if
    bAutoDestroy=true
    .
cpp
// AutoRelease: most common for one-shots (explosions, impacts).
UNiagaraComponent* Comp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
    this, ExplosionSystem, Location, FRotator::ZeroRotator,
    FVector(1.f), /*bAutoDestroy=*/true, /*bAutoActivate=*/true,
    ENCPoolMethod::AutoRelease
);

// ManualRelease: for effects you pause/resume (e.g., a beam while a button is held).
// Reclaim by calling ReleaseToPool() when done.
TrailComp->ReleaseToPool();

// Prime the pool before a gameplay-critical moment via FNiagaraWorldManager.
if (FNiagaraWorldManager* NiagaraWorldMan = FNiagaraWorldManager::Get(GetWorld()))
{
    NiagaraWorldMan->GetComponentPool()->PrimePool(ExplosionSystem, GetWorld());
}
Pool capacity is configured per-system in the
UNiagaraSystem
pooling settings (not a global CVar). Relevant global pool CVars:
FX.NiagaraComponentPool.Enable
(1/0) and
FX.NiagaraComponentPool.KillUnusedTime
(seconds before idle components are culled).

ENCPoolMethod
控制每次生成调用时的对象池行为:
  • AutoRelease
    — 系统完成后,组件自动返回世界对象池。需传入
    bAutoDestroy=true
    ;由对象池处理实际回收。
  • ManualRelease
    — 由你控制组件何时返回;调用
    ReleaseToPool()
    进行回收。
  • None
    — 不使用对象池;若
    bAutoDestroy=true
    ,组件完成时会被销毁。
cpp
// AutoRelease: 最常用于一次性特效(爆炸、冲击)。
UNiagaraComponent* Comp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
    this, ExplosionSystem, Location, FRotator::ZeroRotator,
    FVector(1.f), /*bAutoDestroy=*/true, /*bAutoActivate=*/true,
    ENCPoolMethod::AutoRelease
);

// ManualRelease: 用于需要暂停/恢复的特效(如按住按钮时的光束)。
// 完成时调用ReleaseToPool()回收。
TrailComp->ReleaseToPool();

// 在gameplay关键节点前,通过FNiagaraWorldManager预填充对象池。
if (FNiagaraWorldManager* NiagaraWorldMan = FNiagaraWorldManager::Get(GetWorld()))
{
    NiagaraWorldMan->GetComponentPool()->PrimePool(ExplosionSystem, GetWorld());
}
对象池容量在
UNiagaraSystem
的对象池设置中按系统配置(非全局CVar)。相关全局对象池CVar:
FX.NiagaraComponentPool.Enable
(1/0)和
FX.NiagaraComponentPool.KillUnusedTime
(闲置组件被剔除前的秒数)。

Performance: Scalability and LOD

性能优化:可伸缩性与LOD

cpp
// Allow the scalability manager to cull this component based on distance and budget.
NiagaraComp->SetAllowScalability(true);   // default true; disable for gameplay-critical VFX

// Adjust tick behavior to avoid unnecessary dependency resolution.
// ENiagaraTickBehavior::UsePrereqs  — default; ticks after its prerequisites
// ENiagaraTickBehavior::ForceTickFirst — useful for VFX that leads all tick groups
NiagaraComp->SetTickBehavior(ENiagaraTickBehavior::UsePrereqs);
Scalability per platform is configured in the
UNiagaraEffectType
asset assigned to the
UNiagaraSystem
. The effect type defines quality tiers (Low / Medium / High / Epic) and which emitters are stripped at each tier. This is data-driven; no C++ changes needed per platform.
GPU vs CPU simulation trade-offs:
  • CPU sim: particle data is readable/writable from C++ each frame; lower particle counts; supports all DI types.
  • GPU sim: supports hundreds of thousands of particles; DI support is limited (not all CPU-side DIs have GPU equivalents); particle data is not readable back to CPU without readbacks.
Determinism: GPU simulations are inherently non-deterministic. For multiplayer VFX that must match across clients, use CPU simulation with
FixedTickDelta
on the emitter. Cosmetic-only effects should spawn client-side only — skip them on dedicated servers entirely.

cpp
// 允许可伸缩性管理器根据距离和预算剔除该组件。
NiagaraComp->SetAllowScalability(true);   // 默认开启;对gameplay关键VFX可禁用

// 调整tick行为,避免不必要的依赖解析。
// ENiagaraTickBehavior::UsePrereqs  — 默认;在依赖项之后tick
// ENiagaraTickBehavior::ForceTickFirst — 适用于需要领先所有tick组的VFX
NiagaraComp->SetTickBehavior(ENiagaraTickBehavior::UsePrereqs);
各平台的可伸缩性配置在分配给
UNiagaraSystem
UNiagaraEffectType
资源中设置。该特效类型定义了质量等级(低/中/高/史诗)以及每个等级下会被移除的发射器。这是数据驱动的;无需针对每个平台修改C++代码。
GPU与CPU模拟的权衡:
  • CPU模拟:粒子数据每帧可从C++读写;支持的粒子数量较少;支持所有DI类型。
  • GPU模拟:支持数十万粒子;DI支持有限(并非所有CPU端DI都有GPU等效版本);粒子数据无法直接读回CPU,除非使用回读操作。
确定性:GPU模拟本质上是非确定性的。对于需要在客户端间保持一致的多人游戏VFX,需使用CPU模拟并在发射器上启用
FixedTickDelta
。仅用于视觉效果的特效应仅在客户端生成 — 在专用服务器上完全跳过。

Warm-Up, Server Handling, and Events

预热、服务器处理与事件

Pre-simulation (warm-up): seek to a desired age before the effect is visible.
cpp
NiagaraComp->SetDesiredAge(2.5f);    // age in seconds
NiagaraComp->SeekToDesiredAge(2.5f); // perform seek immediately (skips simulation steps)
// FFXSystemSpawnParameters (used by SpawnSystemAtLocationWithParams) also exposes DesiredAge.
Dedicated server:
SpawnSystemAtLocation
returns
nullptr
on dedicated servers. Always null-check the returned component and guard VFX spawns with
!IsRunningDedicatedServer()
where needed.
Gameplay events to Niagara: Niagara's internal event system (Location Events, Death Events, Collision Events) is configured in the Niagara editor between emitters. From C++, trigger a gameplay-driven burst by updating a User bool parameter that the spawn script reads:
cpp
NiagaraComp->SetVariableBool(FName("User.bJustDied"), true);
// Niagara reads this flag on the next spawn script tick and fires the burst.
// There is no C++ API to inject raw Niagara events directly — use User parameters as the bridge.

预模拟(预热):在特效可见前,将其推进到指定时长。
cpp
NiagaraComp->SetDesiredAge(2.5f);    // 时长(秒)
NiagaraComp->SeekToDesiredAge(2.5f); // 立即推进(跳过模拟步骤)
// FFXSystemSpawnParameters(SpawnSystemAtLocationWithParams使用)也支持DesiredAge。
专用服务器
SpawnSystemAtLocation
在专用服务器上返回
nullptr
。始终对返回的组件进行空值检查,并在必要时使用
!IsRunningDedicatedServer()
保护VFX生成代码。
向Niagara发送gameplay事件:Niagara的内部事件系统(位置事件、死亡事件、碰撞事件)在Niagara编辑器中于发射器间配置。在C++中,可通过更新生成脚本读取的User布尔参数来触发gameplay驱动的粒子爆发:
cpp
NiagaraComp->SetVariableBool(FName("User.bJustDied"), true);
// Niagara会在下一次生成脚本tick时读取该标志并触发爆发。
// 目前没有C++ API可直接注入原始Niagara事件 — 使用User参数作为桥梁。

Required Build.cs

所需Build.cs配置

csharp
PublicDependencyModuleNames.AddRange(new string[]
{
    "Core",
    "CoreUObject",
    "Engine",
    "Niagara",         // UNiagaraComponent, UNiagaraFunctionLibrary, UNiagaraSystem
    "NiagaraCore",     // UNiagaraDataInterface base (NiagaraCore module)
});

csharp
PublicDependencyModuleNames.AddRange(new string[]
{
    "Core",
    "CoreUObject",
    "Engine",
    "Niagara",         // UNiagaraComponent、UNiagaraFunctionLibrary、UNiagaraSystem
    "NiagaraCore",     // UNiagaraDataInterface基类(NiagaraCore模块)
});

Common Mistakes and Anti-Patterns

常见错误与反模式

Spawning a new system component every tick
cpp
// BAD: Creates a new UNiagaraComponent each frame. Destroys performance.
void AMyActor::Tick(float DeltaTime)
{
    UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, TrailFX, GetActorLocation());
}

// GOOD: Create the component once in BeginPlay or constructor; activate/deactivate as needed.
Wrong parameter namespace
cpp
// BAD: "Emitter.Speed" is an internal emitter parameter; cannot be set from C++.
NiagaraComp->SetVariableFloat(FName("Emitter.Speed"), 300.f);

// GOOD: The Niagara author must expose the parameter under "User.*".
NiagaraComp->SetVariableFloat(FName("User.Speed"), 300.f);
Type mismatch between C++ and Niagara
cpp
// BAD: Calling SetVariableVec3 on a parameter that is typed as "Color" in Niagara.
// Silently fails — no runtime error, parameter is just not updated.
NiagaraComp->SetVariableVec3(FName("User.TintColor"), FVector(1, 0, 0));

// GOOD: Match the C++ call to the Niagara parameter type.
NiagaraComp->SetVariableLinearColor(FName("User.TintColor"), FLinearColor::Red);
Setting parameters after system completes
cpp
// The component is valid but the system instance may be inactive. Check before setting.
if (NiagaraComp && NiagaraComp->IsActive())
{
    NiagaraComp->SetVariableFloat(FName("User.Intensity"), NewIntensity);
}
Forgetting to check nullptr on spawn (especially on server)
cpp
UNiagaraComponent* Comp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(...);
// Comp can be nullptr on dedicated server or when bPreCullCheck rejects the spawn.
if (Comp)
{
    Comp->SetVariableFloat(FName("User.Scale"), 2.f);
}
Not removing delegates before destruction
cpp
// BAD: OnSystemFinished fires after owner is garbage collected → crash.
// GOOD: Always RemoveDynamic in BeginDestroy or EndPlay.
void AMyActor::EndPlay(const EEndPlayReason::Type Reason)
{
    if (NiagaraComp)
    {
        NiagaraComp->OnSystemFinished.RemoveDynamic(this, &AMyActor::OnVFXFinished);
    }
    Super::EndPlay(Reason);
}
Niagara Fluids (experimental): The Niagara Fluids plugin provides GPU-based fluid and gas simulations. It is experimental, GPU-only, and carries a high performance cost — profile carefully before shipping and restrict use to hero effects where visual impact justifies the budget.
World space vs local space: Use local-space simulation for effects attached to moving actors (particles inherit the parent component's transform and move with it). Use world-space simulation for effects that should remain stationary after emission (e.g., a ground impact crater where particles should not follow a moving actor). The space setting lives on each emitter in the Niagara editor.

每帧生成新的系统组件
cpp
// 错误:每帧创建新的UNiagaraComponent,严重影响性能。
void AMyActor::Tick(float DeltaTime)
{
    UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, TrailFX, GetActorLocation());
}

// 正确:在BeginPlay或构造函数中创建一次组件;按需激活/停用。
错误的参数命名空间
cpp
// 错误:"Emitter.Speed"是发射器内部参数,无法通过C++设置。
NiagaraComp->SetVariableFloat(FName("Emitter.Speed"), 300.f);

// 正确:Niagara开发者必须将参数暴露在"User.*"下。
NiagaraComp->SetVariableFloat(FName("User.Speed"), 300.f);
C++与Niagara类型不匹配
cpp
// 错误:对Niagara中类型为"Color"的参数调用SetVariableVec3。
// 会静默失败 — 无运行时错误,但参数不会更新。
NiagaraComp->SetVariableVec3(FName("User.TintColor"), FVector(1, 0, 0));

// 正确:C++调用需匹配Niagara参数类型。
NiagaraComp->SetVariableLinearColor(FName("User.TintColor"), FLinearColor::Red);
系统完成后设置参数
cpp
// 组件可能有效,但系统实例可能已停用。设置前请检查。
if (NiagaraComp && NiagaraComp->IsActive())
{
    NiagaraComp->SetVariableFloat(FName("User.Intensity"), NewIntensity);
}
生成时忘记检查空指针(尤其是在服务器上)
cpp
UNiagaraComponent* Comp = UNiagaraFunctionLibrary::SpawnSystemAtLocation(...);
// Comp在专用服务器或bPreCullCheck拒绝生成时可能为nullptr。
if (Comp)
{
    Comp->SetVariableFloat(FName("User.Scale"), 2.f);
}
销毁前未移除委托
cpp
// 错误:OnSystemFinished在所有者被垃圾回收后触发 → 崩溃。
// 正确:始终在BeginDestroy或EndPlay中调用RemoveDynamic。
void AMyActor::EndPlay(const EEndPlayReason::Type Reason)
{
    if (NiagaraComp)
    {
        NiagaraComp->OnSystemFinished.RemoveDynamic(this, &AMyActor::OnVFXFinished);
    }
    Super::EndPlay(Reason);
}
Niagara流体(实验性):Niagara流体插件提供基于GPU的流体和气体模拟。它处于实验阶段,仅支持GPU,且性能开销高 — 发布前需仔细分析性能,仅在视觉效果价值超过性能预算的核心特效中使用。
世界空间与局部空间:对于附着在移动Actor上的特效,使用局部空间模拟(粒子继承父组件的变换并随其移动)。对于发射后应保持静止的特效(如地面冲击 crater,粒子不应跟随移动Actor),使用世界空间模拟。空间设置在Niagara编辑器中每个发射器的属性里。

Related Skills

相关技能

  • ue-actor-component-architecture
    — component creation, attachment, lifecycle
  • ue-materials-rendering
    — particle material setup, dynamic material instances
  • ue-cpp-foundations
    — UObject lifetime, delegates, UPROPERTY references
  • ue-actor-component-architecture
    — 组件创建、附着、生命周期
  • ue-materials-rendering
    — 粒子材质设置、动态材质实例
  • ue-cpp-foundations
    — UObject生命周期、委托、UPROPERTY引用