粒子发射器技术指南

添加新粒子发射器类型的程序员指南。

Windows
MacOS
Linux

创建新的发射器类型需要自定义的"ParticleEmitterInstance"和"ParticleModuleTypeData"。 本指南解释了其中的关键方面,并介绍了创建新的自定义发射器类型的过程。

粒子发射器参考

"ParticleEmitterInstance"变量和函数以及"ParticleModuleTypeData"类都将在本节中进行说明。

ParticleEmitterInstance结构体

"ParticleEmitterInstance"是单个粒子发射器,代表"ParticleSystemComponent"中的效果实例。

成员变量

"ParticleEmitterInstance"结构体包含以下公共变量:

变量

概述

"StaticType"

这是发射器实例的类型辨识符。它用于识别发射器,并提供安全的投射功能。

"SpriteTemplate"

指向实例所基于的"UParticleEmitter"模板的指针。对于自定义发射器类型,所需的任何特定数据通常存储在"TypeDataModule"中,稍后将在本文档中详细介绍。

"Component"

指向 拥有 发射器实例的"UParticleSystemComponent"的指针。

"CurrentLODLevelIndex"

当前设置的LOD级别的索引。

"CurrentLODLevel"

指向当前活动的"UParticleLODLevel"的指针。

"TypeDataOffset"

粒子数据中与"TypeData"有效荷载的偏移量。用于方便地检索特定于发射器类型的逐粒子数据。

"SubUVDataOffset"

粒子数据中特定于SubUV的有效荷载的偏移量。只有当subUV内插模式未设置为"无(NONE)"时才相关。

"Location"

给出发射器实例位置的FVector。

"KillOnDeactivate"

如果为"true",发射器实例将在停用时被终止(删除)。

"bKillOnCompleted"

如果为"true",发射器实例将在完成其循环时被终止(删除)。

"ParticleData"

指向粒子数据阵列的指针。

"ParticleIndices"

指向粒子索引阵列的指针。用于提供使用粒子数据的循环系统。

"ModuleOffsetMap"

将指向其偏移量的模块指针映射到粒子有效荷载数据。

"InstanceData"

指向逐实例数据阵列的指针。

"InstancePayloadSize"

逐实例数据阵列的大小。

"ModuleInstanceOffsetMap"

将指向其偏移量的模块指针映射到逐实例数据。

"PayloadOffset"

粒子有效荷载数据开始时的偏移量。

"ParticleSize"

一个粒子的总大小,单位为字节。

"ParticleStride"

ParticleData阵列中粒子之间的步长(允许潜在地对齐粒子数据)。

"ActiveParticles"

当前在发射器中活动的粒子数。

"MaxActiveParticles"

粒子数据阵列中可容纳的最大粒子数。

"SpawnFraction"

上次帧生成后剩余的时间的部分。

"SecondsSinceCreation"

自创建实例以来经过的秒数。

"EmitterTime"

时间在发射器单回路中的 位置

"OldLocation"

发射器先前的位置。

"ParticleBoundingBox"

发射器的边界框。

"BurstFired"

用于跟踪突发事件的一组条目。

"LoopCount"

实例完成的循环数。

"IsRenderDataDirty"

指示渲染数据是否为脏数据的标记。

"Module_AxisLock"

AxisLock模块(如果存在)。保持以避免每一个标记都搜索它。

"EmitterDuration"

实例的当前持续时间。

"EmitterDurations"

实例的每个LOD级别上的持续时间阵列。

"TrianglesToRender"

发射器将渲染此帧的三角形数。

"MaxVertexIndex"

渲染时发射器将访问的最大顶点索引。

成员函数

ParticleEmitterInstance结构体包含以下成员函数,它们提供了覆盖基本功能的机会:

函数

概述

void SetKillOnDeactivate(bool bKill)

将"KillOnDeactivate"标记设置为给定值。

void SetKillOnCompleted(bool bKill)

将"bKillOnCompleted"标记设置为给定值。

void InitParameters(UParticleEmitter<em> InTemplate, UParticleSystemComponent</em> InComponent)

给定所提供的"ParticleEmitter"模板和父"ParticleSystemComponent",初始化结构体的参数。

void Init()

调用以初始化实例。

void Resize(int32 NewMaxActiveParticles, bool bSetMaxActiveCount = true)

调用以将粒子数据阵列的大小调整到给定的"MaxActiveParticles"数量。

void Tick(float DeltaTime, bool bSuppressSpawning)

用给定的时间片标记实例。如果"bSuppressSpawning"为"true",不要生成任何新粒子。

void Rewind()

倒回发射器实例。

FBox GetBoundingBox()

返回实例的边界框。

void UpdateBoundingBox(float DeltaTime)

更新实例的边界框。这是帧进行粒子最终定位的位置,因为所有的更新至此已保证已经发生了。

uint32 RequiredBytes()

检索发射器所需的逐粒子字节数。

uint8<em> GetModuleInstanceData(UParticleModule</em> Module)

获取指向为给定模块分配的逐实例数据的指针。

uint32 CalculateParticleStride(uint32 ParticleSize)

为本实例计算单个粒子的步长。

void ResetBurstList()

重置实例的突发列表信息。

float GetCurrentBurstRateOffset(float&amp; DeltaTime, int32&amp; Burst)

获取当前突发速率偏移量 - 人为地增加"DeltaTime"来生成突发。

float Spawn(float OldLeftover, float Rate, float DeltaTime, int32 Burst = 0, float BurstTime = 0.0f)

给定当前时间片(DeltaTime),为实例生成粒子。把最后剩下的(OldLeftover)内容考虑进去。

void PreSpawn(FBaseParticle* Particle)

处理此实例中粒子所需的任何预生成操作。

bool HasCompleted()

如果实例已完成运行,则返回"true"。

void PostSpawn(FBaseParticle* Particle, float InterpolationPercentage, float SpawnTime)

处理此实例中粒子所需的任何 生成后 操作。

void KillParticles()

消灭任何死粒子 - 只需从活动阵列中删除它们。

void RemovedFromScene()

当从场景中移除实例时调用。

FBaseParticle* GetParticle(int32 Index)

检索给定索引处的粒子。

FDynamicEmitterDataBase* GetDynamicData(bool bSelected)

获取用于为此帧渲染此实例的动态数据。

void GetAllocatedSize(int32&amp; InNum, int32&amp; InMax)

获取发射器实例的分配大小 - 用于内存跟踪。

ParticleModuleTypeDataBase类

"ParticleModuleTypeDataBase"类提供了生成自定义发射器实例的机制,用于创建引擎中使用的 ParticleSystem。例如,"ParticleModuleTypeDataMesh"类将为ParticleSystem创建一个"FParticleMeshEmitterInstance"。

成员函数

"ParticleModuleTypeDataBase"结构体包含以下公共函数,这些函数有助于生成自定义发射器:

函数

概述

FParticleEmitterInstance<em> CreateInstance(UParticleEmitter</em> InEmitterParent, UParticleSystemComponent* InComponent)

这是在UE4粒子系统中覆盖发射器实例创建的关键。当在正在实例化的UParticleEmitter中发现TypeData模块时,将调用该函数。在这个函数中,应该创建并返回所需的FParticleEmitterInstance结构。

void SetToSensibleDefaults()

在模块首次创建时调用。此函数允许您设置有意义的默认值。

void PreSpawn(FParticleEmitterInstance<em> Owner, FBaseParticle</em> Particle)

在发射器实例的PreSpawn函数期间调用,此函数允许对新生成的粒子进行特定于TypeDataModule的设置。

void PreUpdate(FParticleEmitterInstance* Owner, int32 Offset, float DeltaTime)

在更新发射器实例之前调用,此函数允许处理使用发射器中包含的每个模块更新粒子之前需要进行的任何更新。

void PostUpdate(FParticleEmitterInstance* Owner, int32 Offset, float DeltaTime)

在更新发射器实例之后调用,此函数允许处理使用发射器中包含的每个模块更新粒子之后需要进行的任何更新。

bool SupportsSpecificScreenAlignmentFlags() const

允许覆盖在粒子发射器上发现的屏幕对齐标志。目前仅用于网格体发射器。

示例粒子发射器

编写自定义发射器实例需要两个步骤。首先,需要创建一个"TypeDataModule",它将为发射器实例提供特定的数据,并在适当的时候生成数据。例如,除了"父"粒子系统组件的旋转外,还将创建一个发射器实例来旋转发射器实例。

TypeDataModule声明

第一步是创建"TypeDataModule",它将生成新的发射器实例类型。

UCLASS(editinlinenew, collapsecategories, hidecategories=Object)
public class UParticleModuleTypeDataSpinner : public UParticleModuleTypeDataBase
{

    /** 

        *  在给定时间旋转发射器实例(以完整的旋转方式)   
        *  的数量。
        */

    UPROPERTY(Category=Spinner)
    rawdistributionvector Spin;

(#ifcpp)

if CPP

    /**
    *   创建自定义ParticleEmitterInstance。
    *
    *   @param  InEmitterParent           保存这个TypeData模块的UParticleEmitter。
    *   @param  InComponent               "拥有"正在创建的发射器实例的UParticleSystemComponent。
    *   @return FParticleEmitterInstance* 创建的发射器实例。
    */
    virtual FParticleEmitterInstance*   CreateInstance(UParticleEmitter* InEmitterParent, UParticleSystemComponent* InComponent);
#endif
}       

TypeDataModule实施

"TypeDataModule"的构造函数创建一个"UDistributionVectorConstant"来分配给"Spin"变量。

UParticleModuleTypeDataSpinner::UParticleModuleTypeDataSpinner(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
    UDistributionVectorConstant* DistributionSpin = ConstructorHelpers::CreateDefaultSubobject<UDistributionVectorConstant>(this, TEXT("DistributionSpin"));
    DistributionSpin->Constant = FVector(0.0f, 0.0f, 0.0f);
    Spin.Distribution = DistributionSpin;
}

"CreateInstance()"函数由保存发射器实例的"ParticleSystemComponent"调用。在这里,"TypeDataModule"可以创建系统要使用的任何类型的"ParticleEmitterInstance"。

FParticleEmitterInstance* UParticleModuleTypeDataSpinner::CreateInstance(UParticleEmitter* InEmitterParent, UParticleSystemComponent* InComponent)
{
   // 创建我们的Spinner发射器实例。
   FParticleSpinnerEmitterInstance* SpinnerInst = ::new FParticleSpinnerEmitterInstance();
   if (SpinnerInst)
   {
      // 初始化发射器的参数。
      SpinnerInst->InitParameters(InEmitterParent, InComponent);
      return SpinnerInst;
   }

   // 失败了。返回NULL以生成默认的sprite发射器,或断言。
   return NULL;
}

在本例中,将生成"FParticleSpinnerEmitterInstance"的实例。它派生自"FParticleSpriteEmitterInstance",以尽可能利用现有代码。

粒子发射器声明

"FParticleSpinnerEmitterInstance"自定义发射器实例结构定义如下:

struct FParticleSpinnerEmitterInstance : public FParticleSpriteEmitterInstance
{
   /** 指向spinner TypeDatModule的指针。*/
   UParticleModuleTypeDataSpinner* SpinnerTypeData;
   /** 在标记调用期间使用的旋转。*/
   FVector CurrentRotation;
   /** 组件的旋转。*/
   FRotator ComponentRotation;

   /** 构造函数   */
   FParticleSpinnerEmitterInstance();

   virtual void InitParameters(UParticleEmitter* InTemplate, UParticleSystemComponent* InComponent);
   virtual void Tick(float DeltaTime, bool bSuppressSpawning);
   virtual void UpdateBoundingBox(float DeltaTime);

   /**
    *   调整组件旋转以考虑实例旋转。
    */
   void AdjustComponentRotation();
   /**
    *   恢复组件旋转。
    */
   void RestoreComponentRotation();
};

下面的成员变量包含在"FParticleSpinnerEmitterInstance"中:

变量

概述

"SpinnerTypeData"

指向"UParticleModuleTypeDataSpinner"的指针。它直接保留,以避免在每次需要访问TypeData模块时转换它。

"CurrentRotation"

跟踪发射器实例当前旋转的"FVector"。获取"Tick()"/"TickEditor()"来更新旋转,并存储以在"UpdateBoundingBox()"函数期间使用。

粒子发射器实施

以下成员函数是为我们的自定义发射器实例实施的:

函数

FParticleSpinnerEmitterInstance()

构造函数的代码只是将"SpinnerTypeData"初始化为NULL,将"CurrentRotation"初始化为"(0.0f, 0.0f, 0.0f)"。

FParticleSpinnerEmitterInstance::FParticleSpinnerEmitterInstance() :

     FParticleSpriteEmitterInstance()
   , SpinnerTypeData(NULL)
{
}

virtual void InitParameters(UParticleEmitter<em> InTemplate, UParticleSystemComponent</em> InComponent, bool bClearResources = true)

函数"InitParameters()"调用超级版本来执行标准功能,然后将"TypeDataModule"指针强制转换为"UParticleModuleTypeDataSpinner",以避免每次访问它时都要转换。

void FParticleSpinnerEmitterInstance::InitParameters(UParticleEmitter InTemplate, UParticleSystemComponent InComponent)

{
   // 调用超级InitParameters。
   FParticleEmitterInstance::InitParameters(InTemplate, InComponent);

   // 获取类型数据模块
   UParticleLODLevel* LODLevel   = InTemplate->GetCurrentLODLevel(this);
   check(LODLevel);
   SpinnerTypeData = CastChecked<UParticleModuleTypeDataSpinner>(LODLevel->TypeDataModule);
}

virtual void Tick(float DeltaTime, bool bSuppressSpawning)

"Tick()"函数的作用是生成和更新实例中的粒子。首先,它使用EmitterTime从"SpinnerTypeData"分布中获取当前旋转。由于 组件的"LocalToWorld"可以在各个模块的"Spawn()"/"Update()"函数中使用,所以我们需要确保发射器实例的旋转被考虑在内。这是通过保存组件的"旋转",然后向其中添加发射器实例数量来实现的。然后更新组件转换以包含新的旋转。然后,通过调用超级"Tick()"函数,发射器实例像正常一样被 选中。然后恢复组件"旋转"。

void FParticleSpinnerEmitterInstance::Tick(float DeltaTime, bool bSuppressSpawning)

{
   // 更新我们的当前旋转
   check(SpinnerTypeData);
   CurrentRotation = SpinnerTypeData->Spin.GetValue(EmitterTime, Component);

   // 调整组件旋转
   AdjustComponentRotation();

   // 调用超级标记
   FParticleEmitterInstance::Tick(DeltaTime, bSuppressSpawning);

   // 恢复组件旋转
   RestoreComponentRotation();
}

virtual void UpdateBoundingBox(float DeltaTime)

在这种情况下,必须覆盖"UpdateBoundingBox()"函数,以确保在"bUseLocalSpace"为"true"时考虑发射器实例的旋转。

void FParticleSpinnerEmitterInstance::UpdateBoundingBox(float DeltaTime)

{
    // 不幸的是,我们必须完全覆盖UpdateBoundingBox函数,
    // 以确保我们的旋转被考虑在内...
    // 
    // 除了最后一段代码(其中考虑到了bUseLocalSpace标志)之外, 
    // 该函数与FParticleSpriteEmitterInstance版本是
    // 相同的。
    if (Component)
    {
        // 将组件缩放纳入考虑
        FVector Scale = FVector(1.0f, 1.0f, 1.0f);
        Scale *= Component->Scale * Component->Scale3D;
        AActor* Actor = Component->GetOwner();
        if (Actor && !Component->AbsoluteScale)
        {
            Scale *= Actor->DrawScale * Actor->DrawScale3D;
        }           
        float MaxSizeScale = 1.0f;
        FVector NewLocation;
        float NewRotation;
        ParticleBoundingBox.Init();
        // 对于每个粒子,适当偏移方框 
        for (int32 i=0; i<ActiveParticles; i++)
        {
            DECLARE_PARTICLE(Particle, ParticleData + ParticleStride * ParticleIndices[i]);
            // 做线性积分器并更新边界框
            // 做角积分器,并将结果限定在+/- 2 PI范围内
            Particle.OldLocation = Particle.Location;
            if ((Particle.Flags & STATE_Particle_Freeze) == 0)
            {
                if ((Particle.Flags & STATE_Particle_FreezeTranslation) == 0)
                {
                    NewLocation = Particle.Location + (DeltaTime * Particle.Velocity);
                }
                else
                {
                    NewLocation = Particle.Location;
                }
                if ((Particle.Flags & STATE_Particle_FreezeRotation) == 0)
                {
                    NewRotation = (DeltaTime * Particle.RotationRate) + Particle.Rotation;
                }
                else
                {
                    NewRotation = Particle.Rotation;
                }
            }
            else
            {
                NewLocation = Particle.Location;
                NewRotation = Particle.Rotation;
            }
            FVector Size = Particle.Size * Scale;
            MaxSizeScale = Max(MaxSizeScale, Size.GetAbsMax()); //@todo粒子:这做了很多比较,可以使用SSE/Altivec避免这些比较。
            Particle.Rotation = appFmod(NewRotation, 2.f*(float)PI);
            Particle.Location = NewLocation;
            ParticleBoundingBox += NewLocation;
        }
        ParticleBoundingBox = ParticleBoundingBox.ExpandBy(MaxSizeScale);
        // 如果发射器使用局部空间坐标系,则将边界框转换为世界空间。
        UParticleLODLevel* LODLevel = SpriteTemplate->GetCurrentLODLevel(this);
        check(LODLevel);
        if (LODLevel->RequiredModule->bUseLocalSpace) 
        {
            // 调整组件旋转
            AdjustComponentRotation();
            // 转换边界框
            ParticleBoundingBox = ParticleBoundingBox.TransformBy(Component->LocalToWorld);
            // 恢复组件旋转
            RestoreComponentRotation();
        }
    }
}

void FParticleSpinnerEmitterInstance::AdjustComponentRotation()

"AdjustComponentRotation()"函数将更改组件"LocalToWorld"来解释发射器实例的旋转。

void FParticleSpinnerEmitterInstance::AdjustComponentRotation()

{
   // 保存真旋转
   ComponentRotation = Component->Rotation;
   // 因为组件的LocalToWorld可以用于各个模块的更新函数中,
   // 我们需要欺骗它,这样我们的实例旋转就被考虑进去了。
   // 我们需要重构LocalToWorld组件,以便
   // 在正确的位置考虑旋转。
   FVector CurrRotInDegrees = CurrentRotation * 360.0f;
   FRotator RotationRot = FRotator::MakeFromEuler(CurrRotInDegrees);
   Component->Rotation += RotationRot;
   Component->SetTransformedToWorld();
}

void FParticleSpinnerEmitterInstance::RestoreComponentRotation()

"RestoreComponentRotation()"函数将从组件LocalToWorld中删除发射器实例旋转。

void FParticleSpinnerEmitterInstance::RestoreComponentRotation()

{
   // 恢复组件旋转
   Component->Rotation = ComponentRotation;
   Component->SetTransformedToWorld();
}

结果

下面的屏幕截图演示了正在运行的Spinner发射器实例。设置如下:

  • Spin 分布被设置为一个常量曲线,其中一个点位于 (Time=0,X=0,Y=0,Z=0) 处,另一个点位于 (Time=1,X=0,Y=0,Z=1) 处,导致实例在发射器的整个生命周期内以可变速率绕Z轴旋转。

  • 使用 InitialVelocity 模块并将常量分布设置为 (X=100,Y=100,Z=100),这样所有的粒子都将以直线流的形式发射(不考虑实例的旋转)。

  • 使用 InitialColor 模块并将 StartColor 设置为常量曲线,其中一个点位于 (Time=0,R=1,G=0,B=0),另一个点位于 (Time=1,R=0,G=0,B=1),导致粒子在发射器的整个生命周期内从红色变为蓝色。

Spinner A

Spinner B

Tags
Select Skin
Light
Dark

Welcome to the new Unreal Engine 4 Documentation site!

We're working on lots of new features including a feedback system so you can tell us how we are doing. It's not quite ready for use in the wild yet, so head over to the Documentation Feedback forum to tell us about this page or call out any issues you are encountering in the meantime.

We'll be sure to let you know when the new system is up and running.

Post Feedback