I found a good and efficient way To instantiate and move GameObjects ( Particale System ) and make them follow Projectiles (Pure Entities).
i created 2 Components that manage Attaching, Detaching and Following Logic.
/// Manage Attachement States
/// System States :
/// (+) AttachementEffect, (-) AttachementSystemState : Projectile Entity Require Attachment.
/// (+) AttachementEffect, (+) AttachementSystemState : The GameObject Can Follow the Projectile Entity.
/// (-) AttachementEffect, (+) AttachementSystemState : The GameObject Attached need to be Recycled.
[System.Serializable]
public struct AttachmentEffectSystemState : ISystemStateComponentData
{
public byte EffectId;
public int RefIndex;
}
[System.Serializable]
public struct AttachmentEffect : IComponentData
{
public byte EffectId;
}
“ProjectileSpawnerSystem” Will Spawn entities as Projectile adding AttachmentEffect, Rotation, Translation.
“AttachmentEffectsManagerSystem” will Attach,Dettach & Follow GameObjects to Entities
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(ProjectileSpawnerSystem))]
public class AttachmentEffectsManagerSystem: ComponentSystem
{
GameObjectPool gameObjectPool;
ObjectHolder objectHolder;
protected override void OnCreate()
{
gameObjectPool = new GameObjectPool();
}
protected override void OnUpdate()
{
// Attachment Effects to Recycle
Entities.
WithNone<AttachmentEffect>().
WithAllReadOnly<AttachmentEffectSystemState>().
ForEach((Entity e, ref AttachmentEffectSystemState attachementEffectSystemState) => {
gameObjectPool.RecycleEffect(attachementEffectSystemState.EffectId, attachementEffectSystemState.RefIndex);
PostUpdateCommands.RemoveComponent<AttachmentEffectSystemState>(e);
PostUpdateCommands.DestroyEntity(e);
});
// Move Effects
Entities.
WithAllReadOnly<AttachmentEffectSystemState, AttachmentEffect, Translation>().
ForEach(( ref AttachmentEffectSystemState attachementEffectSystemState, ref Translation translation) => {
gameObjectPool.MoveObject(attachementEffectSystemState.RefIndex, translation.Value);
});
// Attachment Effects to Link
Entities.
WithNone<AttachmentEffectSystemState>().
WithAllReadOnly<AttachmentEffect, Rotation, Translation>().
ForEach((Entity entityProjectile, ref AttachmentEffect attachementEffect, ref Rotation rotation, ref Translation translation) => {
objectHolder = gameObjectPool.GetEffect(attachementEffect.EffectId, translation.Value, rotation.Value, out var indexRef);
PostUpdateCommands.AddComponent(entityProjectile, new AttachmentEffectSystemState { EffectId = attachementEffect.EffectId, RefIndex = indexRef });
});
}
}
“ProjectileMoverSystem” Will Move Projectiles Entities and create Raycasts
GameObjectPool Class is Pool System that Manage Instantiating,Pooling and moving the GameObjects Effects.
public class GameObjectPool
{
public static GameObjectPool instance;
private Queue<int> HolesIndexes;
private List<ObjectHolder> activeGameObjects;
private Dictionary<byte, Queue<ObjectHolder>> pool;
public GameObjectPool()
{
HolesIndexes = new Queue<int>();
activeGameObjects = new List<ObjectHolder>();
pool = new Dictionary<byte, Queue<ObjectHolder>>();
instance = this;
// Addressables.LoadAssetAsync<GameObject>("WEAId12");
Addressables.LoadAssetsAsync<GameObject>(AddressablesLabels.ParticaleSystems, null).Completed += objects =>
{
foreach (var go in objects.Result)
Debug.Log($"Addressable Loaded: {go.name}");
};
}
/// <summary>
/// Get a GameObject From Pool
/// Rotation and Postion are needed to Fix Trail and sound effects
/// </summary>
/// <param name="EffectId"></param>
/// <param name="rotation"></param>
/// <param name="indexRef"></param>
/// <returns></returns>
public ObjectHolder GetEffect(byte EffectId, float3 pos, quaternion rot, out int indexRef)
{
Queue<ObjectHolder> objecQueue;
// This Object type has already been used
if (pool.ContainsKey(EffectId))
{
objecQueue = pool[EffectId];
}
else
{
objecQueue = new Queue<ObjectHolder>();
pool.Add(EffectId, objecQueue);
}
ObjectHolder instance;
// Queue Has Instances
if (objecQueue.Count > 0)
{
// Get Recycled Instance
instance = objecQueue.Dequeue();
instance._transform.rotation = rot;
instance._transform.position = pos;
instance._gameobject.SetActive(true);
/* for(var i = 0; i< instance.particleSystemsIchilds.Length; i++)
{
instance.particleSystemsIchilds[i].Play();
}*/
}
else // Empty Queue
{
var go = Addressables.Instantiate(GameBootstrap.WeaponsEffectsAddressablePrefix + EffectId , pos, rot).Result;
instance = go.GetComponent<ObjectHolder>();
/* for (var i = 0; i < instance.particleSystemsIchilds.Length; i++)
{
instance.particleSystemsIchilds[i].Play();
}*/
}
// Check for indexRef Gaps
if (HolesIndexes.Count > 0)
{
indexRef = HolesIndexes.Dequeue();
activeGameObjects[indexRef] = instance;
}
else
{
activeGameObjects.Add(instance);
indexRef = activeGameObjects.Count-1;
}
return instance;
}
/// <summary>
/// Recycle an Effect
/// </summary>
/// <param name="EffectId"></param>
/// <param name="indexRef"></param>
public void RecycleEffect(byte EffectId, int indexRef)
{
var objectHolder = activeGameObjects[indexRef];
activeGameObjects[indexRef] = null;
objectHolder._gameobject.SetActive(false);
HolesIndexes.Enqueue(indexRef);
pool[EffectId].Enqueue(objectHolder);
}
/// <summary>
/// Move an Active GameObject
/// </summary>
/// <param name="index"></param>
/// <param name="position"></param>
public void MoveObject(int index,float3 position)
{
activeGameObjects[index]._transform.position = position;
}
// Clear all Effects Within the Pool
public void CleanPool()
{
for (var i = 0; i < activeGameObjects.Count; i++)
{
if (activeGameObjects[i] != null)
{
Addressables.ReleaseInstance(activeGameObjects[i]._gameobject);
}
activeGameObjects.Clear();
}
foreach (var Q in pool.Values)
{
for (var i = 0; i < Q.Count; i++)
{
Addressables.ReleaseInstance(Q.Dequeue()._gameobject);
}
}
}
}