Tradução do wiki do projeto Svelto.ECS. Estrutura do ECS para Unity3D


Olá Habr! Apresento a você a tradução do wiki do projeto Svelto.ECS , escrito por Sebastiano Mandalà.

O Svelto.ECS é o resultado de muitos anos de pesquisa e aplicação dos princípios do SOLID no desenvolvimento de jogos no Unity. Essa é uma das muitas implementações do padrão ECS disponíveis para C #, com vários recursos exclusivos introduzidos para solucionar as deficiências do próprio padrão.

Primeiro olhar


A maneira mais fácil de ver os recursos básicos do Svelto.ECS é baixar o Vanilla Example . Se você quiser garantir sua facilidade de uso, mostrarei um exemplo:

//  void ApplicationCompositionRoot() { var simpleSubmissionEntityViewScheduler = new SimpleSubmissionEntityViewScheduler(); _enginesRoot = new EnginesRoot(simpleSubmissionEntityViewScheduler); var entityFactory = _enginesRoot.GenerateEntityFactory(); var entityFunctions = _enginesRoot.GenerateEntityFunctions(); _enginesRoot.AddEngine(new BehaviourForSimpleEntityEngine(entityFunctions)); entityFactory.BuildEntity<SimpleEntityDescriptor>(new EGID(1), new[] { new SimpleImplementor() });` } //  class SimpleEntityDescriptor : GenericEntityDescriptor<BehaviourEntityViewForSimpleEntity> { } public class BehaviourEntityViewForSimpleEntity : EntityView { public ISimpleComponent simpleComponent; } public interface ISimpleComponent { public int counter {get; set;} } class SimpleImplementor : ISimpleComponent { public int counter { get; set; } } //  ()    public class BehaviourForSimpleEntityAsStructEngine : IQueryingEntityViewEngine { public IEntityViewsDB entityViewsDB { private get; set; } public void Ready() { Update().Run(); } //   . //    N ,  N    0  1. IEnumerator Update() { Console.Log("Task Waiting"); while (true) { var entityViews = entityViewsDB .QueryGroupedEntityViews<BehaviourEntityViewForSimpleEntity>(0); if (entityViews.Length> 0) { for (var i = 0; i < entityViews.Length; i++) AddOne(entityViews[i].counter); Console.Log("Task Done"); yield break; } yield return null; } } static void AddOne(int counter) { counter += 1; } } 

Infelizmente, não é possível entender rapidamente a teoria por trás desse código, que pode parecer simples, mas confusa ao mesmo tempo. Para entender isso, você precisa gastar tempo lendo o “mural de texto” e tente os exemplos acima.

1. Introdução


Recentemente, tenho discutido muito o Svelto.ECS com vários programadores, mais ou menos experientes. Reuni muitos comentários e fiz várias anotações que vou usar como ponto de partida para meus próximos artigos, onde falarei mais sobre teoria e boas práticas. Um pequeno spoiler: percebi que quando você começa a usar o Svelto.ECS, o maior obstáculo é mudar o paradigma de programação . É incrível o quanto eu tenho que escrever para explicar os novos conceitos introduzidos pelo Svelto.ECS, em comparação com a pequena quantidade de código escrita para desenvolver a estrutura. De fato, embora a estrutura em si seja muito simples e leve, a transição do OOP com o uso ativo de herança ou componentes convencionais do Unity para o "novo" design modular e fracamente acoplado que o Svelto.ECS sugere que o uso impede que as pessoas se adaptem à estrutura.

O Svelto.ECS é usado ativamente no Freejam (nota do tradutor - O autor é o diretor técnico desta empresa). Como sempre posso explicar aos meus colegas os conceitos básicos da estrutura, eles levam menos tempo para entender como trabalhar com ela. Embora o Svelto.ECS seja o mais difícil possível, é difícil superar os maus hábitos; portanto, os usuários tendem a abusar de alguma flexibilidade que lhes permite adaptar a estrutura aos “velhos” paradigmas com os quais se sentem confortáveis. Isso pode levar a um desastre devido a mal-entendidos ou distorção dos conceitos subjacentes à lógica da estrutura. É por isso que pretendo escrever o maior número possível de artigos, principalmente porque tenho certeza de que o paradigma ECS é a melhor solução no momento para escrever código eficaz e suportado para grandes projetos que mudam e retrabalham várias vezes ao longo de vários anos. Robocraft e Cardlife são a prova disso.

Não vou falar muito sobre as teorias subjacentes a este artigo. Apenas lembrarei por que me recusei a usar o contêiner de IoC e comecei a usar exclusivamente a estrutura do ECS: O contêiner de IoC é uma ferramenta muito perigosa se for usado sem entender a essência da inversão de controle. Como você pode ver nos meus artigos anteriores, eu distingo entre a inversão do controle de criação (Inversão do controle de criação) e a inversão do controle de fluxo (Inversão do controle de fluxo). A inversão do controle de fluxo é como o princípio de Hollywood: "Não nos ligue, nós ligaremos para você". Isso significa que as dependências injetadas nunca devem ser usadas diretamente por meio de métodos públicos, pois, ao fazer isso, você simplesmente usa o contêiner de IoC como substituto de qualquer outra forma de injeção global, como o singleton. No entanto, se o contêiner de IoC for usado com base na Inversão de gerenciamento (IoC), basicamente tudo se resume a reutilizar o padrão "Método de modelo" para implementar gerentes que são usados ​​apenas para registrar objetos que gerenciam. No contexto real de inversões de controle de fluxo, os gerentes são sempre responsáveis ​​pelo gerenciamento de entidades. Isso se parece com um padrão ECS? Claro. Com base nesse raciocínio, peguei o padrão ECS e desenvolvi uma estrutura rígida baseada nele, e seu uso é equivalente à aplicação do novo paradigma de programação.

Raiz e motores de composiçãoRoot


A classe Main é a raiz da composição do aplicativo. A raiz da composição é o local onde as dependências são criadas e implementadas (falei bastante sobre isso em meus artigos). Uma raiz de composição pertence a um contexto, mas um contexto pode ter mais de uma raiz de composição. Por exemplo, a fábrica é a raiz da composição. Um aplicativo pode ter mais de um contexto, mas este é um cenário avançado e, neste exemplo, não o consideraremos.

Antes de mergulhar no código, vamos nos familiarizar com as primeiras regras da linguagem Svelto.ECS. ECS é a abreviatura Entity Component System. A infraestrutura da ECS foi bem analisada em artigos por muitos autores, mas, embora os conceitos básicos sejam comuns, as implementações variam amplamente. Primeiro de tudo, não há uma maneira padrão de resolver alguns problemas que surgem ao usar código orientado a ECS. É com relação a essa questão que faço a maior parte de meus esforços, mas falarei sobre isso mais tarde ou nos seguintes artigos. A teoria é baseada nos conceitos de Essência, Componentes (entidades) e Sistemas. Embora eu entenda por que a palavra Sistema foi usada historicamente, desde o início não a achei intuitiva o suficiente para esse fim, usei o Mecanismo como sinônimo para o Sistema, e você, dependendo de suas preferências, pode usar um desses termos.

A classe EnginesRoot é o núcleo do Svelto.ECS. Com sua ajuda, você pode registrar mecanismos e projetar toda a essência do jogo. Criar mecanismos dinamicamente não faz muito sentido; portanto, todos devem ser adicionados à instância EnginesRoot a partir da mesma raiz da composição em que foi criada. Por razões semelhantes, uma instância EnginesRoot nunca deve ser implantada e os mecanismos não devem ser excluídos após serem adicionados.

Para criar e implementar dependências, precisamos de pelo menos uma raiz da composição. Sim, em um aplicativo pode haver mais de um EnginesRoot, mas não abordaremos isso no artigo atual, que tento simplificar o máximo possível. Aqui está a aparência da raiz da composição com a criação do mecanismo e a injeção de dependência:

 void SetupEnginesAndEntities() { //Engines Root   Svelto.ECS.      EngineRoot // ,  Composition Root     ,   //   . //UnitySumbmissionEntityViewScheduler -  ,   //EnginesRoot,     EntityViews. //    ,   , //         Unity. _enginesRoot = new EnginesRoot(new UnitySumbmissionEntityViewScheduler()); //Engines root      ,   , //   . //   EntityFactory  EntityFunctions. //EntityFactory      //(    ), //   . _entityFactory = _enginesRoot.GenerateEntityFactory(); // EntityFunctions     //   , //  .        var entityFunctions = _enginesRoot.GenerateEntityFunctions(); //GameObjectFactory   Unity GameObject //   // GameObject.Instantiate.      // ,    ,   //       //(  ,   //         //      -  ) GameObjectFactory factory = new GameObjectFactory(); //    3     Svelto.ECS. //        //  . //         //     . var enemyKilledObservable = new EnemyKilledObservable(); var scoreOnEnemyKilledObserver = new ScoreOnEnemyKilledObserver(enemyKilledObservable); //ISequencer   3     Svelto.ECS // .       : //1)       //(   //,   ,     //  ). //2)   ,     // . ISequencer      //  Sequencer playerDamageSequence = new Sequencer(); Sequencer enemyDamageSequence = new Sequencer(); //    Unity. //     . IRayCaster rayCaster = new RayCaster(); ITime time = new Others.Time(); // .         //  . var playerHealthEngine = new HealthEngine(entityFunctions, playerDamageSequence); var playerShootingEngine = new PlayerGunShootingEngine(enemyKilledObservable, enemyDamageSequence, rayCaster, time); var playerMovementEngine = new PlayerMovementEngine(rayCaster, time); var playerAnimationEngine = new PlayerAnimationEngine(); //  var enemyAnimationEngine = new EnemyAnimationEngine(); var enemyHealthEngine = new HealthEngine(entityFunctions, enemyDamageSequence); var enemyAttackEngine = new EnemyAttackEngine(playerDamageSequence, time); var enemyMovementEngine = new EnemyMovementEngine(); var enemySpawnerEngine = new EnemySpawnerEngine(factory, _entityFactory); //    var hudEngine = new HUDEngine(time); var damageSoundEngine = new DamageSoundEngine(); // Sequencer  ,    // ,     . playerDamageSequence.SetSequence( new Steps // ,  ! { { //  //      Next   enemyAttackEngine, new To //        { //      //   Next playerHealthEngine, } }, { //  playerHealthEngine, //      Next   new To //       { //      Next     //DamageCondition.damage { DamageCondition.damage, new IStep[] { hudEngine, damageSoundEngine } }, //      Next     //DamageCondition.dead { DamageCondition.dead, new IStep[] { hudEngine, damageSoundEngine, playerMovementEngine, playerAnimationEngine, enemyAnimationEngine } }, } } }); enemyDamageSequence.SetSequence( new Steps { { playerShootingEngine, new To { enemyHealthEngine, } }, { enemyHealthEngine, new To { { DamageCondition.damage, new IStep[] { enemyAnimationEngine, damageSoundEngine } }, { DamageCondition.dead, new IStep[] { enemyMovementEngine, enemyAnimationEngine, playerShootingEngine, enemySpawnerEngine, damageSoundEngine } }, } } }); // ,     //  _enginesRoot.AddEngine(playerMovementEngine); _enginesRoot.AddEngine(playerAnimationEngine); _enginesRoot.AddEngine(playerShootingEngine); _enginesRoot.AddEngine(playerHealthEngine); _enginesRoot.AddEngine(new PlayerInputEngine()); _enginesRoot.AddEngine(new PlayerGunShootingFXsEngine()); //  _enginesRoot.AddEngine(enemySpawnerEngine); _enginesRoot.AddEngine(enemyAttackEngine); _enginesRoot.AddEngine(enemyMovementEngine); _enginesRoot.AddEngine(enemyAnimationEngine); _enginesRoot.AddEngine(enemyHealthEngine); //  _enginesRoot.AddEngine(new CameraFollowTargetEngine(time)); _enginesRoot.AddEngine(damageSoundEngine); _enginesRoot.AddEngine(hudEngine); _enginesRoot.AddEngine(new ScoreEngine(scoreOnEnemyKilledObserver)); 

Este código é do exemplo de Sobrevivência, que agora é comentado e está em conformidade com quase todas as regras de boas práticas que proponho aplicar, incluindo o uso de lógica de mecanismo testada e independente de plataforma. Os comentários ajudarão você a entender a maioria deles, mas um projeto desse tamanho pode ser difícil de entender se você é novo na Svelto.

Entidades


A primeira etapa após criar a raiz vazia da composição e uma instância da classe EnginesRoot é identificar os objetos com os quais você deseja trabalhar primeiro. É lógico começar com o Entity Player. A essência do Svelto.ECS não deve ser confundida com o Unity Game Object (GameObject). Se você ler outros artigos relacionados ao ECS, poderá ver que em muitos deles, as entidades são frequentemente descritas como índices. Esta é provavelmente a pior maneira de introduzir o conceito de ECS. Embora verdadeiro para Svelto.ECS, ele está oculto nele. Desejo que o usuário do Svelto.ECS represente, descreva e identifique cada entidade em termos do idioma do domínio do design de jogos. A entidade no código deve ser o objeto descrito no documento de design do jogo. Qualquer outra forma de definição de entidade levará a uma maneira absurda de adaptar suas visualizações antigas aos princípios do Svelto.ECS. Siga esta regra fundamental e você não se enganará. A própria classe de entidade não existe no código, mas você ainda não deve defini-la abstratamente.

Motores


O próximo passo é pensar em qual comportamento pedir às Entidades. Cada comportamento é sempre modelado dentro do mecanismo; você não pode adicionar lógica a nenhuma outra classe dentro do aplicativo Svelto.ECS. Podemos começar movendo o personagem do jogador e definindo a classe PlayerMovementEngine . O nome do mecanismo deve ser muito restrito, porque quanto mais específico, maior a probabilidade de o mecanismo seguir a Regra de responsabilidade única. A nomeação de classe adequada no Svelto.ECS é fundamental. E o objetivo não é apenas mostrar claramente suas intenções, mas também ajudá-lo a "vê-las".

Pelo mesmo motivo, é importante que seu mecanismo esteja em um espaço para nome muito especializado. Se você definir espaços para nome de acordo com a estrutura da pasta, adapte-se aos conceitos do Svelto.ECS. O uso de espaços para nome específicos ajuda a detectar erros de design quando entidades são usadas dentro de espaços para nome incompatíveis. Por exemplo, não se supõe que qualquer objeto inimigo seja usado dentro do espaço de nome do jogador, a menos que o objetivo seja violar as regras associadas à modularidade e ao acoplamento fraco de objetos. A idéia é que os objetos de um espaço para nome específico possam ser usados ​​apenas dentro dele ou no espaço para nome pai. É muito mais difícil usar o Svelto.ECS para transformar seu código em espaguete, onde as dependências são injetadas à direita e à esquerda, e essa regra o ajudará a aumentar ainda mais a qualidade da barra de código quando as dependências forem abstraídas corretamente entre as classes.

No Svelto.ECS, a abstração avança algumas linhas, mas o ECS essencialmente ajuda a abstrair dados da lógica que deve processar os dados. As entidades são determinadas por seus dados, não por seu comportamento. Nesse caso, os mecanismos são um lugar onde você pode colocar o comportamento conjunto de entidades idênticas para que os mecanismos possam sempre trabalhar com um conjunto de entidades.

O Svelto.ECS e o paradigma ECS permitem ao codificador alcançar um dos santos grails da pura programação, que é o encapsulamento ideal da lógica. Os motores não devem ter funções públicas. As únicas funções públicas que devem existir são aquelas necessárias para implementar as interfaces da estrutura. Isso leva ao esquecimento da injeção de dependência e ajuda a evitar códigos incorretos que ocorrem ao usar a injeção de dependência sem inversão de controle. Os motores NUNCA devem ser incorporados a qualquer outro mecanismo ou qualquer outro tipo de classe. Se você pensa em implementar o mecanismo, simplesmente comete um erro fundamental no design do código.

Comparado ao Unity MonoBehaviours, os mecanismos já mostram a primeira grande vantagem, que é a capacidade de acessar todos os estados de entidades desse tipo a partir da mesma área de código. Isso significa que o código pode facilmente usar o estado de todos os objetos diretamente do mesmo local em que a lógica do objeto comum será executada. Além disso, os mecanismos individuais podem processar os mesmos objetos para que o mecanismo possa alterar o estado do objeto, enquanto o outro mecanismo pode lê-lo, usando efetivamente dois mecanismos para comunicação através dos mesmos dados da entidade. Um exemplo pode ser visto nos mecanismos PlayerGunShootingEngine e PlayerGunShootingFxsEngine . Nesse caso, dois mecanismos estão no mesmo espaço para nome, para que eles possam compartilhar os mesmos dados da entidade. PlayerGunShootingEngine determina se um jogador (inimigo) foi danificado e grava o valor lastTargetPosition do componente IGunAttributesComponent (que é um componente PlayerGunEntity ). PlayerGunShootFxsEngine processa os efeitos gráficos da arma e lê a posição do alvo selecionado pelo jogador. Este é um exemplo de interação entre mecanismos por meio de pesquisa de dados. Mais adiante neste artigo, mostrarei como permitir que um mecanismo se comunique entre eles pressionando dados (envio de dados) ou ligação de dados (ligação de dados) . Logicamente, os mecanismos nunca devem armazenar estado.

Os mecanismos não precisam saber como interagir com outros mecanismos. A comunicação externa ocorre através da abstração, e o Svelto.ECS resolve a conexão entre os mecanismos de três maneiras oficiais diferentes, mas falarei sobre isso mais tarde. Os melhores mecanismos são aqueles que não requerem nenhuma comunicação externa. Esses mecanismos refletem um comportamento bem encapsulado e geralmente funcionam através de um loop lógico. Os loops são sempre modelados usando as tarefas Svelto.Task nos aplicativos Svelto.ECS. Como o movimento do jogador precisa ser atualizado a cada escala física, seria natural criar uma tarefa a ser executada em cada escala física. O Svelto.Tasks permite executar cada tipo de IEnumerator em vários tipos de agendadores. Nesse caso, decidimos criar uma tarefa no PhysicScheduler , que permite atualizar a posição do jogador:

 public PlayerMovementEngine(IRayCaster raycaster, ITime time) { _rayCaster = raycaster; _time = time; _taskRoutine = TaskRunner.Instance.AllocateNewTaskRoutine() .SetEnumerator(PhysicsTick()).SetScheduler(StandardSchedulers.physicScheduler); } protected override void Add(PlayerEntityView entityView) { _taskRoutine.Start(); } protected override void Remove(PlayerEntityView entityView) { _taskRoutine.Stop(); } IEnumerator PhysicsTick() { // ,      //  EnginesRoot    . // ,         . var _playerEntityViews = entityViewsDB.QueryEntityViews<PlayerEntityView>(); var playerEntityView = _playerEntityViews[0]; while (true) { Movement(playerEntityView); Turning(playerEntityView); //   yield,     ! yield return null; } } 

As tarefas do Svelto.Tasks podem ser executadas diretamente ou por meio de objetos ITaskRoutine . Não vou falar muito sobre Svelto. Tarefas aqui, pois escrevi outros artigos para ele. O motivo pelo qual decidi usar a rotina de tarefas em vez de iniciar a implementação do IEnumerator diretamente é bastante discricionário. Eu queria mostrar que você pode iniciar um ciclo quando o objeto de um jogador é adicionado ao mecanismo e pará-lo quando ele é excluído. , .

Svelto.ECS , , . Svelto.ECS, . , , , . , .

, SingleEntityViewEngine , MultiEntitiesViewEngine <EntityView1, ..., EntityViewN> . - , , .

IQueryingEntityViewEngine . . , - , , , , , , - . , , . , , . EnemyMovementEngine , :

 public void Ready() { Tick().Run(); } IEnumerator Tick() { while (true) { var enemyTargetEntityViews = entityViewsDB.QueryEntityViews<EnemyTargetEntityView>(); if (enemyTargetEntityViews.Count > 0) { var targetEntityView = enemyTargetEntityViews[0]; var enemies = entityViewsDB.QueryEntityViews<EnemyEntityView>(); for (var i = 0; i < enemies.Count; i++) { var component = enemies[i].movementComponent; component.navMeshDestination = targetEntityView.targetPositionComponent.position; } } yield return null; } } 

. Tick ().Run() IEnumerator Svelto.Tasks. IEnumerator , Enemy. , ( ), . Enemy Target ( !), , - . , Unity Nav Mesh System, , , NavMesh. , Unity NavMesh, , , Survival.

, Navmesh Unity. , , . , navMeshDestination Unity Nav Mesh.

, , , , . , , - , , .


, , . , 5 , Svelto.ECS, , , . (Node) (, ECS Ash ), , “” . EntityView , , (Model View Controller), Svelto.ECS View, EntityView — , . , , EntityMap, EntityView , . Svelto.ECS :



, . EntityViews. EntityViews, EntityViews. , Player, , PlayerEntityView . , , . EntityView . , ( . .), PlayerPhysicEngine PlayerPhysicEntityView , PlayerGraphicEngine PlayerGraphicEntityView PlayerAnimationEngine PlayerAnimationEntityView . , PlayerPhysicMovementEngine PlayerPhysicJumpEngine ( . .).


, , , , . , EntityView — , (public) . , , :

, — . , Svelto.ECS. . . « » (Interface Segregation Principle), , , , . ITransformComponent . , , ( , ).

Svelto.ECS , EntityView . «». , .

, . , ref, . , (data oriented), , . , ( !) . , , — , Unity. , Survival, , , Unity.


, . , , . , , EntityView — , . , , . , ECS. , . — , , . , , — . EntityDescriptor , . Player PlayerEntityDescriptor . , , , - , , BuildEntity<PlayerEntityDescriptor>() , .

, EntityDescriptor, — EntityViews!!! EntityViews , , , .

PlayerEntityDescriptor :

 using Svelto.ECS.Example.Survive.Camera; using Svelto.ECS.Example.Survive.HUD; using Svelto.ECS.Example.Survive.Enemies; using Svelto.ECS.Example.Survive.Sound; namespace Svelto.ECS.Example.Survive.Player { public class PlayerEntityDescriptor : GenericEntityDescriptor<HUDDamageEntityView, PlayerEntityView, EnemyTargetEntityView, DamageSoundEntityView, HealthEntityView, CameraTargetEntityView> { } } 

( ) , . PlayerEntityDescriptor EntityViews PlayerEntity.

EntityDescriptorHolder


EntityDescriptorHolder Unity . , Unity GameObject. , . , Robocraft , . . , GameObject MonoBehaviour's. , EntityDescriptorHolders , Svelto.ECS, . , :

 void BuildEntitiesFromScene(UnityContext contextHolder) { //EntityDescriptorHolder -    Svelto.ECS , //       . //         . //      , //    //     IEntityDescriptorHolder[] entities = contextHolder.GetComponentsInChildren<IEntityDescriptorHolder>(); //     Svelto.ECS, ,   //      . //        . //    EntityDescriptorHolder, //    for (int i = 0; i < entities.Length; i++) { var entityDescriptorHolder = entities[i]; var entityDescriptor = entityDescriptorHolder.RetrieveDescriptor(); _entityFactory.BuildEntity (((MonoBehaviour) entityDescriptorHolder).gameObject.GetInstanceID(), entityDescriptor, (entityDescriptorHolder as MonoBehaviour).GetComponentsInChildren<IImplementor>()); } } 

, , BuildEntity . . MonoBehaviour GameObject. . , , . , , MonoBehaviours , !


, Svelto.ECS, . , , C# . , , «». :

  • , .
  • , , .
  • . , .
  • Svelto.ECS (third party) . . Unity, , , Monobehaviour . , Unity, OnTriggerEnter / OnTriggerExit , Unity. , . :

 public class EnemyTriggerImplementor : MonoBehaviour, IImplementor, IEnemyTriggerComponent, IEnemyTargetComponent { public event Action<int, int, bool> entityInRange; bool IEnemyTriggerComponent.targetInRange { set { _targetInRange = value; } } bool IEnemyTargetComponent.targetInRange { get { return _targetInRange; } } void OnTriggerEnter(Collider other) { if (entityInRange != null) entityInRange(other.gameObject.GetInstanceID(), gameObject.GetInstanceID(), true); } void OnTriggerExit(Collider other) { if (entityInRange != null) entityInRange(other.gameObject.GetInstanceID(), gameObject.GetInstanceID(), false); } bool _targetInRange; } 

, , . , .


, , EnginesRoot , , , . . (Entity Factory), EnginesRoot GenerateEntityFactory . EnginesRoot IEntityFactory . , IEntityFactory .

IEntityFactory . PreallocateEntitySlots BuildMetaEntity , BuildEntity BuildEntityInGroup .

BuildEntityInGroup , Survival , , BuildEntity :

 IEnumerator IntervaledTick() { //  :       //MonoBehaviour    . //       //   . // ,     , //         . //        ,     . //  ,        //   , //  .      , // ,   ,   . var enemiestoSpawn = ReadEnemySpawningDataServiceRequest(); while (true) { //Svelto.Tasks    yield  Unity, //    . //       . // ,  , //    . yield return _waitForSecondsEnumerator; if (enemiestoSpawn != null) { for (int i = enemiestoSpawn.Length - 1; i >= 0 && _numberOfEnemyToSpawn > 0; --i) { var spawnData = enemiestoSpawn[i]; if (spawnData.timeLeft <= 0.0f) { //          int spawnPointIndex = Random.Range(0, spawnData.spawnPoints.Length); //       . var go = _gameobjectFactory.Build(spawnData.enemyPrefab); //        MonoBehaviour. //      . var data = go.GetComponent<EnemyAttackDataHolder>(); //     MonoBehaviour   // : List<IImplementor> implementors = new List<IImplementor>(); go.GetComponentsInChildren(implementors); implementors.Add(new EnemyAttackImplementor(data.timeBetweenAttacks, data.attackDamage)); //         EntityViews, //     EntityDescriptor. //,       EntityView //  ,     ,  EntityDescriptorHolder //       , //    . _entityFactory.BuildEntity<EnemyEntityDescriptor>( go.GetInstanceID(), implementors.ToArray()); var transform = go.transform; var spawnInfo = spawnData.spawnPoints[spawnPointIndex]; transform.position = spawnInfo.position; transform.rotation = spawnInfo.rotation; spawnData.timeLeft = spawnData.spawnTime; numberOfEnemyToSpawn--; } spawnData.timeLeft -= 1.0f; } } } } 

, Svelto.ECS. - BuildEntityInGroup , . Robocraft , , . , , , , — . , Svelto.Tasks , .

, , , … ( ):

MonoBehaviour . . , , . , . , , . , , , .

MonoBehaviour, . MonoBehaviour . , , json- , .

Svelto.ECS


, ECS, — . , , Svelto.ECS . — / , .

DispatchOnSet / DispatchOnChange


, (Data polling). DispatchOnSet DispatchOnChange ( ), , T . , (Push) , , . , , , , . DispatchOnSet DispatchOnChange , . , , , . Survival , , targetHit IGunHitTargetComponent . DispatchOnSet DispatchOnChange , , , .


, Svelto.Tasks (IEnumerators). , . , DispatchOnSet DispatchOnChange , , , “” , . , , , , . , ! . IEnumerator Svelto Tasks «» «» .

/


, Svelto.ECS Svelto.ECS. , , , Svelto.ECS, , , , .

Source: https://habr.com/ru/post/pt413107/


All Articles