Mecanismo baseado em sistema de componentes de entidade

9

Nota: estou programando isso em Javascript, mas deve ser independente da linguagem na maior parte.

Estou pensando em converter meu mecanismo para um baseado em ECS.

Eu recebo a ideia básica ( nota: isso está errado, veja minha resposta ):

Entidades são objetos de jogo.
Componentes são bits de funcionalidade ( reactToInput()) ou state ( position) que podem ser "colados" às entidades.
Os sistemas têm uma lista de entidades que gerenciam e atualizam.

Mas não tenho certeza de obter a implementação e alguns detalhes ...

Pergunta: um sistema pode operar em diferentes tipos de entidades? Normalmente, dou o exemplo de uma classe chamada Sceneno meu mecanismo, e isso também servirá a esse propósito agora. Uma cena é um contêiner de todos os objetos que podem ser renderizados, atualizados, afetam a renderização (luzes) e, talvez, no futuro, até 2DSoundEmitterobjetos. Ele possui uma interface de alto nível, para que o usuário não precise se preocupar com o tipo de objeto que está scene.add()inserindo e todo esse tipo de coisa.

Eu percebo que o Scenepoderia ser um sistema. Ele pega entidades, as armazena e, em seguida, pode chamar seus métodos de atualização, e talvez até fazer algumas alterações de estado. Mas há um problema: como descrevi acima, eles Scenepodem ser alimentados com diferentes tipos de objetos! O que devo fazer em, digamos, uma situação em que uma cena tenha objetos renderizáveis ​​("desenháveis") e luzes nela? Devo fazê-lo verificar entidades antes de interagir? Ou devo resolvê-lo em um nível ainda mais baixo: crie um LightSourcecomponente que possa ser adicionado a qualquer objeto, e a luz seria apenas uma entidade com LightSourcee Positioncomponentes. Isso é aceitável?

Além disso, é uma boa prática ainda usar herança convencional e classes tradicionais? Por exemplo, eu simplesmente não consigo descobrir o que seria meu Renderer! Não é um sistema, pois sua única função é capturar uma câmera e uma cena, renderizar tudo e aplicar efeitos (como sombras). Ele também gerencia o contexto, a largura e a altura do jogo, faz traduções ... Mas ainda não é um sistema!

Editar: você poderia vincular algum recurso encontrado no ECS? Estou tendo problemas para encontrar bons.

jcora
fonte
2
Em vez de repassar a resposta nesta página, darei apenas este link: gamedev.stackexchange.com/questions/23533/… A entidade não deve ser derivada, quaisquer diferenças entre as entidades devem ser realizadas por meio de componentes. Geralmente você precisará de uma interface para cada sistema principal (Renderização, Física, Rede, Entrada, Áudio, etc ...). A maneira como configurei meu renderizador é consultar a cena quanto a entidades renderizáveis ​​e o gerenciador de cena que pede a cada entidade que possui um componente de renderização suas informações de renderização.
Nic Foster
11
Projeto componente no blogue T = máquina (desde que você pediu uma boa)
John McDonald
Código e discussão de uma estrutura de entidade: gamadu.com/artemis
Patrick Hughes
@JohnMcDonald, escrevi um comentário sobre esse artigo, embora esteja aguardando moderação. Você pode vê-lo aqui: t-machine.org/index.php/2007/12/22/… . Eu sou "Yannbane".
jcora
Além disso, @NicFoster, o artigo ao qual John vinculou no T = Machine descreve algo meio diferente da sua resposta ... Para aquele Dave, as entidades não têm uma lista de componentes, são apenas um nome. Como "flsjn304" - isso é uma entidade. É armazenado "em algum lugar". E preciso reler a coisa para entender se ele realmente mantém os componentes dentro dos sistemas , o que me parece muito estranho!
jcora

Respostas:

6

Deixe-me ver se, tentando entender como um desenvolvedor de JS da Web / UI, posso ajudar. Além disso, não exagere no agnosticismo da linguagem. Vale a pena estudar muitos padrões estabelecidos em outros idiomas, mas podem ser aplicados de maneira muito diferente no JS devido à sua flexibilidade ou realmente não são necessários devido à natureza maleável do idioma. Você pode aproveitar algumas oportunidades se escrever seu código pensando em JS como tendo o mesmo conjunto de limites que uma linguagem mais clássica orientada a OOP.

Primeiro, no fator "não use OOP", lembre-se de que os objetos JavaScript são como playdough em comparação com outros idiomas e você realmente precisa se esforçar para criar um pesadelo de esquema de herança em cascata, já que JS não é classe baseado em composições é muito mais natural. Se você estiver implementando algum sistema bobo de classe ou protótipo em seu JS, considere abandoná-lo. Em JS, usamos fechamentos, protótipos e passamos funções como doces. É nojento, sujo e errado, mas também poderoso, conciso e é assim que gostamos.

As abordagens pesadas da herança são na verdade apontadas como antipadrão nos Padrões de Design e, por uma boa razão, como qualquer pessoa que percorreu mais de 15 níveis de classe ou estruturas semelhantes a classes para tentar descobrir onde diabos a versão falida de um método estava vindo posso lhe dizer.

Não sei por que tantos programadores adoram fazer isso (especialmente os caras do Java que escrevem JavaScript por algum motivo), mas é horrível, ilegível e completamente impossível de manter quando usado em excesso. A herança é boa aqui e ali, mas não é realmente necessária no JS. Nas linguagens em que é um atalho mais atraente, ele deve ser realmente reservado para preocupações de arquitetura mais abstrata do que para esquemas de modelagem mais literais, como auxiliar na implementação de zumbis por meio de uma cadeia de herança que incluiu um BunnyRabbit porque funcionava. Isso não é uma boa reutilização de código. É um pesadelo de manutenção.

Como um mecanismo baseado em Entidade / Componente / Sistema do JS dev, parece-me um sistema / padrão para dissociar preocupações de design e depois compor objetos para implementação em um nível altamente granular. Em outras palavras, brincadeira de criança em uma linguagem como JavaScript. Mas deixe-me ver se estou grocando corretamente primeiro.

  • Entidade - A coisa específica que você está projetando. Estamos falando mais na direção de nomes próprios (mas não na verdade, é claro). Não é "Cena", mas "IntroAreaLevelOne". IntroAreaLevelOne pode estar dentro de uma caixa sceneEntity de algum tipo, mas estamos focando em algo específico que varia de outras coisas relacionadas. No código, uma entidade é realmente apenas um nome (ou ID) vinculado a um monte de coisas que ele precisa ter implementado ou estabelecido (os componentes) para ser útil.

  • Componentes - tipos de coisas que uma entidade precisa. Estes são substantivos gerais. Como WalkingAnimation. No WalkingAnimation, podemos ser mais específicos, como "Shambling" (boa opção para zumbis e monstros de plantas) ou "ChickenWalker" (ótimo para tipos de robôs com articulação reversa ed-209ish). Nota: Não tenho certeza de como isso poderia se dissociar da renderização de um modelo 3D como esse - talvez um exemplo de merda, mas eu sou mais um profissional em JS do que um desenvolvedor de jogos experiente. Em JS, eu colocaria o mecanismo de mapeamento na mesma caixa que os componentes. É provável que os componentes por si só sejam leves na lógica e mais um roteiro que diga aos seus sistemas o que implementar se forem necessários (na minha tentativa no ECS, alguns componentes são apenas conjuntos de conjuntos de propriedades). Depois que um componente é estabelecido, é '

  • Sistemas - A verdadeira carne do programa está aqui. Os sistemas de IA são construídos e vinculados, a renderização é alcançada, as sequências de animações são estabelecidas, etc. Estou copiando e deixando essas informações principalmente para a imaginação, mas no exemplo System.AI pega várias propriedades e cospe uma função que é usado para adicionar manipuladores de eventos ao objeto que acaba sendo usado na implementação. O principal do System.AI é que ele cobre vários tipos de componentes. Você pode resolver todas as coisas da IA ​​com um componente, mas fazer isso é entender mal o objetivo de tornar as coisas mais granulares.

Tenha em mente os objetivos: queremos facilitar a conexão de algum tipo de interface GUI para que os não designers ajustem facilmente diferentes tipos de coisas, maximizando e combinando componentes dentro de um paradigma que faça sentido para eles, e queremos nos afastar. esquemas de código arbitrário populares que são muito mais fáceis de escrever do que modificar ou manter.

Então, em JS, talvez algo parecido com isto. Desenvolvedores de jogos, por favor, me digam se estou errado:

//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);
}

Agora, sempre que precisar de um NPC, você poderá construir com npcBuilders.<npcName>();

Uma GUI pode conectar-se aos objetos npcEntities e components e permitir que os designers ajustem entidades antigas ou criem novas entidades simplesmente misturando e combinando componentes (embora não exista um mecanismo para componentes não padrão, mas componentes especiais podem ser adicionados rapidamente código, desde que houvesse um componente definido para ele.

Erik Reppen
fonte
Olhando para isso seis anos depois, não sei se entendi minha própria resposta. Isso poderia ser melhorado?
precisa saber é o seguinte
1

Eu li o Entity Systems nos artigos que pessoas gentis forneceram nos comentários, mas ainda tinha algumas dúvidas, então fiz outra pergunta .

Primeiro, minhas definições estavam erradas. Entidades e componentes são apenas detentores de dados burros, enquanto os sistemas fornecem todas as funcionalidades.

Eu aprendi o suficiente para cobrir a maior parte da minha pergunta aqui, então responderei.

A Sceneclasse que eu estava falando não deveria ser um sistema. No entanto, deve ser um gerente central que possa conter todas as entidades, facilitar mensagens e talvez até gerenciar sistemas. Também pode funcionar como uma espécie de fábrica para entidades, e eu decidi usá-lo assim. Ele pode tomar qualquer tipo de entidade, mas então ele deve alimentar essa entidade para um sistema adequado (o que, por motivos de desempenho, não deve realizar qualquer tipo de verificações, a menos que eles estão bit a bit).

Não devo usar OOP durante a implementação de um ES, sugere Adam, mas não encontro motivo para não ter objetos com métodos para entidades e componentes, não apenas para detentores de dados burros.

O Rendererpode simplesmente ser implementado como um sistema. Ele manteria uma lista de objetos desenháveis ​​e chamaria o draw()método do componente de renderização a cada 16ms.

jcora
fonte
11
"Entidades e componentes são apenas detentores de dados idiotas, enquanto os sistemas fornecem todas as funcionalidades" "chamam o método draw () do componente de renderização" você ainda está confuso, além de um método "draw" derrotar completamente o objetivo do sistema de renderização. Também não entendo por que o gráfico Scene não pode fazer parte do Renderer, é apenas uma ferramenta conveniente, você sempre pode implementar o componente "drawable" como um nó. Tornar o gráfico da cena responsável por mais do que a cena é apenas desnecessário e tenho certeza de que será uma bagunça para depuração.
Dreta
@dreta, o renderizador atualmente (implementação não-ES do mecanismo) faz transformações, alterações na câmera, coisas alfa e, no futuro, ele desenhará vários efeitos, a GUI e as sombras. Parecia natural agrupar essas coisas. A cena não deveria ser responsável por criar uma entidade de armazenamento? Ou algo mais deveria armazená-los? A parte de criação é provavelmente apenas algumas linhas de agregação de componentes fornecidos pelo usuário, não está realmente "criando" nada, apenas instanciando.
jcora
nem todo objeto é renderizável, nem todo objeto pode ser colidido ou emitir um som; com o seu objeto de cena, você está fazendo um acoplamento extremo, por que? isso vai ser difícil de escrever e depurar. As entidades são usadas para identificar um objeto, os componentes mantêm os dados e os sistemas operam nos dados. Por que você juntaria tudo isso em vez de ter sistemas adequados, como RenderingSystem e SoundSystem, e só os incomodaria se uma entidade tivesse todos os componentes necessários.
dreta 19/07/12
11
a projeção de sombras geralmente é anexada a fontes de luz, embora você possa criar um componente "CastsShadow" e procurá-lo ao renderizar objetos dinâmicos. se você estiver fazendo 2D, isso é apenas uma questão básica de pedido, um algoritmo simples do pintor resolverá esse problema para você. TBH você está se preocupando muito cedo. você descobrirá isso quando for a hora de fazê-lo e terá apenas isso em mente, agora você está apenas se confundindo. você não pode esperar acertar tudo da primeira vez, isso simplesmente não vai acontecer. você atravessará a ponte quando chegar lá.
dreta 19/07/12
11
"Entidades e componentes são apenas detentores de dados estúpidos, enquanto os sistemas fornecem todas as funcionalidades." Não necessariamente. Eles estão na abordagem de algumas pessoas. Mas não outros. Veja o mecanismo do Unity - todo o comportamento está nos componentes.
Kylotan
-2

Introdução ao gerenciamento de dependências 101.

Este curso pressupõe que você tenha conhecimentos básicos de injeção de dependência e design de repositório.

A injeção de dependência é apenas uma maneira elegante de os objetos se comunicarem (via mensagens / sinais / delegados / o que for) sem serem acoplados diretamente.

Ele segue a frase: " newé cola".

Vou demonstrar isso em 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; }
    }
}

Este é um sistema básico para entrada, movimento e renderização. A Playerclasse é a entidade nesse caso e os componentes são as interfaces. A Playerclasse não sabe nada sobre os detalhes internos de como um concreto IRenderSystem, IMovementSystemou IInputSystemtrabalho. No entanto, as interfaces fornecem uma maneira de Playerenviar sinais (por exemplo, invocar Draw no IRenderSystem) sem depender de como o resultado final é alcançado.

Por exemplo, considere minha implementação do 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; }
    }
}

MovementSystempode ter suas próprias dependências e Playerelas nem se importam. Usando interfaces, uma máquina de estado do jogo pode ser criada:

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();
        }
    }
}

E esse é o começo de um jogo adorável (que também é testável por unidade).

E com algumas adições e algum polimorfismo:

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();
        }
    }
}

Agora temos um sistema de entidade / componente primitivo baseado em interfaces de agregação e herança solta.

Dustin Kingen
fonte
11
Isto é tão contrário ao design de componentes :) O que você faria se quisesse que um jogador emitisse sons e outro não?
Kikaimaru
@Kikaimaru Passe em um ISoundSystem que não reproduz som. ou seja, não faz nada.
Dustin Kingen
3
-1, não porque é um código incorreto, mas porque não é relevante para a arquitetura baseada em componentes - na verdade, é a proliferação de interfaces como essa que os componentes tentam evitar.
Kylotan
@Kylotan Acho que meu entendimento deve estar errado.
Dustin Kingen