Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save andrew-raphael-lukasik/530e90ee27cd7a9be47351e03b2634d1 to your computer and use it in GitHub Desktop.

Select an option

Save andrew-raphael-lukasik/530e90ee27cd7a9be47351e03b2634d1 to your computer and use it in GitHub Desktop.
Mirror hundreds of `Transform`s in an ECS `World` easily. Uses `IJobParallelForTransform`/`TransformAccessArray`
// src* https://gist.github.com/andrew-raphael-lukasik/530e90ee27cd7a9be47351e03b2634d1
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Jobs;
using Unity.Entities;
using Unity.Transforms;
using Unity.Rendering;
using Unity.Collections;
using Unity.Mathematics;
public class MirrorThisTransformInEcsWorld : MonoBehaviour
{
public static TransformAccessArray Transforms;
public static List<MirrorThisTransformInEcsWorld> Instances = new ();
public static Dictionary<MirrorThisTransformInEcsWorld,int> Indices = new ();
public static NativeHashMap<int,Entity> Entities;
[SerializeField] Mesh _mesh;
[SerializeField] Material _material;
EntityManager _entityManager;
World _world;
void OnEnable ()
{
_world = World.DefaultGameObjectInjectionWorld;
_entityManager = _world.EntityManager;
if( !Transforms.isCreated )
Transforms = new TransformAccessArray( capacity:1 );
if( !Entities.IsCreated )
Entities = new NativeHashMap<int,Entity>( initialCapacity:32 , Allocator.Persistent );
int index = Transforms.length;
Transforms.Add( transform );
Instances.Add( this );
Indices.Add( this , index );
Entities.Add( index , CreateMyEntity() );
}
void OnDisable ()
{
int index = Indices[this];
Entity entity = Entities[index];
Transforms.RemoveAtSwapBack( index );
Instances.RemoveAtSwapBack( index );
Indices.Remove( this );
Entities.Remove( index );
if( index<Transforms.length && Transforms.length!=0 )// did these RemoveAtSwapBack changed sb else index ?
{
int affectedIndex = Transforms.length;
var affectedInstance = Instances[ index ];
Indices[affectedInstance] = index;
Entity e = Entities[affectedIndex];
Entities.Remove( affectedIndex );
Entities.Add( index , e );
}
if( Entities.Count==0 ) Entities.Dispose();
if( _world.IsCreated )//prevents error on (editor) game exiting play mode
_entityManager.DestroyEntity( entity );
}
Entity CreateMyEntity ()
{
Entity entity = _entityManager.CreateEntity();
_entityManager.AddComponent<IsMirroredTransform>( entity );
_entityManager.AddComponent<LocalToWorld>( entity );
RenderMeshUtility.AddComponents(
entity , _entityManager ,
new RenderMeshDescription( UnityEngine.Rendering.ShadowCastingMode.On , receiveShadows:true , renderingLayerMask:1 ) ,
new RenderMeshArray( new Material[]{ _material } , new Mesh[]{ _mesh } ) ,
MaterialMeshInfo.FromRenderMeshArrayIndices( 0 , 0 )
);
#if UNITY_EDITOR
_entityManager.SetName( entity , $"{gameObject.name} #{gameObject.GetInstanceID()}" );
#endif
return entity;
}
public partial class UpdateSystem : SystemBase
{
EndSimulationEntityCommandBufferSystem _ecbSystem;
protected override void OnCreate ()
{
_ecbSystem = World.GetOrCreateSystemManaged<EndSimulationEntityCommandBufferSystem>();
}
protected override void OnUpdate ()
{
if( !MirrorThisTransformInEcsWorld.Transforms.isCreated || MirrorThisTransformInEcsWorld.Transforms.length==0 )
return;
var entities = MirrorThisTransformInEcsWorld.Entities;
var ecbpw = _ecbSystem.CreateCommandBuffer().AsParallelWriter();
var updateJob = new UpdateJob{
Entities = entities ,
ECBPW = ecbpw ,
};
Dependency = updateJob.Schedule( MirrorThisTransformInEcsWorld.Transforms , Dependency );
_ecbSystem.AddJobHandleForProducer( Dependency );
}
}
[Unity.Burst.BurstCompile]
public struct UpdateJob : IJobParallelForTransform
{
[ReadOnly] public NativeHashMap<int,Entity> Entities;
public EntityCommandBuffer.ParallelWriter ECBPW;
void IJobParallelForTransform.Execute ( int index , TransformAccess transform )
{
Entity entity = Entities[index];
ECBPW.SetComponent( sortKey:index , entity , new LocalToWorld{
Value = transform.localToWorldMatrix
} );
}
}
}
public struct IsMirroredTransform : IComponentData {}
@andrew-raphael-lukasik

andrew-raphael-lukasik commented May 31, 2023

Copy link
Copy Markdown
Author

Here is a shorter, simpler version that does not use IJobParallelForTransform hence is easier to understand for ECS newcomers.
Also it is simply good enough when there is dozens of such GameObjects at play. Nevertheless it scales poorly when 100s of such instances are needed.

// src* https://gist.github.com/andrew-raphael-lukasik/530e90ee27cd7a9be47351e03b2634d1
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Rendering;
using Unity.Mathematics;
    
public class MirrorThisTransformInEcsWorld : MonoBehaviour
{
    [SerializeField] Mesh _mesh;
    [SerializeField] Material _material;
    EntityManager _entityManager;
    World _world;
    Entity _entity;
    void OnEnable ()
    {
    	_world = World.DefaultGameObjectInjectionWorld;
    	_entityManager = _world.EntityManager;
    		
    	_entity = _entityManager.CreateEntity();
    	_entityManager.AddComponent<IsMirroredTransform>( _entity );
    	_entityManager.AddComponentObject( _entity , this );// optional
    	_entityManager.AddComponent<LocalToWorld>( _entity );
    	RenderMeshUtility.AddComponents(
    		_entity , _entityManager ,
    		new RenderMeshDescription( UnityEngine.Rendering.ShadowCastingMode.On , receiveShadows:true , renderingLayerMask:1 ) ,
    		new RenderMeshArray( new Material[]{ _material } , new Mesh[]{ _mesh } ) ,
    		MaterialMeshInfo.FromRenderMeshArrayIndices( 0 , 0 )
    	);
    
    	#if UNITY_EDITOR
    	_entityManager.SetName( _entity , $"{gameObject.name} #{gameObject.GetInstanceID()}" );
    	#endif
    }
    void Update ()
    {
        _entityManager.SetComponentData( _entity , new LocalToWorld{
    		Value = transform.localToWorldMatrix
    	} );
    }
    void OnDisable ()
    {
    	if(_world.IsCreated)//prevents error on (editor) game exiting play mode
    		_entityManager.DestroyEntity( _entity );
    }
}
    
public struct IsMirroredTransform : IComponentData {}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment