Moteur basé sur Entity Component System

9

Remarque: je programme cela en Javascript, mais cela devrait être indépendant de la langue dans la plupart des cas.

Je pense à convertir mon moteur en moteur ECS.

J'ai l'idée de base ( note: c'est faux, voir ma réponse ):

Les entités sont des objets de jeu.
Les composants sont des éléments de fonctionnalité ( reactToInput()) ou d'état ( position) qui peuvent être «collés» aux entités.
Les systèmes ont une liste d'entités qu'ils gèrent et mettent à jour.

Mais, je ne suis pas tout à fait sûr d'avoir la mise en œuvre et quelques détails ...

Question: un système peut-il fonctionner sur différents types d'entités? Je donne généralement l'exemple d'une classe appelée Scenedans mon moteur, et elle servira également à cette fin maintenant. Une scène est un conteneur de tous les objets qui peuvent être rendus, mis à jour, affecter le rendu (lumières) et peut-être, à l'avenir, même des 2DSoundEmitterobjets. Il a une interface de haut niveau afin que l'utilisateur n'ait pas à se soucier du type d'objet qu'il scene.add()ingère et de tout ce genre de choses.

Je me rends compte que cela Scenepourrait être un système. Il prend des entités, les stocke, puis il peut appeler leurs méthodes de mise à jour et peut-être même effectuer des changements d'état. Mais, il y a un problème: comme je l'ai décrit ci-dessus, ils Scenepeuvent être alimentés en différents types d'objets! Que dois-je faire dans, disons, une situation où une scène contient à la fois des objets pouvant être rendus ("dessinables") et des lumières? Dois-je faire des vérifications de type avant d'interagir? Ou, devrais-je le résoudre à un niveau encore plus bas: créez un LightSourcecomposant qui peut être ajouté à n'importe quel objet, et la lumière ne serait qu'une entité avec LightSourceet des Positioncomposants. Est-ce acceptable?

En outre, est-ce une bonne pratique de continuer à utiliser l'héritage conventionnel et les classes traditionnelles? Par exemple, je n'arrive pas à comprendre ce que je Rendererserais! Ce n'est pas un système, car sa seule fonction est de prendre un appareil photo et une scène, de tout rendre et d'appliquer des effets (comme des ombres). Il gère également le contexte, la largeur et la hauteur du jeu, fait des traductions ... Mais ce n'est toujours pas un système!

Edit: pourriez-vous peut-être lier des ressources que vous avez trouvées sur l'ECS? J'ai du mal à en trouver de bons.

jcora
la source
2
Plutôt que de republier la réponse sur cette page, je vais simplement donner ce lien: gamedev.stackexchange.com/questions/23533/… L' entité ne doit pas être dérivée de, toutes les différences entre les entités doivent être accomplies via les composants. Généralement, vous aurez besoin d'une interface pour chaque système principal (rendu, physique, mise en réseau, entrée, audio, etc.). La façon dont j'ai configuré mon moteur de rendu consiste à interroger la scène pour les entités pouvant être rendues, et le gestionnaire de scènes demande ensuite à chaque entité qui a un composant de rendu pour ses informations de rendu.
Nic Foster
1
Conception des composants sur le blog T = Machine (puisque vous en avez demandé un bon)
John McDonald
Code et discussion d'un framework d'entité: gamadu.com/artemis
Patrick Hughes
@JohnMcDonald, j'ai écrit un commentaire sur cet article, bien qu'il soit en attente de modération. Vous pouvez le voir ici: t-machine.org/index.php/2007/12/22/… . Je suis "Yannbane".
jcora
De plus, @NicFoster, l'article auquel John a lié sur T = Machine décrit quelque chose d'un peu différent de votre réponse ... Pour Dave, les entités n'ont pas de liste de composants, ce sont juste des noms. Comme "flsjn304" - c'est une entité. Il est stocké "quelque part". Et je dois relire la chose pour comprendre s'il garde réellement des composants à l'intérieur des systèmes , ce qui me semble très étrange!
jcora

Réponses:

6

Permettez-moi de voir si en essayant de comprendre en tant que développeur JS Web / UI, je peux être utile. N'allez pas trop loin dans l'agnosticisme linguistique. De nombreux modèles établis dans d'autres langues méritent d'être étudiés, mais peuvent être appliqués très différemment dans JS en raison de sa flexibilité ou ne sont vraiment pas nécessaires en raison de la nature malléable de la langue. Vous pourriez souffler quelques opportunités si vous écrivez votre code en pensant que JS a le même ensemble de frontières qu'un langage orienté OOP plus classique.

Tout d'abord, sur le facteur "ne pas utiliser OOP", rappelez-vous que les objets JavaScript sont comme de la pâte à modeler par rapport à d'autres langages et vous devez en fait faire tout votre possible pour construire un cauchemar de schéma d'héritage en cascade puisque JS n'est pas une classe et le compositing lui viennent beaucoup plus naturellement. Si vous implémentez une classe idiote ou un prototype de système manuel dans votre JS, envisagez de l'abandonner. Dans JS, nous utilisons des fermetures, des prototypes et nous transmettons des fonctions comme des bonbons. C'est dégoûtant et sale et faux mais aussi puissant, concis et c'est ainsi que nous l'aimons.

Les approches lourdes d'héritage sont en fait énoncées comme un anti-modèle dans les modèles de conception et pour une bonne raison, toute personne qui a parcouru plus de 15 niveaux de classe ou des structures de type classe pour essayer de comprendre où diable la version éclatée d'une méthode venait de peut vous le dire.

Je ne sais pas pourquoi tant de programmeurs aiment faire cela (en particulier les gars de Java qui écrivent JavaScript pour une raison quelconque), mais c'est horrible, illisible et complètement impossible à entretenir lorsqu'il est utilisé à outrance. L'héritage est correct ici et là, mais pas vraiment nécessaire dans JS. Dans les langues où c'est un raccourci plus attrayant, il devrait vraiment être réservé à des préoccupations d'architecture plus abstraites plutôt qu'à des schémas de modélisation plus littéraux comme frankensteining une implémentation de zombie via une chaîne d'héritage qui comprenait un BunnyRabbit parce que cela fonctionnait. Ce n'est pas une bonne réutilisation du code. C'est un cauchemar d'entretien.

En tant que moteur JS dev Entity / Component / System, les moteurs me semblent être un système / modèle pour découpler les problèmes de conception, puis pour composer des objets à implémenter à un niveau très granulaire. En d'autres termes, un jeu d'enfant dans un langage comme JavaScript. Mais permettez-moi de voir si je suis en train de faire ça correctement en premier.

  • Entité - La chose spécifique que vous concevez. Nous parlons plus dans le sens des noms propres (mais pas en fait, bien sûr). Pas «Scene», mais «IntroAreaLevelOne». IntroAreaLevelOne peut s'asseoir dans une boîte sceneEntity d'une certaine sorte, mais nous nous concentrons sur quelque chose de spécifique qui diffère d'autres choses connexes. Dans le code, une entité n'est en fait qu'un nom (ou ID) lié à un tas de choses qu'elle doit avoir implémenté ou établi (les composants) pour être utile.

  • Composants - types de choses dont une entité a besoin. Ce sont des noms généraux. Comme WalkingAnimation. Dans WalkingAnimation, nous pouvons être plus précis, comme "Shambling" (bon choix pour les zombies et les monstres végétaux), ou "ChickenWalker" (idéal pour les types de robots ed-209ish à articulation inversée). Remarque: Je ne sais pas comment cela pourrait se dissocier du rendu d'un modèle 3D comme celui-ci - alors peut-être un exemple de merde, mais je suis plus un JS pro qu'un développeur de jeu expérimenté. Dans JS, je mettrais le mécanisme de mappage dans la même boîte avec les composants. Les composants à part entière sont susceptibles d'être légers sur la logique et plus d'une feuille de route indiquant à vos systèmes ce qu'il faut implémenter si des systèmes sont même nécessaires (dans ma tentative d'ECS, certains composants ne sont que des collections d'ensembles de propriétés). Une fois qu'un composant est établi, il '

  • Systèmes - La vraie viande programmée est ici. Les systèmes d'IA sont construits et liés, le rendu est réalisé, les séquences d'animations établies, etc ... Je les supprime et les laisse principalement à l'imagination mais dans l'exemple System.AI prend un tas de propriétés et crache une fonction qui est utilisé pour ajouter des gestionnaires d'événements à l'objet qui est finalement utilisé dans l'implémentation. L'essentiel sur System.AI est qu'il couvre plusieurs types de composants. Vous pouvez trier tous les éléments de l'IA avec un seul composant, mais le faire est de mal comprendre le point de rendre les choses granulaires.

Gardez à l'esprit les objectifs: nous voulons faciliter la connexion d'une sorte d'interface graphique pour les non-concepteurs afin de modifier facilement différents types de choses en maximisant et en faisant correspondre les composants dans un paradigme qui leur convient, et nous voulons nous éloigner de des schémas de code arbitraires populaires qui sont beaucoup plus faciles à écrire qu'à modifier ou à maintenir.

Donc, dans JS, peut-être quelque chose comme ça. Les développeurs de jeu, s'il vous plaît, dites-moi si je me trompe horriblement:

//I'm going with simple objects of flags over arrays of component names
//easier to read and can provide an opt-out default
//Assume a genre-bending stealth assassin game

//new (function etc... is a lazy way to define a constructor and auto-instantiate
var npcEntities = new (function NpcEntities(){

    //note: {} in JS is an object literal, a simple obj namespace (a dictionary)
    //plain ol' internal var in JS is akin to a private member
    var default={ //most NPCs are humanoids and critters - why repeat things?
        speedAttributes:true,
        maneuverAttributes:true,
        combatAttributes:true,
        walkingAnimation:true,
        runningAnimation:true,
        combatAnimation:true,
        aiOblivious:true,
        aiAggro:true,
        aiWary:true, //"I heard something!"
        aiFearful:true
    };

    //this. exposes as public

    this.zombie={ //zombies are slow, but keep on coming so don't need these
        runningAnimation:false,
        aiFearful:false
    };

    this.laserTurret={ //most defaults are pointless so ignore 'em
        ignoreDefault:true,
        combatAttributes:true,
        maneuverAttrubtes:true, //turning speed only
    };
    //also this.nerd, this.lawyer and on and on...

    //loop runs on instantiation which we're forcing on the spot

    //note: it would be silly to repeat this loop in other entity collections
    //but I'm spelling it out to keep things straight-forward.
    //Probably a good example of a place where one-level inheritance from
    //a more general entity class might make sense with hurting the pattern.
    //In JS, of course, that would be completely unnecessary. I'd just build a
    //constructor factory with a looping function new objects could access via
    //closure.

    for(var x in npcEntities){

        var thisEntity = npcEntities[x];

        if(!thisEntity.ignoreDefaults){

            thisEntity = someObjectXCopyFunction(defaults,thisEntity);
            //copies entity properties over defaults

        }
        else {
            //remove nonComponent property since we loop again later
            delete thisEntity.ignoreDefaults;
        }
    }
})() //end of entity instantiation

var npcComponents = {
    //all components should have public entityMap properties

    //No systems in use here. Just bundles of related attributes
    speedAttributes: new (function SpeedAttributes(){
        var shamblingBiped = {
            walkingAcceleration:1,
            topWalking:3
        },
        averageMan = {
            walkingAcceleration:3,
            runningAcceleration:4,
            topWalking: 4,
            topRunning: 6
        },
        programmer = {
            walkingAcceleration:1,
            runningAcceleration:100,
            topWalking:2
            topRunning:2000
        }; //end local/private vars

        //left is entity names | right is the component subcategory
        this.entityMap={
            zombie:shamblingBiped,
            lawyer:averageMan,
            nerd:programmer,
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(), //end speedAttributes

    //Now an example of an AI component - maps to function used to set eventHandlers
    //functions which, because JS is awesome we can pass around like candy
    //I'll just use some imaginary systems on this one

    aiFearful: new (function AiFearful(){
        var averageMan = Systems.AI({ //builds and returns eventSetting function
            fearThreshold:70, //%hitpoints remaining
            fleeFrom:'lastAttacker',
            tactic:'avoidIntercept',
            hazardAwareness:'distracted'
        }),
        programmer = Systems.AI({
            fearThreshold:95,
            fleeFrom:'anythingMoving',
            tactic:'beeline',
            hazardAwareness:'pantsCrappingPanic'
        });//end local vars/private members


         this.entityMap={
            lawyer:averageMan,
            nerd:averageMan, //nerds can run like programmers but are less cowardly
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(),//and more components...

    //Systems.AI is general and would get called for all the AI components.
    //It basically spits out functions used to set events on NPC objects that
    //determine their behavior. You could do it all in one shot but
    //the idea is to keep it granular enough for designers to actually tweak stuff
    //easily without tugging on developer pantlegs constantly.
    //e.g. SuperZombies, zombies, but slightly tougher, faster, smarter
}//end npcComponents

function createNPCConstructor(npcType){

    var components = npcEntities[npcType],

    //objConstructor is returned but components is still accessible via closure.

    objConstructor = function(){
        for(var x in components){
            //object iteration <property> in <object>

            var thisComponent = components[x];

            if(typeof thisComponent === 'function'){
                thisComponent.apply(this);
                //fires function as if it were a property of instance
                //would allow the function to add additional properties and set
                //event handlers via the 'this' keyword
            }
            else {
                objConstructor.prototype[x] = thisComponent;
                //public property accessed via reference to constructor prototype
                //good for low memory footprint among other things
            }
        }
    }
    return objConstructor;
}

var npcBuilders= {}; //empty object literal
for (var x in npcEntities){
    npcConstructors[x] = createNPCConstructor(x);
}

Maintenant, chaque fois que vous avez besoin d'un PNJ, vous pouvez construire avec npcBuilders.<npcName>();

Une interface graphique pourrait se connecter aux objets npcEntities et composants et permettre aux concepteurs de modifier d'anciennes entités ou de créer de nouvelles entités en mélangeant simplement et en faisant correspondre les composants (bien qu'il n'y ait pas de mécanisme pour les composants non par défaut, mais des composants spéciaux pourraient être ajoutés à la volée dans le code tant qu'il y avait un composant défini pour lui.

Erik Reppen
la source
En regardant cela six ans plus tard, je ne suis pas sûr de comprendre ma propre réponse. Cela pourrait-il être amélioré?
Erik Reppen
1

J'ai lu sur Entity Systems dans les articles que les gens aimables ont fournis dans les commentaires, mais j'avais encore des doutes, alors j'ai posé une autre question .

Tout d'abord, mes définitions étaient fausses. Les entités et les composants ne sont que des détenteurs de données stupides, tandis que les systèmes fournissent toutes les fonctionnalités.

J'ai suffisamment appris pour couvrir la plupart de ma question ici, alors je vais y répondre.

La Sceneclasse dont je parlais ne devrait pas être un système. Il devrait cependant s'agir d'un gestionnaire central qui peut contenir toutes les entités, faciliter les messages et peut-être même gérer les systèmes. Il peut également fonctionner comme une sorte d'usine pour les entités, et j'ai décidé de l'utiliser comme ça. Il peut prendre n'importe quel type d'entité, mais il doit ensuite alimenter cette entité vers un système approprié (qui, pour des raisons de performances, ne doit effectuer aucune vérification de type, sauf s'il est au niveau du bit).

Je ne devrais pas utiliser de POO lors de l'implémentation d'un ES, suggère Adam, mais je ne trouve aucune raison de ne pas avoir d'objets avec des méthodes pour les entités et les composants, pas seulement des détenteurs de données stupides.

Le Rendererpeut simplement être implémenté en tant que système. Il conserverait une liste d'objets dessinables et appellerait la draw()méthode de leur composant de rendu toutes les 16 ms.

jcora
la source
1
"Les entités et les composants ne sont que des détenteurs de données stupides, tandis que les systèmes fournissent toutes les fonctionnalités" "appelez la méthode draw () de leur composant de rendu", vous êtes toujours confus, en plus d'une méthode "draw" vainc complètement le purpouse du système de rendu. De plus, je ne comprends pas pourquoi votre graphe de scène ne peut pas faire partie du rendu, c'est juste un outil pratique, vous pouvez toujours implémenter votre composant "dessinable" en tant que nœud. Rendre le graphique de scène responsable de plus que la scène n'est tout simplement pas nécessaire et je suis sûr que ce sera un gâchis à déboguer.
dreta
@dreta, le rendu actuellement (implémentation non-ES du moteur) fait des transformations, des changements de caméra, des trucs alpha, et à l'avenir il dessinera divers effets, l'interface graphique et les ombres. Il semblait naturel de regrouper ces trucs. La scène ne devrait-elle pas être responsable de la création d'une entité de stockage? Ou quelque chose d'autre devrait-il les stocker? La partie création n'est probablement que quelques lignes d'agrégation de composants fournis par l'utilisateur, ce n'est pas vraiment "créer" quoi que ce soit, juste une instanciation.
jcora
tous les objets ne peuvent pas être rendus, tous les objets ne peuvent pas entrer en collision ou émettre un son, avec votre objet de scène, vous faites un couplage extrême, pourquoi? cela va juste être pénible à écrire et à déboguer. Les entités sont utilisées pour identifier un objet, les composants contiennent des données et les systèmes fonctionnent sur les données. Pourquoi voudriez-vous écraser tout cela ensemble au lieu d'avoir des systèmes appropriés comme RenderingSystem et SoundSystem et ne déranger ces systèmes que si une entité possède tous les composants requis.
dreta
1
la projection d'ombres est généralement attachée aux sources de lumière, bien que vous puissiez simplement créer un composant "CastsShadow" et le rechercher lors du rendu d'objets dynamiques. si vous faites de la 2D, ce n'est qu'un problème de commande de base, un simple algorithme de peintre résoudra ce problème pour vous. TBH vous vous inquiétez trop tôt. vous comprendrez cela quand il est temps de le faire et vous n'avez que cela à l'esprit, en ce moment, vous vous confondez. vous ne pouvez pas espérer que tout se passe bien la première fois, cela ne se produira tout simplement pas. vous traverserez ce pont quand vous y arriverez.
dreta
1
"Les entités et les composants ne sont que des détenteurs de données stupides, tandis que les systèmes fournissent toutes les fonctionnalités." Pas nécessairement. Ils sont dans l'approche de certaines personnes. Mais pas les autres. Regardez le moteur Unity - tout le comportement est dans les composants.
Kylotan
-2

Introduction à la gestion des dépendances 101.

Ce cours suppose que vous avez des connaissances de base sur l'injection de dépendances et la conception de référentiels.

L'injection de dépendance n'est qu'un moyen sophistiqué pour que les objets se parlent (via des messages / signaux / délégués / quoi que ce soit) sans être directement couplés.

Il va par la phrase: " newest de la colle."

Je vais le démontrer en C #.

public interface IEntity
{
    int[] Position { get; }
    int[] Size { get; }
    bool Update();
    void Render();
}

public interface IRenderSystem
{
    void Draw(IEntity entity);
}

public interface IMovementSystem
{
    bool CanMoveLeft();
    void MoveLeft();
    bool CanMoveRight();
    void MoveRight();
    bool CanMoveUp();
    void MoveUp();
    bool CanMoveDown();
    void MoveDown();
    bool Moved();
    int[] Position { get; set; }
}

public interface IInputSystem
{
    string Direction { get; set; }
}

public class Player : IEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public bool Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();

        return _movementSystem.Moved();
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

Il s'agit d'un système de base pour l'entrée, le mouvement et le rendu. La Playerclasse est l'entité dans ce cas et les composants sont les interfaces. La Playerclasse ne sait rien sur les entrailles de la façon dont un béton IRenderSystem, IMovementSystemou le IInputSystemtravail. Cependant, les interfaces fournissent un moyen Playerd'envoyer des signaux (par exemple, invoquer Draw sur le système IRender) sans dépendre de la manière dont le résultat final est atteint.

Par exemple, prenez mon implémentation d'IMovementSystem:

public interface IGameMap
{
    string LeftOf(int[] currentPosition);
    string RightOf(int[] currentPosition);
    string UpOf(int[] currentPosition);
    string DownOf(int[] currentPosition);
}

public class MovementSystem : IMovementSystem
{
    private readonly IGameMap _gameMap;
    private int[] _previousPosition;
    private readonly int[] _currentPosition;
    public MovementSystem(IGameMap gameMap, int[] initialPosition)
    {
        _gameMap = gameMap;
        _currentPosition = initialPosition;
        _previousPosition = initialPosition;
    }

    public bool CanMoveLeft()
    {
        return _gameMap.LeftOf(_currentPosition) == "Unoccupied";
    }

    public void MoveLeft()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]--;
    }

    public bool CanMoveRight()
    {
        return _gameMap.RightOf(_currentPosition) == "Unoccupied";
    }

    public void MoveRight()
    {
        _previousPosition = _currentPosition;
        _currentPosition[0]++;
    }

    public bool CanMoveUp()
    {
        return _gameMap.UpOf(_currentPosition) == "Unoccupied";
    }

    public void MoveUp()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]--;
    }

    public bool CanMoveDown()
    {
        return _gameMap.DownOf(_currentPosition) == "Unoccupied";
    }

    public void MoveDown()
    {
        _previousPosition = _currentPosition;
        _currentPosition[1]++;
    }

    public bool Moved()
    {
        return _previousPosition == _currentPosition;
    }

    public int[] Position
    {
        get { return _currentPosition; }
    }
}

MovementSystempeut avoir ses propres dépendances et Playerne s'en soucierait même pas. En utilisant des interfaces, une machine d'état du jeu peut être créée:

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IEntity>();
        foreach (var entity in _entities)
        {
            if(entity.Update())
                _renderQueue.Add(entity);
        }
        // Linq version for those interested
        //_renderQueue.AddRange(_entities.Where(e => e.Update()));
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

Et c'est le début d'un beau jeu (qui est également testable à l'unité).

Et avec quelques ajouts et un certain polymorphisme:

public interface IEntity
{
}

public interface IRenderableEntity : IEntity
{
    void Render();        
}

public interface IUpdateableEntity : IEntity
{
    void Update();
    bool Updated { get; }
}

public interface IRenderSystem
{
    void Draw(IRenderableEntity entity);
}

// new player class
public class Player : IRenderableEntity, IUpdateableEntity
{
    private readonly IInputSystem _inputSystem;
    private readonly IMovementSystem _movementSystem;
    private readonly IRenderSystem _renderSystem;
    private readonly int[] _size = new[] { 10, 10 };

    public Player(IRenderSystem renderSystem, IMovementSystem movementSystem, IInputSystem inputSystem)
    {
        _renderSystem = renderSystem;
        _movementSystem = movementSystem;
        _inputSystem = inputSystem;
    }

    public void Update()
    {
        if (_inputSystem.Direction == "Left" && _movementSystem.CanMoveLeft())
            _movementSystem.MoveLeft();
        if (_inputSystem.Direction == "Right" && _movementSystem.CanMoveRight())
            _movementSystem.MoveRight();
        if (_inputSystem.Direction == "Up" && _movementSystem.CanMoveUp())
            _movementSystem.MoveUp();
        if (_inputSystem.Direction == "Down" && _movementSystem.CanMoveDown())
            _movementSystem.MoveDown();
    }

    public bool Updated
    {
        get { return _movementSystem.Moved(); }
    }

    public void Render()
    {
        if (_movementSystem.Moved())
            _renderSystem.Draw(this);
    }

    public int[] Position
    {
        get { return _movementSystem.Position; }
    }

    public int[] Size
    {
        get { return _size; }
    }
}

public class GameEngine
{
    private readonly List<IEntity> _entities;
    private List<IRenderableEntity> _renderQueue; 

    public GameEngine()
    {
        _entities = new List<IEntity>();
    }

    public void RegisterEntity(IEntity entity)
    {
        _entities.Add(entity);
    }

    public void Update()
    {
        _renderQueue = new List<IRenderableEntity>();
        foreach (var entity in _entities)
        {
            if (entity is IUpdateableEntity)
            {
                var updateEntity = entity as IUpdateableEntity;
                updateEntity.Update();
            }

            if (entity is IRenderableEntity)
            {
                var renderEntity = entity as IRenderableEntity;
                _renderQueue.Add(renderEntity);
            }
        }
    }

    public void Render()
    {
        foreach (var entity in _renderQueue)
        {
            entity.Render();
        }
    }
}

Nous avons maintenant un système entité / composant primitif basé sur des interfaces d'agrégation et un héritage lâche.

Dustin Kingen
la source
1
C'est contre la conception des composants :) Que feriez-vous si vous vouliez qu'un joueur fasse des sons et d'autres pas?
Kikaimaru
@Kikaimaru Passez dans un ISoundSystem qui ne lit pas le son. c'est à dire ne fait rien.
Dustin Kingen
3
-1, non pas parce que c'est du mauvais code, mais parce qu'il n'est pas du tout pertinent pour l'architecture basée sur les composants - en fait, c'est la prolifération d'interfaces comme celle-ci que les composants essaient d'éviter.
Kylotan
@Kylotan Je suppose que ma compréhension doit être fausse.
Dustin Kingen