Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

ECS and Jobs, my first attempt

Discussion in 'Entity Component System' started by Zeraphan, Feb 10, 2020.

  1. Zeraphan

    Zeraphan

    Joined:
    Nov 20, 2019
    Posts:
    14
    Super quick background: I am a high school teacher that has taught computer science for 17 years now. We have been using Unity in our Junior and Senior level courses for the last 3 years. I am currently trying to teach myself ECS so that I can then pass that on to my students.

    First project: The goal of this project was simple, spawn a number of cubes and have them rotate around a given point. I put the following together based on as many tutorials as I could find.

    Code:
    Code (CSharp):
    1.  
    2. [System.Serializable]
    3. public class GameManager : MonoBehaviour
    4. {
    5.     private EntityManager entityManager;
    6.     private EntityArchetype shipArchetype;
    7.     private List<Entity> ships;
    8.    
    9.     [Header("Rendering")]
    10.     [SerializeField] private Mesh mesh;
    11.     [SerializeField] private Material material;
    12.  
    13.     [Header("Entities")]
    14.     [SerializeField] private int numberToSpawn;
    15.     [SerializeField] private Transform targetToOrbit;
    16.  
    17.     private void Start()
    18.     {
    19.         Initialize();
    20.     }
    21.  
    22.     private void Initialize()
    23.     {
    24.         entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
    25.         ships = new List<Entity>();
    26.         shipArchetype = entityManager.CreateArchetype(
    27.             typeof(Translation),
    28.             typeof(Rotation),
    29.             typeof(LocalToWorld),
    30.             typeof(RenderMesh),
    31.             typeof(OrbitTarget),
    32.             typeof(ShipSpeed));
    33.        
    34.         for (int i = 0; i < numberToSpawn; i++)
    35.         {
    36.             Entity entity = entityManager.CreateEntity(shipArchetype);
    37.            
    38.             entityManager.SetComponentData(entity, new Translation
    39.             {
    40.                 Value = new float3 (UnityEngine.Random.Range(-10f, 10f),
    41.                     UnityEngine.Random.Range(-10f, 10f),
    42.                     (UnityEngine.Random.Range(-10f, 10f)))
    43.             });
    44.            
    45.             entityManager.SetComponentData(entity, new OrbitTarget
    46.             {
    47.                 orbitTarget = new float3 (targetToOrbit.transform.position.x,
    48.                     targetToOrbit.transform.position.y,
    49.                     targetToOrbit.transform.position.z)
    50.             });
    51.            
    52.             entityManager.SetComponentData(entity, new ShipSpeed
    53.             {
    54.                 shipSpeed = .005f
    55.             });
    56.            
    57.             entityManager.SetComponentData(entity, new Rotation
    58.             {
    59.                 Value = new float4
    60.                 (
    61.                     UnityEngine.Random.Range(-1f, 1f),
    62.                     UnityEngine.Random.Range(-1f, 1f),
    63.                     UnityEngine.Random.Range(-1f, 1f),
    64.                     UnityEngine.Random.Range(-1f, 1f)
    65.                 )
    66.             });
    67.  
    68.             entityManager.SetSharedComponentData(entity, new RenderMesh {
    69.                 mesh = mesh,
    70.                 material = material,
    71.             });
    72.            
    73.             ships.Add(entity);
    74.         }
    75.  
    76.     }
    77.  
    78.    private void Update()
    79.     {
    80.         //Create arrays to copy data into for Job
    81.         NativeArray<float3> positions = new NativeArray<float3>(ships.Count, Allocator.TempJob);
    82.         NativeArray<float4> rotations = new NativeArray<float4>(ships.Count, Allocator.TempJob);
    83.         NativeArray<float3> orbits = new NativeArray<float3>(ships.Count, Allocator.TempJob);
    84.         NativeArray<float> speeds = new NativeArray<float>(ships.Count, Allocator.TempJob);
    85.  
    86.         //Copy data for job use
    87.         for (int i = 0; i < ships.Count; i++)
    88.         {
    89.             positions[i] = new float3(entityManager.GetComponentData<Translation>(ships[i]).Value);
    90.             rotations[i] = new float4(entityManager.GetComponentData<Rotation>(ships[i]).Value.value);
    91.             orbits[i] = entityManager.GetComponentData<OrbitTarget>(ships[i]).orbitTarget;
    92.             speeds[i] = entityManager.GetComponentData<ShipSpeed>(ships[i]).shipSpeed;
    93.         }
    94.        
    95.         //create the job with the data
    96.         RotateAroundJob rotateAroundJob = new RotateAroundJob
    97.         {
    98.             deltaTime = Time.deltaTime,
    99.             shipPositions = positions,
    100.             shipSpeeds = speeds,
    101.             shipRotations = rotations,
    102.             shipOrbitTargets = orbits
    103.         };
    104.        
    105.         //schedule the job
    106.         JobHandle jobHandle = rotateAroundJob.Schedule(ships.Count, 20);
    107.         jobHandle.Complete();
    108.        
    109.         //copy information back
    110.         for (int i = 0; i < ships.Count; i++)
    111.         {
    112.             entityManager.SetComponentData(ships[i], new Translation
    113.             {
    114.                 Value = positions[i]
    115.             });
    116.         }
    117.        
    118.         //dispose of arrays
    119.         positions.Dispose();
    120.         rotations.Dispose();
    121.         orbits.Dispose();
    122.         speeds.Dispose();
    123.     }
    124.  
    125.     [BurstCompile]
    126.     public struct RotateAroundJob : IJobParallelFor
    127.     {
    128.         public NativeArray<float3> shipPositions;
    129.         public NativeArray<float> shipSpeeds;
    130.         public NativeArray<float4> shipRotations;
    131.         public NativeArray<float3> shipOrbitTargets;
    132.         public float deltaTime;
    133.                
    134.         public void Execute(int index)
    135.         {
    136.            // Debug.Log(shipPositions[index]);
    137.             float3 axis = new float3
    138.             {
    139.                 x = shipRotations[index].x,
    140.                 y = shipRotations[index].y,
    141.                 z = shipRotations[index].z
    142.             };            
    143.            
    144.             float3 vector3 = Quaternion.AngleAxis(.35f, axis) * (shipPositions[index] - shipOrbitTargets[index]);
    145.             shipPositions[index] = shipOrbitTargets[index] + vector3;            
    146.            
    147.         }
    148.     }
    149. }
    150.  
    This does result in the given number of cubes that rotate around their target. I am not sure however if I am doing this the best way possible.

    Is it bad to store the entities in a list so that I can access them later? I couldn't find a way to query the Ship archetype to run through all of the ships.

    Is this the best way to create these cubes? While I can get a large number to appear at once, the frame rate drops much quicker than I was expecting as I increase the number of cubes.
     
  2. theodor349

    theodor349

    Joined:
    Mar 6, 2016
    Posts:
    11
    To my knowledge using a JobComponentSystem would result in faster querying of entities because of how it can utilize the different entity chunks.

    So creating a system would make it faster and could look like this:
    Code (CSharp):
    1. public class RotateAroundSystem  : JobComponentSystem
    2. {
    3.  
    4.     public struct RotateAroundJob : IJobForEach<Translation, Speed, Rotation, OrbitTarget>
    5.     {
    6.         public float deltaTime;
    7.  
    8.         public void Execute(Translation shipPosition, Speed shipSpeed, Rotation shipRotation, OrbitTarget shipOrbitTarget)
    9.         {
    10.             float3 axis = new float3
    11.             {
    12.                 x = shipRotation.x,
    13.                 y = shipRotation.y,
    14.                 z = shipRotation.z
    15.             };        
    16.        
    17.             float3 vector3 = Quaternion.AngleAxis(.35f, axis) * (shipPosition - shipOrbitTarget);
    18.             shipPosition = shipOrbitTarget + vector3;        
    19.         }
    20.     }
    21.  
    22.     protected override JobHandle OnUpdate(JobHandle inputDeps)
    23.     {
    24.         return new RotateAroundJob()
    25.         {
    26.             deltaTime = Time.deltaTime
    27.         }.Schedule(this, inputDeps);
    28.     }
    29. }
     
    IsDon likes this.
  3. Zeraphan

    Zeraphan

    Joined:
    Nov 20, 2019
    Posts:
    14
    Using the system was much faster thank you.
     
    IsDon and theodor349 like this.