Optimization Ideas for managing large amount of enemies GameObject

Hello folks,

I’m currently making a game as part of an end-of-studies project.
I’m in a team of 3 developpers and we all have an assigned challenge, mine is to make a kind of crowd controller/manager in order to manage a large amount of ennemies.

Game Pitch:
You are the player and need to defend a train, you can add trainCart equipped with turret to your train in order to get rid of enemies and keep the train safe for the maximum distance you can.

(i’m using navmesh for navigation wich i know is not very performances friendly)

First i’ve been looking for unity DOTS and ECS but it seems to override a lot with the rest of a project. My two coworkers haven’t been into this before and they have procedural generation for challenge (terrain and spline for rails) wich is already kind of complicated without bringing DOTS or ECS in.
That’s why I decided to keep basic GameObjects for my enemies.

For now I have a singleton class which refer all enemies and their positions in a NativeArray<float3>.
So each Update it updates positions and assign a target to each enemy:
targetList and enemyList are List<Transform> and managed by adding or deleting Object when a cart or enemy is spawned or destroyed.

for (int i = 0; i < enemyList.Count; i++)
    enemyPositions[i] = enemyList[i].position;
    enemyList[i].SetNearestTargetPositionAndIndex(NearestTargetPositions[i], NearestTargetIndex[i]);

for (int i = 0; i < targetList.Count; i++)
    if (targetList[i] == null)
    targetPositions[i] = targetList[i].position;
FindNearestJob findJob = new FindNearestJob
    TargetPositions = targetPositions,
    SeekerPositions = enemyPositions,
    NearestTargetPositions = NearestTargetPositions,
    NearestTargetIndex = NearestTargetIndex

// Schedule() puts the job instance on the job queue.
JobHandle findHandle = findJob.Schedule();

According to this we don’t have any colliders. Shooting is processed by Jobs, looping through enemies or target to see if the bullet will hit or not. For now the performances are good for 1000 enemies and 10-20 turrets however i don’t know if it is a good way to go about it or even if it’s really clean and for now enemies don’t have a complex behaviour, i’m just using the NavMeshAgent avoidance and navigation.

For example some issues with accessing the List while the object has been destroy or the index has been changed in an other thread because an object has been removed from it. Quite situational but could happen.

I may could fix it by doing more check and all but before doing this i wanted to know what are your advices about it. Is it a good or at least “not too bad” way to handle the logic?

NB: I’m not closed to an ECS approach but i don’t know how to make it work if only my enemies are Entities and keep my player, train and terrain like so.

Thanks for your replies and the time taken to read my bad english ^^’

You don’t need ECS, entities replace GameObjects and, from I’ve read, you don’t need them. Well-scheduled Burst-compiled IJobParallelFor & IJobParallelForTransform jobs are more than enough for everything you need. ECS is useful for some use-cases but is not a general-use miracle solution, it’s just not and (more importantly) is still incomplete; not at feature parity with GameObjects side of the engine - something that most tutorial people omit (no animation system, hello).
If you reach a point where there is a performance issue don’t jump to conclusions too fast but open up the Profiler window and investigate, understand the cause of the issue first.

Absolutely get rid of these List<Transform> entirely with TransformAccessArrays
| How to use TransformAccessArray | How to add Transform to TransformAccessArray mid execution | How to remove Transform from TransformAccessArray mid execution | · GitHub

To find nearest target properly use a Quadtree or Bounding Volume Hierarchy (BVH)
Quadtree built on `Unity.Collections`, `Unity.Jobs` and `Unity.Burst` · GitHub