World creation for ECS with Monobehaviour

Assume I had a physics-based MonoBehaviour player controller and i want to use ECS for controlling 1000s of npcs with physics based movement.

  • Q: what should be my approach for this because physics does not work with unity physics. Do i have to create a duplicate of the entire world as entity as well.

  • Q: Also what would be the best way of providing entities the data about MonoBehaviour ( like Position, Rotation etc) that changes constantly.

Assume i had a physics based monobehaviour player controller and i want to use ECS for controlling 1000s of npcs with physics based movement.

github.com/Unity-Technologies/CharacterControllerSamples

This is how you can forward transform data from a MonoBehaviour to an Entity:

// 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 {}

But be aware that this solution will not scale to 100s well. For that you need to go 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 {}

Note that anything involving GameObjects will never scale to 1000s well. This is what pure ECS/Dots path is for.

First of all thanks for your response.

And I tried this

public struct PlayerTransformRefComponent : IComponentData
{
    public float3 Position;
    public quaternion Rotation;
  
    public void setPosRot(float3 pos, quaternion rot)
    {
        Position = pos;
        Rotation = rot;
    }
}

the above component will be held by npcs( Entities )

void Update()
{
    float3 position = transform.position;
    quaternion rotation = transform.rotation;
    // if(logAllWorlds)
    //     for (var i = 0; i < World.All.Count; i++)
    //     {
    //         Debug.Log(i + " -> " + World.All*.Name);
    //     }
    foreach (World world in World.All)
    {
        EntityQuery transformRefQuery = world.EntityManager.CreateEntityQuery(typeof(PlayerTransformRefComponent));
        NativeArray<Entity> entities = transformRefQuery.ToEntityArray(Allocator.TempJob);
        SystemHandle systemHandle = new SystemHandle { };
        foreach (Entity entity in entities)
        {
            RefRW<PlayerTransformRefComponent> transformRefRw = world.EntityManager.GetComponentDataRW<PlayerTransformRefComponent>(systemHandle);
     
            Debug.Log("setting : " + transformRefRw.ValueRO.Position + " , " + transformRefRw.ValueRO.Rotation);
            transformRefRw.ValueRW.setPosRot(position, rotation);
        }
    }
}

the above code is run on a monobehaviour on every update to modify its position and rotation references in all entities.

but the problem I think is that the player is in the main world and it can not write data in entities that are in a sub scene
Error

alt text

what should i do