Salut Hackernoon ! Le sujet suivant que j'ai choisi est ECS (Entity-Component-System) dans . Je l'ai divisé en deux parties pour vous aider à percevoir toutes les informations plus accessibles. le développement Unity Je vais vous dire tout ce que je sais sur Entity-Component-System et essayer de dissiper diverses idées préconçues sur cette approche. Vous trouverez de nombreux mots sur les avantages et les inconvénients de l'ECS, les particularités de cette approche, comment se lier d'amitié avec elle, les pièges potentiels et les pratiques utiles. J'examinerai également brièvement les frameworks ECS pour Unity/C#. Cet article sera bon pour ceux qui veulent/commencent à se familiariser avec ECS. J'espère que les personnes qui ont goûté à l'ECS pourront également mettre l'accent sur quelque chose de nouveau pour elles-mêmes. Si vous créez des jeux dans un langage autre que C#, vous pouvez toujours trouver cet article utile. Il n'y aura pas d'échantillons de code et d'historique du modèle, seulement mon expérience, mon raisonnement et mes observations :) Qu'est-ce qu'ECS (Entity-Component-System) ? Entity-Component-System est un modèle architectural créé spécifiquement pour le développement de jeux. Il est parfait pour décrire un monde virtuel dynamique. En raison de ses particularités, certaines personnes le considèrent presque comme un nouveau paradigme de programmation. ECS est un principe absolu de la composition sur l'héritage. Il peut s'agir d'un exemple particulier de conception orientée données (DOD), mais cela dépend de l'interprétation du modèle par une implémentation particulière. Décryptons le nom de ce pattern : - un objet au maximum abstrait. C'est un conteneur conditionnel pour les propriétés qui définissent ce que sera cette entité. Il est souvent représenté comme un identifiant pour accéder aux données. Entité - une propriété avec des données d'objet. Les composants d'ECS ne doivent contenir que des données pures sans une seule goutte de logique. Néanmoins, certains développeurs autorisent divers getters et setters dans les composants. Pourtant, je pense que les utilitaires statiques sont mieux adaptés à ces fins. Composant - la logique du traitement des données. Les systèmes dans ECS ne doivent contenir aucune donnée, uniquement une logique de traitement de données. Mais, encore une fois, certains développeurs lui permettent de définir certains comportements auxiliaires du système, par exemple des constantes ou divers services auxiliaires. Système Comme vous l'avez déjà compris, ECS sépare strictement les données de la logique. Le comportement d'un objet n'est pas déterminé par des interfaces/contrats/API publiques, comme nous en avons l'habitude dans la programmation orientée objet (POO) classique, mais par des propriétés attribuées à l'objet avec données + logique de traitement existant séparément. Dans ECS, les données définissent tout. C'est la principale propriété qui la distingue des autres approches de développement : tout est donnée. Les propriétés, les caractéristiques et les événements des objets ne sont que des données dans le monde ECS. La logique est simplement le traitement en pipeline de toutes ces données. Pourquoi ECS est-il nécessaire ? Vous avez probablement déjà une question : "Pourquoi ai-je besoin d'ECS ? A quoi ça sert ?". Et pour vous aider à décider si vous souhaitez lire cet article plus en détail, je vais vous dire pourquoi j'aime ECS. Personnellement, j'aime ECS parce que : Avec ECS, il vous suffit de vous asseoir et au lieu de vous battre avec l'architecture du projet. Il n'est pas nécessaire de construire de grandes et belles hiérarchies, de penser à de nombreuses connexions et de s'inquiéter de "X ne devrait pas connaître Y". Dans le même temps, les principes ECS vous protègent (pas à 100 %, bien sûr) de la situation désespérée causée par une mauvaise architecture lorsque le développement ultérieur du projet devient très pénible. Et même si quelque chose s'est mal passé, la refactorisation dans ECS n'est pas un problème. Et cela , à mon avis, est la meilleure chose à propos d'ECS. de créer un jeu dans Unity Le code sur ECS est simple et clair. Vous n'avez pas besoin de parcourir les appels entre les classes pour comprendre ce que fait un système particulier. Vous pouvez tout voir en même temps, surtout si vous divisez une fonctionnalité en systèmes, des systèmes en méthodes, et ne compliquez pas trop le code. De plus, ECS simplifie grandement le profilage. Vous pouvez voir immédiatement quelle logique (système) prend combien de temps de trame. Vous n'avez pas besoin de rechercher la source des décalages dans la profondeur des appels. Il est facile de manipuler la logique. L'ajout d'une nouvelle logique est pratiquement indolore. Il vous suffit d'insérer un nouveau système au bon endroit sans craindre d'affecter directement le reste du code (à noter qu'une influence indirecte via les données est possible). Vous pouvez utiliser une logique commune (systèmes) entre client et serveur sans aucun problème tout en conservant les données (composants) utilisées. Vous pouvez facilement réécrire des systèmes, en remplaçant les anciens systèmes par des systèmes refactorisés sans impact sur le reste du code. Si vous n'aimez pas le résultat, rallumez simplement l'ancien système. Le même mécanisme peut facilement organiser des tests A/B. Tout tourne autour des données. Cela s'avère extrêmement pratique. En manipulant directement les données sur les entités, les possibilités de combinatoire sont énormes. Vous pouvez utiliser des données pour transformer une entité en n'importe quoi. Et supposons que le framework offre des outils pour visualiser les données sur les entités. Dans ce cas, vous pouvez examiner les données et leur dynamique sur n'importe quelle entité sans exécuter de débogueur pour rechercher en mémoire. Maintenant, est-ce que tu me comprends ? Comment travailler avec ECS ? Ici, je vais décrire en termes simples comment fonctionne le processus de développement avec ECS dans l'exemple le plus simple. Je le ferai de la manière la plus abstraite possible sans faire référence à un langage de programmation. Si vous avez déjà une certaine expérience avec ECS, vous pouvez passer directement à la section suivante :) créer un objet qui se déplace dans la direction d'un vecteur de mouvement donné. Tâche : Tout d'abord, définissons les données dont nous avons besoin pour notre travail. Pour notre tâche, nous aurons besoin de la position de l'objet et du vecteur de mouvement donné. Dans le langage ECS, ce seront : PositionComponent pour stocker le vecteur de position MovementComponent pour le vecteur de mouvement L'étape suivante consiste à décrire la logique. Créons un . Dans la méthode principale du système, selon l'implémentation, cela peut être ou autre chose. Vous obtenez toutes les entités dans ECS qui ont et . La façon exacte dont cela peut être fait dépend du framework, mais cela ressemble souvent à une sorte de requête SQL comme . MovementSystem Run()/Execute()/Update() PositionComponent MovementComponent GetAllEntities().With<PositionComponent>().With<MovementComponent>() Et enfin, vous créez simplement une entité (même dix pièces) avec nos deux composants et définissez le vecteur de mouvement différent de zéro. Maintenant, à chaque appel de (peu importe où et quand nous l'appelons), notre objet changera de position dans la direction du vecteur de mouvement donné. Tâche accomplie ! :) MovementSystem Souvent, les systèmes sont en quelque sorte intégrés dans la GameLoop du projet et secouent chaque image par le moteur lui-même. Mais vous pouvez le faire à la main, et de toute autre manière, car il s'agit simplement d'un appel de méthode. Voyons quelles possibilités supplémentaires de développement nous avons en plus de résoudre le problème principal : N'importe lequel de nos autres systèmes peut déterminer si un objet se déplace en vérifiant simplement la présence de la propriété MovementComponent Tout autre système peut obtenir le vecteur de mouvement pour ses besoins N'importe lequel de nos autres systèmes pourra spécifier un vecteur de mouvement pour n'importe laquelle de nos entités à volonté Si nous le voulons, nous pouvons également faire bouger n'importe quelle autre entité en plaçant simplement et dessus. Ceci est très utile lors . PositionComponent MovementComponent de la création de jeux Unity Avantages d'ECS dans Unity Dans cette section, nous discuterons de ce qui est bon et de ce qui est mauvais avec ECS. Certaines des caractéristiques décrites ci-dessous ont les deux faces de la médaille. Ils sont à la fois bénéfiques pour le développement et inconfortables, créant des limites qu'il faut parfois contourner. Voyons d'abord les avantages d'ECS dans Unity. Faible cohésion du code Il s'agit d'une propriété avantageuse pour . Cela nous permet de refactoriser et d'étendre la base de code relativement facilement et sans casser d'anciens morceaux de code. Nous pouvons toujours ajouter un nouveau comportement en utilisant d'anciennes données sur le côté sans interférer avec l'ancienne logique de quelque manière que ce soit. ECS obtient cet effet car les données expriment toutes les interactions logiques dans Entity. Il s'agit d'un objet au maximum abstrait sans aucune garantie, comme certains objets en C#/Java. les développeurs de jeux Unity Cependant, vous devez vous rappeler que dans ECS, l'ordre des changements de données joue un rôle important. Cela peut éventuellement affecter la complexité de la refactorisation et casser votre ancienne logique ou même créer des bogues avec des effets secondaires désagréables. Parfaite modularité et testabilité de la logique Si toutes les interactions sont exprimées en données pures, notre logique est toujours complètement découplée de la source de données. Cela nous permet de déplacer la logique d'un projet à l'autre et de la réutiliser (tout en préservant le format des données, bien sûr), ainsi que d'exécuter la logique sur n'importe quelle donnée d'entrée pour tester son fonctionnement. Il est plus difficile d'écrire du mauvais code ECS est moins exigeant sur l'architecture car il définit le cadre avec lequel il est plus difficile de créer une très mauvaise conception de code. Dans le même temps, comme cela a été dit ci-dessus, nous pouvons résoudre le problème de manière relativement simple et avec un impact minimal sur le reste du code, même si une mauvaise conception de code se produit. ECS nous permet de moins réfléchir à "comment intégrer cette logique dans notre architecture sans rien casser" et d'ajouter de nouvelles fonctionnalités. Combinatoire des propriétés Cet avantage fait d'ECS une excellente option pour décrire des mondes dynamiques. Imaginez : vous pouvez donner n'importe quelle propriété (et donc logique) à n'importe laquelle de vos entités sans aucun tracas ! Si vous voulez que la caméra soit saine, vous pouvez mettre un sur la caméra. Il subira des dégâts (s'il existe un tel système). Mettez un sur une entité, et elle commencera immédiatement à subir des dégâts de brûlure si elle a un . Voulez-vous que la maison se déplace sous le contrôle du joueur ? Pas de problème, utilisez simplement . HealthComponent InFireComponent HealthComponent PlayerInputListenerComponent Un développeur expérimenté dira : "Ah, la plupart des modèles de composition sur héritage peuvent gérer cela. En quoi ECS est-il meilleur ?". Ma réponse est : "ECS vous permet de combiner des propriétés non seulement en termes de formation d'entités, mais également de créer une logique spécifique lors de la combinaison de plusieurs propriétés (composants) sur la même entité." Je n'ai même pas mentionné la possibilité d'ajouter une logique entièrement nouvelle pour les anciennes données sans toucher aux composants de l'entité ! Il est plus facile d'appliquer une responsabilité unique Lorsque nous avons une logique complètement séparée des données et non liée à un objet/entité, il devient plus facile de contrôler le partitionnement de la logique par son objectif plutôt que par sa place dans la hiérarchie. Chaque système exécute simplement une tâche spécifique qui lui est propre. Souvent, le code système ressemble à un seul appel de méthode pour plusieurs composants du même type. En conséquence, le code est généralement facile à lire et à percevoir. Un profilage plus clair Lors du profilage, nous pouvons voir quelle logique et combien de temps cela prend. Ceci est possible grâce à des systèmes séparés avec leur logique responsable du traitement. Nous n'avons pas besoin d'approfondir la pile d'appels pour comprendre ce qui prend le plus de temps. Nous pouvons immédiatement voir le coupable CharMovementSystem. Il convient de noter que cet avantage dépend du dispositif de framework ECS car le framework lui-même peut avoir sa pile d'appels. ECS peut donner une bonne amélioration des performances Beaucoup de gens pensent que de bonnes performances sont le principal avantage d'ECS (grâce à la propagande Unity). Ce n'est pas tout à fait vrai. La vitesse d'exécution du code n'est qu'un joli bonus résultant des principes du pattern : données à un endroit - logique à un autre + SIMD (instruction unique, données multiples). Et si le cadre suit DOD lors de la mise en œuvre d'ECS et atteint une bonne localité des données, nous obtenons également un code plus convivial pour le cache, ce qui rendra votre processeur heureux. Les performances ECS finales dépendent de nombreux facteurs : la manière exacte dont le framework stocke les données, la manière dont le framework filtre les entités, la rapidité avec laquelle les systèmes accèdent aux données et la rapidité avec laquelle le code à l'intérieur de vos systèmes fonctionne. Cependant, , ECS sera toujours plus rapide que l'approche MonoBehaviour habituelle, en particulier sur de grandes quantités de données. Mais n'oubliez pas que ce qui compte dans les performances de votre jeu n'est pas tant le modèle architectural que la complexité algorithmique et les performances du code que vous écrivez. dans le cadre du développement d'Unity Parallélisation plus facile du traitement des données Étant donné que la logique est séparée dans un processeur de données séparé et que les données sont en fait une séquence linéaire, nous pouvons paralléliser le traitement au sein d'un système sans aucun problème. Ceci est très important si le système traite simultanément un grand nombre d'entités et qu'elles ne se croisent en aucune façon. Vous pouvez aller encore plus loin et envoyer à différents threads une logique qui ne chevauche pas les données modifiées. Cependant, il est beaucoup plus difficile à contrôler et à surveiller. Pourtant, il y aura un goulot d'étranglement dans la synchronisation avec le thread principal pour préparer les données. En outre, il peut s'avérer que la surcharge de préparation et de distribution des données entre les threads sera supérieure au temps d'exécution du code dans vos systèmes. Par conséquent, vous devez évaluer si cela en vaut la peine. Il est très facile de travailler avec des données propres Dans presque tous les jeux Unity, nous devons enregistrer, charger ou sérialiser quelque chose à envoyer sur le réseau. C'est beaucoup plus facile lorsque les données sont séparées de la logique. Il n'est pas nécessaire de penser "Comment cela devrait-il entrer dans les données privées ..." et d'appeler des méthodes spéciales pour une sérialisation appropriée. Vous enregistrez/chargez simplement les composants nécessaires sur l'entité. Ensuite, le système le complétera jusqu'à l'état souhaité s'il le juge nécessaire. Vous pouvez modifier les frameworks ECS aussi souvent que vous le souhaitez Les frameworks ECS sont similaires les uns aux autres car les principes sont les mêmes. Un développeur qui a reconstruit son cerveau pour ECS et qui a bien compris un framework une fois peut travailler avec un autre framework ECS sans aucun problème. Apprendre l'API et les particularités d'un framework particulier ne prendra que du temps. Mais il n'y aura pas besoin de reconstruire votre tête pour la nouvelle approche. Inconvénients d'ECS dans Unity Comme vous pouvez le constater, ECS dans Unity présente de nombreux avantages précieux par rapport aux autres modèles. Parlons maintenant des inconvénients d'ECS dans Unity. Un seuil élevé pour les développeurs Unity expérimentés Bien que le concept ECS puisse être décrit en une phrase, apprendre à l'utiliser correctement peut demander beaucoup de pratique. ECS vous demande d'oublier tout ce que vous saviez auparavant sur la conception : toutes vos hiérarchies d'héritage verticales, que le comportement d'un objet est déterminé par son interface, qu'un objet est quelque chose de concret et d'immuable, qu'un objet peut avoir un espace privé et que la logique peut être appelé où vous voulez. Dans ECS, tout n'est pas comme ça. C'est le contraire de ce qui est décrit ci-dessus. Ici toutes les données sont ouvertes, toutes les entités sont abstraites et très dynamiques, leurs propriétés sont dans un seul plan et accessibles à tous, la logique fonctionne sur le principe du convoyeur, et le comportement des entités en général change à la volée en fonction des données. Une faible cohésion du code peut être un problème Supposons que vous ayez soudainement besoin d'une interaction étroite entre 2 entités concrètes (par exemple, un corps de chenille et une tourelle de char). Dans ce cas, vous êtes confronté au problème que les entités sont abstraites et vous ne pouvez pas garantir au niveau du compilateur que le corps de la chenille sera à l'autre extrémité. Cela gênera car les jeux Unity sont un lieu où il y a beaucoup d'interactions étroites, et vous voulez toujours avoir une référence directe avec une garantie de propriétés et de comportement. Vous devrez vérifier la présence du composant et gérer d'une manière ou d'une autre son absence, accéder au composant depuis l'entité pour commencer à interagir avec lui, etc. Accédez à toutes les données de n'importe où Le monde ECS est une boîte ouverte d'entités avec des données disponibles pour tous les composants. Comme la faible cohésion du code ci-dessus, c'est à la fois un avantage et un inconvénient d'ECS. D'une part, c'est extrêmement pratique. Vous n'avez pas besoin de comprendre comment contourner le cadre auto-limitant créé plus tôt dans le processus de conception ("X ne doit pas connaître Y") et divulguer des données précédemment cachées au public pour résoudre un problème immédiat. D'un autre côté, tout programmeur inexpérimenté essaiera de changer les données là où elles ne devraient pas être. Mais généralement, le travail d'équipe implique de faire confiance au travail des autres, alors faites confiance mais vérifiez ;) Les systèmes fonctionnent exclusivement en flux, les uns après les autres Lorsque vous suivez correctement les principes ECS, vous ne devez pas appeler la logique d'un système à l'intérieur d'un autre système. Les systèmes ne devraient pas du tout être conscients de l'existence de l'autre. Sinon, cela entraînera une cohésion inutile du code et pourrait nuire à votre projet. Cependant, cette limitation peut être gênante et conduira parfois à diverses solutions de contournement qui ne violeront pas les principes ECS. Si vous avez encore besoin d'appeler du code ici et maintenant, créez simplement un objet normal avec des méthodes et placez-le dans un composant, ne vous torturez pas. Ne fonctionne pas bien avec la logique récursive Cet inconvénient est une conséquence du précédent. En raison de l'impossibilité d'appeler du code système en dehors du thread et où nous le voulons, ECS rend presque impossible la création de code récursif en dehors d'un système particulier. Comme solution à cette lacune (c'est-à-dire une solution de contournement pour se conformer aux principes ECS), je ne peux que vous proposer de créer une structure/un système spécialisé qui appellera une liste spécifique de systèmes dans une boucle infinie tant qu'une condition particulière est remplie. Je veux dire, tant qu'il y a des entités avec un DoActionComponent. Si vous avez des solutions de contournement plus élégantes, je serais heureux de les lire dans les commentaires :) L'ordre d'exécution des systèmes est essentiel Dans ECS, il est crucial de comprendre et de contrôler la manière dont les systèmes modifient vos données. Il est souvent possible de manquer l'effet d'un système sur les données avec lesquelles nous travaillons et de se retrouver avec divers effets secondaires imprévus. Soit dit en passant, ils peuvent être compliqués à suivre (ce qui est le prochain inconvénient). Cependant, il est souvent possible, lors de l'écriture de systèmes, de les concevoir de telle manière que l'ordre dans lequel les systèmes sont invoqués n'a pas d'importance. Plus difficile à déboguer C'est un point assez controversé, en particulier avec les IDE intelligents modernes. En raison du manque de StackTrace approfondi (nous avons une logique dans nos systèmes qui n'est pas liée à l'entité) et de l'impossibilité de savoir comment et par qui les données et l'état de l'entité ont été modifiés, il peut être difficile de trouver la raison pour laquelle votre système tout à coup ne fonctionne pas comme prévu. Il n'est pas facile de comprendre ce qui a conduit à un appel particulier, même si quelqu'un vient d'ajouter un composant à l'entité ou de faire un ++ supplémentaire. Pour résumer, dans ECS, sans outils de débogage, il est difficile de savoir pourquoi et comment les données des composants ont changé, en particulier lorsque vous avez des milliers d'entités et une seule problématique. Cela peut être résolu par des outils de débogage que les frameworks peuvent fournir. Mais ils peuvent ne pas être disponibles immédiatement et vous devez les écrire vous-même ou en souffrir. Mauvaise option pour les structures de données, en particulier les structures hiérarchiques La mise en œuvre de structures de données avec ECS est difficile, peu pratique et, à mon avis, n'a aucun sens. Je ne dis pas que c'est impossible (si vous essayez assez fort, tout est possible), mais ce sera un chemin épineux sans grand avantage au bout du compte, alors soyez rationnel dans votre choix. Je vais énumérer quelques problèmes qui interféreront lors de la tentative de réalisation d'une structure de données sur ECS : Dans ECS, toutes les données sont accessibles de partout. Cela peut être extrêmement dangereux pour les structures de données où une cohérence maximale est requise. Tout "crocodile" qui passe peut modifier n'importe quelle donnée interne pour contourner votre logique, brisant complètement votre structure de données. Si nous suivons honnêtement les principes ECS, nous ne pouvons pas invoquer la logique de notre structure de données ici et maintenant, comme cela est généralement requis lorsque nous travaillons avec eux. Cependant, ce point peut être combattu avec des utilitaires/extensions statiques. ECS est représentatif des architectures horizontales. Toutes les données qu'il contient se trouvent dans un seul plan, presque toujours juste des tableaux unidimensionnels de composants. Cela complique la tâche si votre structure de données nécessite de la verticalité/hiérarchie. Il n'est pas rare que les structures de données nécessitent des références croisées entre les éléments (hiérarchie). Mais, comme vous vous en souvenez peut-être, tout tourne autour d'une Entité maximalement abstraite dans ECS. Cela rend le travail difficile car il n'y a aucune garantie d'un élément du type dont nous avons besoin à l'autre bout. En conséquence, il devra être traité séparément. La structure de données et ses éléments n'ont généralement pas besoin de changer de format de données lors de l'exécution, ni de combinatoire. Ils sont assez rigides. Chaque entité de structure de données peut finir par n'avoir qu'un seul composant. Supposons que vous ayez toujours besoin d'une structure de données. Dans ce cas, je vous recommande de le créer en tant qu'objet séparé avec des méthodes, puis de placer cet objet dans votre composant et de travailler avec lui à partir des systèmes comme d'habitude. Plus de fichiers et de classes Dans , le nombre de fichiers dans un projet croît plus rapidement que dans le cas d'un code similaire dans les approches classiques. Au moins parce qu'au lieu d'une classe avec données et logique, vous avez deux classes : composant et système (vous pouvez toujours les cacher dans un seul fichier). Tout au plus, si vous rendez tous les composants atomiques (1 composant - 1 champ), il y aura de très, très nombreux fichiers... l'approche ECS Code standard Cet inconvénient dépend fortement de l'implémentation spécifique du framework ECS. Dans certains frameworks, vous devez écrire beaucoup de code technique. Dans d'autres, le développeur a essayé de rendre l'API la plus simple possible et de minimiser le passe-partout. Mais, si vous le comparez à d'autres approches, il y a presque toujours au moins une petite quantité de code supplémentaire que vous devez écrire. Je veux dire déclarer des composants, obtenir un filtre avec les composants nécessaires, obtenir des entités à partir de celui-ci, obtenir un composant à partir d'une entité, etc. Petite conclusion C'est la fin de la 1 partie. Dans la 2 partie, j'aborderai : Erreurs de débutant dans ECS Bonnes pratiques en ECS Frameworks pour travailler avec ECS dans Unity/C# Si vous avez des questions, laissez-les dans les commentaires !