Comment et pourquoi nous avons écrit notre ECS

Dans un article précédent, j'ai décrit les technologies et les approches que nous utilisons lors du développement d'un nouveau jeu de tir mobile rapide. Parce que c'était une revue et même un article superficiel - aujourd'hui, je vais approfondir et expliquer en détail pourquoi nous avons décidé d'écrire notre propre framework ECS et n'avons pas utilisé ceux existants. Il y aura des exemples de code et un petit bonus à la fin.


Qu'est-ce que l'ECS Ă  titre d'exemple


J'ai déjà brièvement décrit ce qu'est Entity Component System, et il y a des articles sur Habré à propos d'ECS (en gros, cependant, des traductions d'articles - voir ma revue des plus intéressants d'entre eux à la fin de l'article, en bonus). Et aujourd'hui, je vais vous dire comment nous utilisons ECS - en utilisant notre exemple de code.

Le diagramme ci-dessus décrit l'essence du lecteur, ses composants et leurs données, ainsi que les systèmes qui fonctionnent avec le lecteur et ses composants. L'objet clé du diagramme est le joueur:

  • peut se dĂ©placer dans l'espace - Composants Transform et Movement , MoveSystem ;
  • a une certaine santĂ© et peut mourir - composant Health , Damage , DamageSystem ;
  • après la mort apparaĂ®t au point de rĂ©apparition - le composant Transformer pour la position, le RespawnSystem ;
  • peut ĂŞtre invulnĂ©rable - composant Invincible .

Nous décrivons cela avec un code. Tout d'abord, obtenons des interfaces pour les composants et les systèmes. Les composants peuvent avoir des méthodes auxiliaires communes, le système n'a qu'une seule méthode Execute , qui reçoit l'état du monde à l'entrée pour le traitement:

public interface IComponent { // < > } public interface ISystem { void Execute(GameState gs); } 

Pour les composants, nous créons des classes de stub qui sont utilisées par notre générateur de code pour les convertir en code de composant réellement utilisé. Obtenons quelques blancs pour Health , Damage et Invincible (pour le reste des composants, ce sera similaire).

 [Component] public class Health { [Max(1000)] //  -  1000 public int Hp; // -   public Health(int hp) {} } [Component] public class Damage { [DontSend] //      ,      public uint Amount; // -  public Entity Victim; //    public Entity Source; //    public Damage(uint amount, Entity victim, Entity source) {} } [Component] public class Invincible //   ,  ,    { } 

Les composants déterminent l'état du monde, ils ne contiennent donc que des données, sans méthodes. En même temps, il n'y a pas de données dans Invincible , elles sont utilisées en logique comme signe d'invulnérabilité - si l'essence du joueur a cette composante, alors le joueur est désormais invulnérable.

L'attribut Component est utilisé par le générateur pour rechercher les classes vides pour les composants. Les attributs Max et DontSend sont nécessaires comme conseils lors de la sérialisation et de la réduction de la taille de l'état du monde transmis sur le réseau ou enregistrés sur le disque. Dans ce cas, le serveur ne sérialisera pas le champ Montant et ne l'enverra pas sur le réseau (car les clients n'utilisent pas ce paramètre, il n'est nécessaire que sur le serveur). Et le champ Hp peut être bien emballé en plusieurs bits, étant donné la valeur maximale de la santé.

Nous avons également une classe préfabriquée Entity , où nous ajoutons des informations sur tous les composants possibles de toute entité, et le générateur va déjà créer une classe réelle à partir de celle-ci:

 public class Entity { public Health Health; public Damage Damage; public Invincible Invincible; // ... < > } 

Après cela, notre générateur créera le code des classes de composants Health , Damage et Invincible , qui sera déjà utilisé dans la logique du jeu:

 public sealed class Health : IComponent { public int Hp; public void Reset() { Hp = default(int); } // ... <  > } public sealed class Damage : IComponent { public int Amount; public Entity Victim; public Entity Source; public void Reset() { Amount = default(int); Victim = default(Entity); Source = default(Entity); } // ... <  > } public sealed class Invincible : IComponent { } 

Comme vous pouvez le voir, les données sont restées dans les classes et des méthodes ont été ajoutées, par exemple, Réinitialiser . Il est nécessaire pour optimiser et réutiliser les composants dans les pools. D'autres méthodes auxiliaires ne contiennent pas de logique métier - je ne les donnerai pas par souci de concision.

Une classe sera également générée pour l'état du monde, qui contient une liste de tous les composants et entités:

 public sealed class GameState { //  public Table<Movement> Movements; public Table<Health> Healths; public Table<Damage> Damages; public Table<Transform> Transforms; public Table<Invincible> Invincibles; //   public Entity CreateEntity() { /* <> */ } public void Copy(GameState gs2) { /* <> */ } public Entity this[uint id] { /* <> */ } // ... <   > } 

Et enfin, le code généré pour Entity :

 public sealed class Entity { public uint Id; //   public GameState GameState; //     //     : public Health Health { get { return GameState.Healths[Id]; } } public Damage Damage { get { return GameState.Damages[Id]; } } public Invincible Invincible { get { return GameState.Invincibles[Id]; } } // …     public Damage AddDamage() { return GameState.Damages.Insert(Id); } public Damage AddDamage(int total, Entity victim, Entity source) { var c = GameState.Damages.Insert(Id); c.Amount = total; c.Victim = victim; c.Source = source; return c; } public void DelDamage() { GameState.Damages.Delete(Id); } // … <     > } 

La classe Entity n'est essentiellement qu'un identifiant de composant. La référence aux objets du monde GameState n'est utilisée que dans les méthodes auxiliaires pour la commodité de l'écriture du code logique métier. Connaissant l'identifiant d'un composant, nous pouvons l'utiliser pour sérialiser les relations entre entités, implémenter des liens dans les composants vers d'autres entités. Par exemple, le composant Damage contient une référence à l'entité Victime pour déterminer qui a été endommagé.

Ceci termine le code généré. En général, nous avons besoin d'un générateur pour ne pas écrire à chaque fois des méthodes auxiliaires. Nous décrivons uniquement les composants comme des données, puis le générateur fait tout le travail. Exemples de méthodes d'assistance:

  • crĂ©er / supprimer des entitĂ©s;
  • ajouter / supprimer / copier un composant, y accĂ©der s'il existe;
  • comparer deux Ă©tats du monde;
  • sĂ©rialiser l'Ă©tat du monde;
  • compression delta;
  • code d'une page Web ou d'une fenĂŞtre Unity pour afficher l'Ă©tat du monde, les entitĂ©s, les composants (voir dĂ©tails ci-dessous);
  • et autres

Passons au code système. Ils définissent la logique métier. Par exemple, écrivons le code d'un système qui calcule les dommages subis par un joueur:

 public sealed class DamageSystem : ISystem { void ISystem.Execute(GameState gs) { foreach (var damage in gs.Damages) { var invincible = damage.Victim.Invincible; if (invincible != null) continue; var health = damage.Victim.Health; if (health == null) continue; health.Hp -= damage.Amount; } } } 

Le système passe par tous les composants de dommages dans le monde et cherche à voir s'il y a un composant invincible sur un joueur potentiellement endommagé ( victime ). S'il l'est, le joueur est invulnérable, les dégâts ne sont pas accumulés. Ensuite, nous obtenons le composant Santé de la victime et réduisons la santé du joueur par la taille des dégâts.

Tenez compte des principales caractéristiques des systèmes:

  1. Un système est généralement une classe sans état, ne contient aucune donnée interne, n'essaie pas de l'enregistrer quelque part, à l'exception des données sur le monde transmises de l'extérieur.
  2. Les systèmes passent généralement par tous les composants d'un certain type et fonctionnent avec eux. Ils sont généralement appelés par le type de composant ( Damage → DamageSystem ) ou par l'action qu'ils effectuent ( RespawnSystem ).
  3. Le système implémente une fonctionnalité minimale. Par exemple, si nous allons plus loin, après l' exécution du DamageSystem, un autre RemoveDamageSystem supprimera tous les composants de Damage . Dans la coche suivante, un autre système ApplyDamage basé sur le tir du joueur peut à nouveau bloquer le composant Damage avec de nouveaux dégâts. Et puis le PlayerDeathSystem vérifiera la santé du joueur ( Health.Hp ) et, s'il est inférieur ou égal à 0, il détruira tous les composants du joueur à l'exception de Transform et ajoutera le composant Dead flag.

Au total, nous obtenons les classes suivantes et les relations entre elles:


Quelques faits sur ECS


ECS a ses avantages et ses inconvénients comme une approche du développement et une façon de représenter le monde du jeu, de sorte que chacun décide lui-même de l'utiliser ou non. Commençons par les pros:

  • Composition versus hĂ©ritage multiple. Dans le cas d'un hĂ©ritage multiple, un tas de fonctionnalitĂ©s inutiles peuvent ĂŞtre hĂ©ritĂ©es. Dans le cas d'ECS, la fonctionnalitĂ© apparaĂ®t / disparaĂ®t lorsqu'un composant est ajoutĂ© / supprimĂ©.
  • SĂ©paration de la logique et des donnĂ©es. La possibilitĂ© de changer la logique (changer de système, supprimer / ajouter des composants) sans casser les donnĂ©es. C'est-Ă -dire vous pouvez dĂ©sactiver le groupe de systèmes responsable d'une certaine fonctionnalitĂ© Ă  tout moment, tout le reste continuera Ă  fonctionner et cela n'affectera pas les donnĂ©es.
  • Le cycle de jeu est simplifiĂ©. Une mise Ă  jour apparaĂ®t et l'ensemble du cycle est divisĂ© en systèmes. Les donnĂ©es sont traitĂ©es par le «flux» dans le système, quel que soit le moteur (il n'y a pas des millions d'appels de mise Ă  jour , comme dans Unity).
  • Une entitĂ© ne sait pas quelles classes l'affectent (et ne doit pas savoir).
  • Utilisation efficace de la mĂ©moire . Cela dĂ©pend de la mise en Ĺ“uvre d'ECS. Vous pouvez rĂ©utiliser les objets et composants d'entitĂ© créés Ă  l'aide de pools; vous pouvez utiliser des types de valeurs pour les donnĂ©es et les stocker cĂ´te Ă  cĂ´te en mĂ©moire ( localitĂ© des donnĂ©es ).
  • Il est plus facile de tester lorsque les donnĂ©es sont sĂ©parĂ©es de la logique. Surtout quand on considère que la logique est un petit système avec plusieurs lignes de code.
  • Affichez et modifiez l'Ă©tat du monde en temps rĂ©el . Parce que l'Ă©tat du monde n'est que des donnĂ©es, nous avons Ă©crit un outil qui affiche sur la page web tout l'Ă©tat du monde dans un match sur le serveur (ainsi que la scène du match en 3D). Tout composant d'une entitĂ© peut ĂŞtre affichĂ©, modifiĂ©, supprimĂ©. La mĂŞme chose peut ĂŞtre effectuĂ©e dans l'Ă©diteur Unity pour le client.



Et maintenant les inconvénients:

  • Vous devez apprendre Ă  penser, concevoir et Ă©crire du code diffĂ©remment . Pensez en termes d'entitĂ©s, de composants et de systèmes. De nombreux modèles de conception dans ECS sont implĂ©mentĂ©s d'une manière complètement diffĂ©rente (voir un exemple d'implĂ©mentation du modèle State dans l'un des articles de revue Ă  la fin).
  • Plus de code . Discutable. D'une part, en raison du fait que nous divisons la logique en petits systèmes, au lieu de dĂ©crire toutes les fonctionnalitĂ©s dans une classe, il y a plus de classes, mais il n'y a pas beaucoup plus de code.
  • L'ordre dans lequel les systèmes sont appelĂ©s affecte le fonctionnement de l'ensemble du jeu . Habituellement, les systèmes dĂ©pendent les uns des autres, l'ordre de leur exĂ©cution est dĂ©fini par la liste et ils sont exĂ©cutĂ©s dans cet ordre. Par exemple, d'abord DamageSystem considère les dommages, puis RemoveDamageSystem supprime le composant Damage . Si vous modifiez accidentellement la commande, tout fonctionnera diffĂ©remment. En gĂ©nĂ©ral, cela est Ă©galement vrai pour le cas OOP habituel, si vous modifiez l'ordre des appels de mĂ©thode, mais il est plus facile de faire des erreurs dans ECS. Par exemple, si une partie de la logique s'exĂ©cute sur le client pour la prĂ©diction, l'ordre doit ĂŞtre le mĂŞme que sur le serveur.
  • Nous devons en quelque sorte connecter les donnĂ©es et les Ă©vĂ©nements de la logique avec la vue . Dans le cas d'Unity, nous avons MVP:

    - Modèle - GameState d'ECS;
    - Afficher - avec nous, ce sont exclusivement des classes MonoBehavior Unity standard ( Renderer , Text , etc.) et prefabs;
    - Le présentateur utilise le GameState pour déterminer les événements d'apparition / disparition d'entités, de composants, etc., crée des objets Unity à partir de préfabriqués et les modifie en fonction des changements de l'état du monde.

Saviez-vous que:

  • ECS ne concerne pas seulement la localisation des donnĂ©es . Pour moi, c'est plus un paradigme de programmation, un modèle, une autre façon de concevoir le monde du jeu - appelez-le comme vous voulez. La localisation des donnĂ©es n'est qu'une optimisation.
  • L'unitĂ© n'a pas d'ECS! Souvent, vous demandez aux candidats lors d'un entretien d'Ă©quipe - que savez-vous d'ECS? Si vous n’avez pas entendu, vous leur dites, et ils ont rĂ©pondu: "Ah, c’est comme dans Unity, alors je sais!". Mais non, ce n'est pas comme dans le moteur Unity. LĂ , les donnĂ©es et la logique sont combinĂ©es dans le composant MonoBehaviour , et GameObject (par rapport Ă  une entitĂ© dans ECS) a des donnĂ©es supplĂ©mentaires - un nom, une place dans la hiĂ©rarchie, etc. Les dĂ©veloppeurs Unity travaillent actuellement sur une implĂ©mentation normale d'ECS dans le moteur, et jusqu'Ă  prĂ©sent, il semble que ce sera bon. Ils ont embauchĂ© des spĂ©cialistes dans ce domaine - j'espère que ce sera cool.

Nos critères de sélection pour le cadre ECS


Lorsque nous avons décidé de créer un jeu sur ECS, nous avons commencé à chercher une solution toute faite et avons noté les conditions requises en fonction de l'expérience de l'un des développeurs. Et ils ont peint comment les solutions existantes répondent à nos exigences. C'était il y a un an, en ce moment, quelque chose aurait pu changer. Comme solutions, nous avons considéré:

  • Entitas
  • Artemis C #
  • Ash.net
  • ECS est notre propre solution au moment oĂą nous l'avons conçue. C'est-Ă -dire nos hypothèses et souhaits, ce que nous pouvons faire nous-mĂŞmes.

Nous avons compilé un tableau de comparaison, où j'ai également inclus notre solution actuelle (désignée comme ECS (maintenant) ):


Couleur rouge - la solution ne prend pas en charge notre exigence, orange - prend en charge partiellement, vert - prend entièrement en charge.

Pour nous, l'analogie des opérations d'accès aux composants et de recherche d'entités dans ECS était des opérations dans une base de données SQL. Par conséquent, nous avons utilisé des concepts tels que table (table), jointure (opération de jointure), indices (indices), etc.

Nous décrirons nos exigences et dans quelle mesure les bibliothèques et frameworks tiers leur correspondent:

  • ensembles de donnĂ©es sĂ©parĂ©s (historique, actuel, visuel, statique) - la possibilitĂ© d'obtenir et de stocker sĂ©parĂ©ment les Ă©tats du monde (par exemple, l'Ă©tat actuel pour le traitement, le rendu, l'historique des Ă©tats, etc.). Toutes les dĂ©cisions examinĂ©es appuyaient cette exigence .
  • ID d'entitĂ© sous forme d'entier - prise en charge de la reprĂ©sentation d'une entitĂ© par son numĂ©ro d'identification. Il est nĂ©cessaire pour la transmission sur le rĂ©seau et la capacitĂ© de connecter des entitĂ©s dans l'histoire des Ă©tats. Aucune des solutions considĂ©rĂ©es comme prises en charge. Par exemple, dans Entitas, une entitĂ© est reprĂ©sentĂ©e par un objet Ă  part entière (comme un GameObject dans Unity).
  • join by ID O (N + M) - prise en charge d'un Ă©chantillonnage relativement rapide de deux types de composants. Par exemple, lorsque vous devez obtenir toutes les entitĂ©s avec des composants de type DĂ©gâts (par exemple, leurs N pièces) et SantĂ© (M pièces) pour calculer et causer des dommages. Il y avait un soutien total Ă  Artemis; dans Entitas et Ash.NET, il est plus rapide que O (N²), mais plus lent que O (N + M). Je ne me souviens plus de l'Ă©valuation maintenant.
  • joindre par rĂ©fĂ©rence ID O (N + M) - le mĂŞme que ci-dessus uniquement lorsqu'un composant d'une entitĂ© a un lien avec un autre, et que ce dernier a besoin d'obtenir un autre composant (dans notre exemple, le composant Damage sur l'entitĂ© auxiliaire se rĂ©fère Ă  l'entitĂ© joueur Victime et Ă  partir de lĂ , vous devez obtenir le volet SantĂ© ). N'est pris en charge par aucune des solutions envisagĂ©es.
  • aucune allocation de requĂŞte - aucune allocation de mĂ©moire supplĂ©mentaire lors de l'interrogation de composants et d'entitĂ©s Ă  partir de l'Ă©tat du monde. Dans Entitas, c'Ă©tait dans certains cas, mais insignifiant pour nous.
  • tables de pool - stockage des donnĂ©es mondiales dans des pools, possibilitĂ© de rĂ©utiliser la mĂ©moire, allocation uniquement lorsque le pool est vide. Il y avait "un" support dans Entitas et Artemis, une absence totale dans Ash.NET.
  • comparer par ID (ajouter, supprimer) - prise en charge intĂ©grĂ©e des Ă©vĂ©nements de crĂ©ation / destruction d'entitĂ©s et de composants par ID. Il est nĂ©cessaire que le niveau d'affichage (Affichage) affiche / masque les objets, joue les animations, les effets. N'est pris en charge par aucune des solutions envisagĂ©es.
  • Δ sĂ©rialisation (quantification, skip) - compression delta intĂ©grĂ©e pour sĂ©rialiser l'Ă©tat du monde (par exemple, pour rĂ©duire la taille des donnĂ©es envoyĂ©es sur le rĂ©seau). Out of the Box n'Ă©tait pris en charge dans aucune des solutions.
  • L'interpolation est un mĂ©canisme d'interpolation intĂ©grĂ© entre les Ă©tats du monde. Aucune des solutions prises en charge.
  • rĂ©utiliser le type de composant - la possibilitĂ© d'utiliser une fois le type de composant Ă©crit dans diffĂ©rents types d'entitĂ©s. Entitas uniquement pris en charge .
  • ordre explicite des systèmes - la possibilitĂ© de dĂ©finir vos propres systèmes d'ordre d'appel. Toutes les dĂ©cisions sont appuyĂ©es.
  • Ă©diteur (unitĂ© / serveur) - prise en charge de la visualisation et de l'Ă©dition des entitĂ©s en temps rĂ©el, Ă  la fois pour le client et pour le serveur. Entitas a uniquement pris en charge la possibilitĂ© d'afficher et de modifier des entitĂ©s et des composants dans l'Ă©diteur Unity.
  • copie / remplacement rapide - la possibilitĂ© de copier / remplacer des donnĂ©es Ă  moindre coĂ»t. Aucune des solutions prises en charge.
  • composant comme type de valeur (struct) - composants comme types de valeur. En principe, je voulais atteindre de bonnes performances sur cette base. Aucun système n'Ă©tait pris en charge, les classes de composants Ă©taient partout.

Exigences facultatives ( aucune des solutions à l'époque ne les supportait ):

  • indices - indexation des donnĂ©es comme dans une base de donnĂ©es.
  • clĂ©s composites - clĂ©s complexes pour un accès rapide aux donnĂ©es (comme dans la base de donnĂ©es).
  • vĂ©rification d'intĂ©gritĂ© - la capacitĂ© de vĂ©rifier l'intĂ©gritĂ© des donnĂ©es dans un Ă©tat du monde. Utile pour le dĂ©bogage.
  • la compression sensible au contenu est la meilleure compression de donnĂ©es basĂ©e sur la connaissance de la nature des donnĂ©es. Par exemple, si nous connaissons la taille maximale de la carte ou le nombre maximum d'objets dans le monde.
  • types / systèmes limite - restriction du nombre de types de composants ou de systèmes. Ă€ l'Ă©poque, Ă  Artemis, il Ă©tait impossible de crĂ©er plus de 32 ou 64 types de composants et de systèmes .

Comme on peut le voir dans le tableau, nous voulions nous-mêmes implémenter toutes les exigences, sauf celles facultatives. En fait, pour le moment, nous n'avons pas fait:

  • joindre par ID O (N + M) et joindre par rĂ©fĂ©rence ID O (N + M) - la sĂ©lection pour deux composants diffĂ©rents occupe toujours O (N²) (en fait, une boucle imbriquĂ©e pour ). D'un autre cĂ´tĂ©, il n'y a pas autant d'entitĂ©s et de composants pour une correspondance.
  • comparer par ID (ajouter, supprimer) - pas nĂ©cessaire au niveau du framework. Nous l'avons implĂ©mentĂ© Ă  un niveau supĂ©rieur dans MVP.
  • copie / remplacement rapide et composant comme type de valeur (struct) - Ă  un moment donnĂ©, nous avons rĂ©alisĂ© que travailler avec des structures ne serait pas aussi pratique qu'avec des classes, et nous nous sommes installĂ©s sur les classes - nous avons prĂ©fĂ©rĂ© la commoditĂ© du dĂ©veloppement plutĂ´t que de meilleures performances. Soit dit en passant, les dĂ©veloppeurs d'Entitas ont finalement fait de mĂŞme .

Dans le même temps, nous avons néanmoins réalisé une des exigences initialement facultatives selon nous:

  • compression sensible au contenu - grâce Ă  elle, nous avons pu rĂ©duire de manière significative (des dizaines de fois) la taille du paquet transmis sur le rĂ©seau. Pour les rĂ©seaux de donnĂ©es mobiles, il est très important d'ajuster la taille du paquet dans le MTU afin qu'il ne soit pas «dĂ©composé» en petites parties qui pourraient se perdre, passer dans un ordre diffĂ©rent, puis devoir ĂŞtre assemblĂ© en parties. Par exemple, dans Photon, si la taille des donnĂ©es ne tient pas dans la bibliothèque MTU, il divise les donnĂ©es en paquets et les envoie comme fiables (avec livraison garantie), mĂŞme si vous les envoyez comme «non fiables» par le haut. TestĂ© avec douleur de première main.

Caractéristiques de notre développement chez ECS


  • Chez ECS, nous Ă©crivons exclusivement la logique mĂ©tier . Pas de travail avec les ressources, les vues, etc. Étant donnĂ© que le code logique ECS s'exĂ©cute simultanĂ©ment sur le client dans Unity et sur le serveur, il doit ĂŞtre aussi indĂ©pendant que possible des autres niveaux et modules.
  • Nous essayons de minimiser les composants et les systèmes . Habituellement, pour chaque nouvelle tâche, nous dĂ©marrons de nouveaux composants et systèmes. Mais il arrive parfois que nous modifions les anciens, ajoutions de nouvelles donnĂ©es aux composants et «gonflions» les systèmes.
  • Dans notre implĂ©mentation ECS, vous ne pouvez pas ajouter plusieurs composants du mĂŞme type Ă  une seule entitĂ© . Par consĂ©quent, si un joueur a Ă©tĂ© touchĂ© plusieurs fois en une seule fois (par exemple, plusieurs adversaires), nous crĂ©ons gĂ©nĂ©ralement une nouvelle entitĂ© pour chaque dĂ©gât et lui ajoutons un composant DĂ©gâts .
  • Parfois, la prĂ©sentation ne suffit pas des informations contenues dans le GameState . Ensuite, vous devez ajouter des composants spĂ©ciaux ou des donnĂ©es supplĂ©mentaires qui ne sont pas impliquĂ©s dans la logique, mais dont la vue a besoin. Par exemple, la prise de vue est instantanĂ©e sur le serveur, une tique vit et visuellement, elle est plus longue sur le client. Par consĂ©quent, pour le client, la prise de vue est ajoutĂ©e au paramètre "durĂ©e de vie de la prise de vue".
  • Nous mettons en Ĺ“uvre des Ă©vĂ©nements / demandes en crĂ©ant des composants spĂ©ciaux . Par exemple, si un joueur est dĂ©cĂ©dĂ©, nous lui accrochons un composant sans donnĂ©es Dead , qui est un Ă©vĂ©nement pour d'autres systèmes et le niveau de vue que le joueur est dĂ©cĂ©dĂ©. Ou si nous devons faire revivre le joueur sur le point, nous crĂ©ons une entitĂ© distincte avec le composant Respawn avec des informations supplĂ©mentaires sur qui revivre. Un RespawnSystem sĂ©parĂ© au tout dĂ©but du cycle de jeu passe par ces composants et crĂ©e dĂ©jĂ  l'essence du joueur. C'est-Ă -dire en fait, la première entitĂ© est une demande de crĂ©ation de la seconde.
  • Nous avons des composants / entitĂ©s «singleton» spĂ©ciaux . Par exemple, nous avons une entitĂ© avec ID = 1, sur laquelle pendent des composants spĂ©ciaux - les paramètres du jeu.

Bonus


— ECS — . , , , :

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


All Articles