IEnumerator IntervaledTick() { //this is of fundamental importance: Never create implementors as Monobehaviour just to hold //data (especially if read only data). Data should always been retrieved through a service layer //regardless the data source. //The benefits are numerous, including the fact that changing data source would require //only changing the service code. In this simple example I am not using a Service Layer //but you can see the point. //Also note that I am loading the data only once per application run, outside the //main loop. You can always exploit this pattern when you know that the data you need //to use will never change var enemiestoSpawny = ReadEnemySpawningDataServiceRequest(); var enemyAttackDatay = ReadEnemyAttackDataServiceRequest(); yield return(enemiestoSpawny); yield return(enemyAttackDatay); var enemiestoSpawn = enemiestoSpawny.Current; var enemyAttackData = enemyAttackDatay.Current; var spawningTimes = new float[enemiestoSpawn.Length]; for (var i = enemiestoSpawn.Length - 1; i >= 0 && _numberOfEnemyToSpawn > 0; --i) { spawningTimes[i] = enemiestoSpawn[i].enemySpawnData.spawnTime; } _enemyFactory.Preallocate(); while (true) { //Svelto.Tasks can yield Unity YieldInstructions but this comes with a performance hit //so the fastest solution is always to use custom enumerators. To be honest the hit is minimal //but it's better to not abuse it. yield return(_waitForSecondsEnumerator); //cycle around the enemies to spawn and check if it can be spawned for (var i = enemiestoSpawn.Length - 1; i >= 0 && _numberOfEnemyToSpawn > 0; --i) { if (spawningTimes[i] <= 0.0f) { var spawnData = enemiestoSpawn[i]; //In this example every kind of enemy generates the same list of EntityViews //therefore I always use the same EntityDescriptor. However if the //different enemies had to create different EntityViews for different //engines, this would have been a good example where EntityDescriptorHolder //could have been used to exploit the the kind of polymorphism explained //in my articles. var enemyAttackStruct = new EnemyAttackStruct { attackDamage = enemyAttackData[i].enemyAttackData.attackDamage, timeBetweenAttack = enemyAttackData[i].enemyAttackData.timeBetweenAttacks }; //has got a compatible entity previously disabled and can be reused? //Note, pooling make sense only for Entities that use implementors. //A pure struct based entity doesn't need pooling because it never allocates. //to simplify the logic, we use a recycle group for each entity type var fromGroupId = ECSGroups.EnemiesToRecycleGroups + (uint)spawnData.enemySpawnData.targetType; if (entitiesDB.HasAny <EnemyEntityViewStruct>(fromGroupId)) { ReuseEnemy(fromGroupId, spawnData); } else { yield return(_enemyFactory.Build(spawnData.enemySpawnData, enemyAttackStruct)); } spawningTimes[i] = spawnData.enemySpawnData.spawnTime; _numberOfEnemyToSpawn--; } spawningTimes[i] -= SECONDS_BETWEEN_SPAWNS; } } }