UE4 Particle Emitter Technical Guide

1649 篇文章 12 订阅
1623 篇文章 23 订阅

Particle Emitter Technical Guide

Creating new types of emitters requires a custom ParticleEmitterInstance and ParticleModuleTypeData. This guide explains the key aspects of each of these and walks through the creation of a new custom emitter type.

Particle Emitter Reference

The ParticleEmitterInstance variables and functions, as well as the ParticleModuleTypeData class, are explained in this section.

The ParticleEmitterInstance Struct

ParticleEmitterInstance is a single particle emitter that represents an instance of an effect in a ParticleSystemComponent.

Member Variables

The ParticleEmitterInstance struct contains the following public variables:

Variable Overview
StaticType This is the type identifier of the emitter instance. It is used to identify the emitter as well as provide safe casting functionality.
SpriteTemplate Pointer to the UParticleSpriteEmitter template that the instance is based on. In the case of custom emitter types, any specific data required is usually stored in the TypeDataModule which will be detailed later in this document.
Component Pointer to the UParticleSystemComponent that owns the emitter instance.
CurrentLODLevelIndex The index of the currently set LOD level.
CurrentLODLevel Pointer to the UParticleLODLevel that is currently active.
TypeDataOffset The offset to the TypeData payload in the particle data. Used to easily retrieve per-particle data that is specific to the type of emitter.
SubUVDataOffset The offset to SubUV-specific payload in the particle data. Only relevant when the subUV interpolation mode is not set to NONE.
Location An FVector giving the location of the emitter instance.
KillOnDeactivate If true, the emitter instance will be killed (deleted) when it is deactivated.
bKillOnCompleted If true, the emitter instance will be killed (deleted) when it completes its cycle.
ParticleData Pointer to the particle data array.
ParticleIndices Pointer to the particle index array. Used to provide a round-robin system of using particle data.
ModuleOffsetMap Maps module pointers to their offset into the particle payload data.
InstanceData Pointer to the per-instance data array.
InstancePayloadSize The size of the per-instance data array.
ModuleInstanceOffsetMap Maps module pointers to their offset into the per-instance data.
PayloadOffset The offset to the start of the particle payload data.
ParticleSize The total size of a particle in bytes.
ParticleStride The stride between particles in the ParticleData array (to allow for potentially aligning particle data).
ActiveParticles The number of particles that are currently active in the emitter.
MaxActiveParticles The maximum number of particles that can be held in the particle data array.
SpawnFraction The fraction of time left over from the last frame spawning.
SecondsSinceCreation The number of seconds that have passed since the instance was created.
EmitterTime The position of the time in a single loop of the emitter.
OldLocation The previous location of the emitter.
ParticleBoundingBox The bounding box for the emitter.
BurstFired An array of entries for tracking the firing of bursts.
LoopCount The number of loops completed by the instance.
IsRenderDataDirty Flag indicating if the render data is dirty.
Module_AxisLock The AxisLock module, if present. Held to avoid searching for it each Tick.
EmitterDuration The current duration of the instance.
EmitterDurations An array of the durations at each LOD level of the instance.
TrianglesToRender The number of triangles the emitter will render this frame.
MaxVertexIndex The max vertex index that will be accessed by the emitter when rendering.
Member Functions

The ParticleEmitterInstance struct contains the following member functions which provide the opportunity to override the base functionality:

Function Overview

void SetKillOnDeactivate(bool bKill)

Sets the KillOnDeactivate flag to the given value.

void SetKillOnCompleted(bool bKill)

Sets the bKillOnCompleted flag to the given value.

void InitParameters(UParticleEmitter* InTemplate, UParticleSystemComponent* InComponent, bool bClearResources = true)

Initializes the parameters for the structure given the providedParticleEmitter template and parent ParticleSystemComponent.

void Init()

Called to Initialize the instance.

void Resize(int32 NewMaxActiveParticles, bool bSetMaxActiveCount = true)

Called to resize the particle data array to the given number ofMaxActiveParticles.

void Tick(float DeltaTime, bool bSuppressSpawning)

Tick the instance with the given time slice. If bSuppressSpawning is true, do not spawn any new particles.

void Rewind()

Rewind the emitter instance.

FBox GetBoundingBox()

Return the bounding box for the instance.

void UpdateBoundingBox(float DeltaTime)

Update the bounding box for the instance. This is where the final positioning of the particles takes place for the frame as all updates are guaranteed to have occurred by now.

uint32 RequiredBytes()

Retrieve the number of per-particle bytes that the emitter requires.

uint8* GetModuleInstanceData(UParticleModule* Module)

Get the pointer to the per-instance data allocated for the given module.

uint32 CalculateParticleStride(uint32 ParticleSize)

Calculate the stride of a single particle for this instance.

void ResetBurstList()

Reset the burst list information for the instance.

float GetCurrentBurstRateOffset(float& DeltaTime, int32& Burst)

Get the current burst rate offset - artificially increases DeltaTime to generate the bursts.

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

Spawn particles for the instance given the current time slice (DeltaTime). Take the leftover from last from (OldLeftover) into account.

void PreSpawn(FBaseParticle* Particle)

Handle any pre-spawning actions required for the particles in this instance.

bool HasCompleted()

Return true if the instance has completed its run.

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

Handle any post-spawning actions required for the particles in this instance.

void KillParticles()

Kill off any dead particle - simply remove them from the active array.

void RemovedFromScene()

Called when the instance is removed from the scene.

FBaseParticle* GetParticle(int32 Index)

Retrieve the particle at the given Index.

FDynamicEmitterDataBase* GetDynamicData(bool bSelected)

Get the dynamic data for rendering this instance this frame.

void GetAllocatedSize(int32& InNum, int32& InMax)

Get the allocated size of the emitter instance - for memory tracking.

The ParticleModuleTypeDataBase class

The ParticleModuleTypeDataBase class provides the mechanism for generating custom emitter instances when creating a ParticleSystemfor use in the engine. For example, the ParticleModuleTypeDataMesh class will result in a FParticleMeshEmitterInstance getting created for the ParticleSystem.

Member Functions

The ParticleModuleTypeDataBase struct contains the following public functions which aid in generating custom emitters:

Function Overview

FParticleEmitterInstance* CreateInstance(UParticleEmitter* InEmitterParent, UParticleSystemComponent* InComponent)

This is the key to overriding emitter instance creation in the UE4 particle system. The function is called when a TypeData module is found in a UParticleEmitter that is being instantiated. In this function, the desired FParticleEmitterInstance structure should be created and returned.

void SetToSensibleDefaults()

Called when the module is first created. This function allows for you to set default values that make sense.

void PreSpawn(FParticleEmitterInstance* Owner, FBaseParticle* Particle)

Called during the PreSpawn function of the emitter instance, this function allows for TypeDataModule-specific setup of a newly spawned particle.

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

Called prior to any updating of the emitter instance, this function allows for handling any updates that need to occur prior to updating particles with each module contained in the emitter.

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

Called after the updating of the emitter instance, this function allows for handling any updates that need to occur after updating particles with each module contained in the emitter.

bool SupportsSpecificScreenAlignmentFlags() const

Allows for overriding the screen alignment flags found on the particle emitter. Currently only used by the mesh emitter.

Example Particle Emitter

Writing a custom emitter instance is a two-step process. First, a TypeDataModule needs to be created which will provide the specific data for your emitter instance, as well as generate it at the appropriate time. As an example, an emitter instance will be created that spins the emitter instance in addition to the rotation of the 'parent' particle system component.

TypeDataModule Declaration

The first step is to create the TypeDataModule that will generate the new emitter instance type.

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

    /** 

        *  The amount to spin the emitter instance (in complete rotations) at   
        *  the given time.
        */

    UPROPERTY(Category=Spinner)
    rawdistributionvector Spin;

#if CPP
    /**
    *   Create the custom ParticleEmitterInstance.
    *
    *   @param  InEmitterParent           The UParticleEmitter that holds this TypeData module.
    *   @param  InComponent               The UParticleSystemComponent that 'owns' the emitter instance being created.
    *   @return FParticleEmitterInstance* The create emitter instance.
    */
    virtual FParticleEmitterInstance*   CreateInstance(UParticleEmitter* InEmitterParent, UParticleSystemComponent* InComponent);
#endif
}       

TypeDataModule Implementation

The constructor for the TypeDataModule creates a UDistributionVectorConstant to assign to the Spin variable.

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

The CreateInstance() function is called by the ParticleSystemComponent that will hold the emitter instance. This is where theTypeDataModule can create any type of ParticleEmitterInstance to be utilized by the system.

FParticleEmitterInstance* UParticleModuleTypeDataSpinner::CreateInstance(UParticleEmitter* InEmitterParent, UParticleSystemComponent* InComponent)
{
   // Create our Spinner emitter instance.
   FParticleSpinnerEmitterInstance* SpinnerInst = ::new FParticleSpinnerEmitterInstance();
   if (SpinnerInst)
   {
      // Initialize the parameters for the emitter.
      SpinnerInst->InitParameters(InEmitterParent, InComponent);
      return SpinnerInst;
   }

   // We failed. Return NULL to let a default sprite emitter be generated, or assert.
   return NULL;
}

In this example, an instance of the FParticleSpinnerEmitterInstance will be generated. It is derived from theFParticleSpriteEmitterInstance to leverage as much existing code as possible.

Particle Emitter Declaration

The FParticleSpinnerEmitterInstance custom emitter instance structure is defined as follows:

struct FParticleSpinnerEmitterInstance : public FParticleSpriteEmitterInstance
{
   /** Pointer to the spinner TypeDatModule.         */
   UParticleModuleTypeDataSpinner* SpinnerTypeData;
   /** The rotation that was used during the Tick call.   */
   FVector CurrentRotation;
   /** The rotation of the component.            */
   FRotator ComponentRotation;

   /** Constructor   */
   FParticleSpinnerEmitterInstance();

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

   /**
    *   Adjust the component rotation to take the instance rotation into account.
    */
   void AdjustComponentRotation();
   /**
    *   Restore the component rotation.
    */
   void RestoreComponentRotation();
};

The following member variables are contained in the FParticleSpinnerEmitterInstance:

Variable Overview
SpinnerTypeData A pointer to the UParticleModuleTypeDataSpinner. It is held on to directly to avoid casting the TypeData module each time we need to access it.
CurrentRotation An FVector which tracks the current rotation of the emitter instance. Grabbed in Tick()/TickEditor() to update the rotation, and stored for use during the UpdateBoundingBox() function.

Particle Emitter Implementation

The following member functions are implemented for our custom emitter instance:

Function

FParticleSpinnerEmitterInstance()

The code for the constructor simply initializes the SpinnerTypeData to NULL and the CurrentRotation to (0.0f, 0.0f, 0.0f).
FParticleSpinnerEmitterInstance::FParticleSpinnerEmitterInstance() :
     FParticleSpriteEmitterInstance()
   , SpinnerTypeData(NULL)
{
}

virtual void InitParameters(UParticleEmitter* InTemplate, UParticleSystemComponent* InComponent, bool bClearResources = true)

The InitParameters() function calls the Super version to perform the standard functionality, and then casts the TypeDataModule pointer to anUParticleModuleTypeDataSpinner to avoid having to cast each time it is accessed.
void FParticleSpinnerEmitterInstance::InitParameters(UParticleEmitter* InTemplate, UParticleSystemComponent* InComponent, bool bClearResources)
{
   // Call the super InitParameters.
   FParticleEmitterInstance::InitParameters(InTemplate, InComponent, bClearResources);

   // Get the type data module
   UParticleLODLevel* LODLevel   = InTemplate->GetCurrentLODLevel(this);
   check(LODLevel);
   SpinnerTypeData = CastChecked<UParticleModuleTypeDataSpinner>(LODLevel->TypeDataModule);
}

virtual void Tick(float DeltaTime, bool bSuppressSpawning)

The Tick() function is responsible for spawning and updating the particles in the instance. First, it gets the current rotation from the SpinnerTypeData distribution using the EmitterTime. Since the LocalToWorld of the parent component can be used in the Spawn()/Update() functions of various modules, we need to ensure that the emitter instance rotation is taken into account. This is accomplished by saving off the Rotation of the component, and then adding the emitter instance amount to it. The component transform is then updated to include the new rotation. The emitter instance is then ticked like normal by calling the super Tick() function. The component Rotation is then restored.
void FParticleSpinnerEmitterInstance::Tick(float DeltaTime, bool bSuppressSpawning)
{
   // Update our current rotation
   check(SpinnerTypeData);
   CurrentRotation = SpinnerTypeData->Spin.GetValue(EmitterTime, Component);

   // Adjust the component rotation
   AdjustComponentRotation();

   // Call the super Tick
   FParticleEmitterInstance::Tick(DeltaTime, bSuppressSpawning);

   // Restore the component rotation
   RestoreComponentRotation();
}

virtual void UpdateBoundingBox(float DeltaTime)

The UpdateBoundingBox() function has to be overridden in this case to ensure that the emitter instance rotation is taken into account when bUseLocalSpace is true.
void FParticleSpinnerEmitterInstance::UpdateBoundingBox(float DeltaTime)
{
    // Unfortunately, we have to completely override the UpdateBoundingBox function in
    // order to ensure our rotation is taken into account...
    // 
    // Except for the last bit of code (where the bUseLocalSpace flag is taken into 
    // account), the function is identical to the FParticleSpriteEmitterInstance
    // version.
    if (Component)
    {
        // Take component scale into account
        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 each particle, offset the box appropriately 
        for (int32 i=0; i<ActiveParticles; i++)
        {
            DECLARE_PARTICLE(Particle, ParticleData + ParticleStride * ParticleIndices[i]);
            // Do linear integrator and update bounding box
            // Do angular integrator, and wrap result to within +/- 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 particles: this does a whole lot of compares that can be avoided using SSE/ Altivec.
            Particle.Rotation = appFmod(NewRotation, 2.f*(float)PI);
            Particle.Location = NewLocation;
            ParticleBoundingBox += NewLocation;
        }
        ParticleBoundingBox = ParticleBoundingBox.ExpandBy(MaxSizeScale);
        // Transform bounding box into world space if the emitter uses a local space coordinate system.
        UParticleLODLevel* LODLevel = SpriteTemplate->GetCurrentLODLevel(this);
        check(LODLevel);
        if (LODLevel->RequiredModule->bUseLocalSpace) 
        {
            // Adjust the component rotation
            AdjustComponentRotation();
            // Transform the bounding box
            ParticleBoundingBox = ParticleBoundingBox.TransformBy(Component->LocalToWorld);
            // Restore the component rotation
            RestoreComponentRotation();
        }
    }
}

void FParticleSpinnerEmitterInstance::AdjustComponentRotation()

The AdjustComponentRotation() function will alter the components LocalToWorld to account for the emitter instance rotation.
void FParticleSpinnerEmitterInstance::AdjustComponentRotation()
{
   // Save the true rotation
   ComponentRotation = Component->Rotation;
   // Since the LocalToWorld of the component can be used in the Update functions of various modules,
   // we need to spoof it so our instance rotation is taken into account.
   // We need to reconstruct the components LocalToWorld so the rotation is taken into account
   // in the correct place.
   FVector CurrRotInDegrees = CurrentRotation * 360.0f;
   FRotator RotationRot = FRotator::MakeFromEuler(CurrRotInDegrees);
   Component->Rotation += RotationRot;
   Component->SetTransformedToWorld();
}

void FParticleSpinnerEmitterInstance::RestoreComponentRotation()

The RestoreComponentRotation() function will remove the emitter instance rotation from the components LocalToWorld.
void FParticleSpinnerEmitterInstance::RestoreComponentRotation()
{
   // Restore the component rotation
   Component->Rotation = ComponentRotation;
   Component->SetTransformedToWorld();
}

Results

The following screen shots demonstrate the Spinner emitter instance in action. The settings were as follows:

  • The Spin distribution was set to a constant curve with a point at (Time=0,X=0,Y=0,Z=0) and another point at (Time=1,X=0,Y=0,Z=1)leading to the instance spinning around the Z-axis at a variable rate over the life of the emitter.

  • An InitialVelocity module was used with a Constant Distribution set to (X=100,Y=100,Z=100) so that all particles would be emitted in a straight-line stream (discounting the spinning of the instance).

  • An InitialColor module was used with StartColor set to a constant curve with a point at (Time=0,R=1,G=0,B=0) and another at(Time=1,R=0,G=0,B=1) leading to the particles being emitted going from red to blue over the life of the emitter.

Spinner A Spinner B
   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值