How to use Particle System in ECS?

I am trying to make a simple shump game with ECS. I have already written a SpawnSystem and assign a prefab to it, it works fine.The question is my plane was made of serveral cube and particle system. After the conversion(gameobject to entity), the particle system is all missing. Is anyone have any idea how to do it right?

namespace Shump
{
    [Serializable]
    public struct SpawnInPoint : ISharedComponentData
    {
        public Entity Prefab;
    }

    namespace Authoring
    {
        [RequiresEntityConversion]
        public class SpawnInPoint : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity
        {
            public GameObject Prefab;

            // Lets you convert the editor data representation to the entity optimal runtime representation

            public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
            {
                var spawnerData = new Shump.SpawnInPoint
                {
                    // The referenced prefab will be converted due to DeclareReferencedPrefabs.
                    // So here we simply map the game object to an entity reference to that prefab.
                    Prefab = conversionSystem.GetPrimaryEntity(Prefab),
                };
                dstManager.AddSharedComponentData(entity, spawnerData);
            }

            // Referenced prefabs have to be declared so that the conversion system knows about them ahead of time
            public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
            {
                referencedPrefabs.Add(Prefab);
            }
        }
    }
}
2 Likes

You’re either destroying the GameObjects during conversion or not creating them at all. Make sure all ConvertToEntity scripts are set to “Convert and Inject Game Object”. The above code only passes the entity along, which of course doesn’t have any GameObject (and therefore particle system) with it.

Using particle systems within ECS seems to be a common question so I’ll probably try to create an example for that in the future. It kind of depends on what you want to do exactly, but you usually want to sync the particle system GameObject transform to the associated Entity translation. You can do this with a custom system or by attaching the CopyTransformToGameObjectProxy script.

1 Like

Here comes a new question. When I use the ConvertToEntity scripts with “Convert and Inject Game Object” mode, I need a CopyTransformToGameObjectProxy script to copy the transfrom from Entity to original GameObject, and it will add a GameObjectEntity automatically. These two scirpts(ConvertToEntity and GameObjectEntity) seems to have conflicts some how, since I got an error message on the inspector.What should I do with this? Or it just a known issue?

Don’t use CopyTransformToGameObjectProxy

Just create your own simple IConvertGameObjectToEntity script that adds CopyTransformToGameObject

public class CopyTransformToGameObjectAuth : MonoBehaviour, IConvertGameObjectToEntity
{
    /// <inheritdoc />
    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        dstManager.AddComponentData(entity, default(CopyTransformToGameObject));
    }
}

You can ignore the warnings. It will work just fine for now but that proxy will likely be removed in a future version. Also be aware that when using the hybrid renderer package any mesh renderer on the GameObject will be duplicated to the entity side . So you should remove or delete the one on the GameObject at startup.

I would think that since the proxy is being deprecated that the data component is too. So it would be saferest to just roll your own system. Gives you more control too. Here’s one I came up with:

using Unity.Entities;
using UnityEngine;

public enum TransformSyncMode { GameObjectToEntity, EntityToGameObject }

[DisallowMultipleComponent]
[RequiresEntityConversion]
public class SyncTransform : MonoBehaviour, IConvertGameObjectToEntity
{
    public TransformSyncMode SyncMode;

    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        dstManager.AddComponentData(entity, new SyncTransformData() { SyncMode = SyncMode });
    }
}

public struct SyncTransformData : IComponentData
{
    public TransformSyncMode SyncMode;
}
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;

/// <summary>
/// Synchronizes position and rotation between GameObjects and their associated entities.
/// </summary>
/// <remarks>Does not sync scale since that is more complicated and not really needed at the moment.</remarks>
public class SyncTransformSystem : ComponentSystem
{
    EntityQuery query;

    protected override void OnCreate()
    {
        query = GetEntityQuery(
            ComponentType.ReadOnly<SyncTransformData>(),
            ComponentType.ReadWrite<Transform>(),
            ComponentType.ReadWrite<Translation>(),
            ComponentType.ReadWrite<Rotation>());
    }

    protected override void OnUpdate()
    {
        Entities.With(query).ForEach((Entity entity, Transform tran, ref Translation pos, ref Rotation rot, ref SyncTransformData sync) =>
        {
            if (sync.SyncMode == TransformSyncMode.GameObjectToEntity)
            {
                // Update entity values.
                PostUpdateCommands.SetComponent(entity, new Translation { Value = tran.position });
                PostUpdateCommands.SetComponent(entity, new Rotation { Value = tran.rotation });
            }
            else
            {
                // Update GameObject values.
                tran.position = pos.Value;
                tran.rotation = rot.Value;
            }
        });
    }
}

That’s terrible logic - TranslationProxy and RotationProxy are also depreciated so maybe he shouldn’t use Translation/Rotation either?

(I’m not saying it won’t eventually be replaced just that is poor logic and why duplicate code when you don’t need to.)

Translation and Rotation are obviously newer and more fundamental concepts but even those have changed over time. Do you still use the Position and Heading components that no longer exist? In any case, rolling your own sync system isn’t hard and still gives you better control so I’m not sure why that’s “terrible logic”?

Fair enough, but deprecation of the proxy isn’t the only reason I suspect it will be removed. (e.g. it’s older and not in the DOTS component menu while other deprecated proxies are) Now I may be totally wrong about this but I don’t think playing it safe is terribly illogical either. But to each his own. There all really simple components that can be easily swapped around in any case.

It seems the default CopyTransformToGameObjectSysyem would ignore any changes about Scale. Does that means I need to copy the value of NonUniformScale(created by ConvertToEntity ) to the real GameObject by myself?It’s a little weird. Or I should just directly manipulate the LocalToWorld component and write a system to copy it back?

Changing scale is not well supported in any of the systems currently. In general changing scale can be pretty bad (stops batching, etc) but if you want it, implement a system like IsaiahKelly posted.

Does dynamic batching have other scale limitation?The Unity Manual just mention a mirror scale can stop dynamic batching. Objects with different scales greater than zero is ok?

Particle system are kind of a special case. They very often attach to something else, and generally you want to pool them also and only enable ones that are active. So we just standardized on they always attach to a parent, they don’t live in ECS. The parent does. This also simplifies logic where the particle system is a child, we don’t need to move it directly just have attach/detach points in the code.

the conversion is pratically useless in this case if the GameObject will exist anyway. it will even add more overhead cause you will need to convert every new ‘GameObject Effect’ that you will attach to your entities an creating a new entity will not magically create a new GameObject.
also even attaching your gameobject to an entity using the AddComponentObject(Entity, Object) without converting it using his Transform is not that efficient due to the fact that it creates a sync point for every new Link and it’s A LOT if you are doing it repeatedly, something like projectiles.
i think we should simply wait for Unity to officially supports the Particale Systems Conversion.

the most efficient approach ive found was not converting them at all.
so what i did is simply creating an efficient Object pooling system which keep track of everything his giving within a List.
the ObjectHolder class is just caching the Transform and GameObject of the ‘GameObject Effect’ attached, to prevent using the GetComponent everytime.

the PoolClass is giving an id which identifies a gameobject requested, the id is simply the index within the List. (these indexes are recyclable to prevent growing the List uselessly)
this idex is linked to an entity and used to Recycle / Move ‘GameObject Effect’.

getting the Cached Transform ( ObjectHolder ) from the List via an index is SuperFast.

so i just let the “EntityProjectile” and the “GameObject Effect” totally separated and getting a quick access at the same time. im using it for mobile and it’s working like a charm.

Yes, “converting” particle systems is useless because it is impossible to do that at the moment! I think the OP is just interested in syncing them to entities in a hybrid approach for now. Which is exactly what you are doing yourself. I also agree that they should be pooled for best performance. Which is what I am doing in the example I am making.

@sharpwind11 Do you need these particles to follow entities or just spawn at specific positions? The first objective takes a bit of work to get running optimally but the latter is quite easy to do.

Why do you need the scale values? I would just control that on the GameObject side in any case.

it’s not that hard to create. i used SystemStates to Manage Linking, Unlinking and Following.
for Temporary Prefabs, that are not related to any Entity LifeCycle. i added a new SystemState with an additional field (cooldown).

    /// <summary>
    /// Manage Following Attachments States
    /// System States :
    /// +FollowAttachmentEffect, -AttachmentEffectSystemState : Require Attachment.
    /// +FollowAttachmentEffect, +AttachmentEffectSystemState : Follow.
    /// -FollowAttachmentEffect-TemporaryAttachmentEffect, +AttachmentEffectSystemState : Require Attachment Recycling. (TemporaryAttachmentEffect is added here otherwise it will recycle Temporary Attachments Too without processing the Cooldown)
    ///             
    /// +TemporaryAttachmentEffect, -AttachmentEffectSystemState : Require Attachment.
    /// +TemporaryAttachmentEffect, +AttachmentEffectSystemState : Process Cooldown when reaches 0 destroy entity and Recycle Effect
    /// </summary>
    public struct AttachmentEffectSystemState : ISystemStateComponentData
    {
        public byte EffectId;
        public int RefIndex;
    }

    public struct FollowAttachmentEffect : IComponentData
    {
        public byte EffectId;
    }
  

    public struct TemporaryAttachmentEffect : IComponentData
    {
        public byte EffectId;
        public float AttachmentCooldown;
    }

Yes it is not hard, but by comparison the other is much quicker and simpler to setup.

    public class DeathEffectSystem : ComponentSystem
    {
        protected override void OnUpdate()
        {
            Entities.ForEach((Entity entity, ref DeathEffectEvent death) =>
            {
                DeathEffect.Fire(death.Position);
                PostUpdateCommands.DestroyEntity(entity);
            });
        }
    }

I am also using ISystemStateComponentData for that purpose as well.

By the way, why are you making all your components Serializable?

1 Like

i just copied and pasted another Component before modifying it so i did by mistake XD

Well I have seen official learn projects do that too. So do not feel bad. :wink:

1 Like

The original model I used is quite big, so I need to give it a scale of 0.3 to make it look normal.But an entity with a scale which is not 1 can interrupt the Rotating(when plane move horizontal, and don’t know why). So right now, I create a subobject with a scale of 0.3 to avoid scaling the original gameobject.

I need a constant particle attach to my own plane, and several other particles when enemy plane are going to explode. I am going to make an explosion object pool to handle this.

BTW, the current hybrid mode is really frustrating, since it seems to perform worse than the traditional OOP model(maintain both the gameobject and the entity data). Can’t wait to have a full functional pure-ECS solution.

If your scale is fixed you should change the scale on the mesh on the unity importer instead of on the transform.

1 Like