Last active
May 31, 2023 16:34
-
-
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`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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 {} |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is a shorter, simpler version that does not use
IJobParallelForTransformhence 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.