Introdução
Os sistemas de componentes de entidade são uma técnica de arquitetura orientada a objetos.
Não há consenso universal sobre o que o termo significa, o mesmo que programação orientada a objetos. No entanto, é claro que os sistemas de entidade-componente são especificamente planejados como uma alternativa arquitetural à herança . Hierarquias de herança são naturais para expressar o que um objeto é , mas em certos tipos de software (como jogos), você prefere expressar o que um objeto faz .
É um modelo de objeto diferente daquele das “classes e herança” ao qual você provavelmente está acostumado a trabalhar em C ++ ou Java. As entidades são tão expressivas quanto as classes, assim como os protótipos, como no JavaScript ou no Self - todos esses sistemas podem ser implementados em termos um do outro.
Exemplos
Vamos dizer que Player
é uma entidade com Position
, Velocity
e KeyboardControlled
componentes, que fazem as coisas óbvias.
entity Player:
Position
Velocity
KeyboardControlled
Sabemos que Position
deve ser afetado por Velocity
e Velocity
por KeyboardControlled
. A questão é como gostaríamos de modelar esses efeitos.
Entidades, componentes e sistemas
Suponha que os componentes não tenham referências um ao outro; um Physics
sistema externo percorre todos os Velocity
componentes e atualiza o Position
da entidade correspondente; um Input
sistema percorre todos os KeyboardControlled
componentes e atualiza o Velocity
.
Player
+--------------------+
| Position | \
| | Physics
/ | Velocity | /
Input | |
\ | KeyboardControlled |
+--------------------+
Isso satisfaz os critérios:
Os sistemas agora são responsáveis por manipular eventos e aprovar o comportamento descrito pelos componentes. Eles também são responsáveis por lidar com interações entre entidades, como colisões.
Entidades e componentes
No entanto, suponha que os componentes fazem ter referências a um outro. Agora, a entidade é simplesmente um construtor que cria alguns componentes, os une e gerencia sua vida útil:
class Player:
construct():
this.p = Position()
this.v = Velocity(this.p)
this.c = KeyboardControlled(this.v)
A entidade agora pode enviar eventos de entrada e atualização diretamente para seus componentes. Velocity
responderia às atualizações e KeyboardControlled
responderia à entrada. Isso ainda satisfaz nossos critérios:
Aqui, as interações de componentes são explícitas, não impostas de fora por um sistema. Os dados que descrevem um comportamento (qual é a quantidade de velocidade?) E o código que o representa (o que é velocidade?) São acoplados, mas de maneira natural. Os dados podem ser vistos como parâmetros para o comportamento. E alguns componentes não agem de maneira alguma - a Position
é o comportamento de estar em um lugar .
As interações podem ser tratadas no nível da entidade ("quando uma Player
colide com um Enemy
...") ou no nível de componentes individuais ("quando uma entidade com Life
colide com uma entidade com Strength
...").
Componentes
Qual o motivo da existência da entidade? Se for apenas um construtor, podemos substituí-lo por uma função que retorna um conjunto de componentes. Se depois desejarmos consultar entidades por tipo, também podemos ter um Tag
componente que nos permita fazer exatamente isso:
function Player():
t = Tag("Player")
p = Position()
v = Velocity(p)
c = KeyboardControlled(v)
return {t, p, v, c}
Agora, as interações devem ser tratadas por consultas abstratas, dissociando completamente os eventos dos tipos de entidade. Não há mais tipos de entidades a serem consultados - os Tag
dados arbitrários provavelmente são mais usados para depuração do que a lógica do jogo.
Conclusão
Entidades não são funções, regras, atores ou combinadores de fluxo de dados. São substantivos que modelam fenômenos concretos - em outras palavras, são objetos. É como a Wikipedia diz - sistemas de componentes de entidades são um padrão de arquitetura de software para modelar objetos gerais.
NÃO.E estou surpreso com quantas pessoas votaram de outra forma!
Paradigma
É orientado a dados, também conhecido como orientado a dados, porque estamos falando sobre a arquitetura e não a linguagem em que está escrita. As arquiteturas são realizações de estilos ou paradigmas de programação , que geralmente podem ser imprevisivelmente contornados em uma determinada linguagem.
Funcional?
Sua comparação com a programação funcional / processual é uma comparação relevante e significativa. Observe, no entanto, que uma linguagem "Funcional" é diferente do paradigma "Procedimental" . E você pode implementar um ECS em uma linguagem funcional como Haskell , o que as pessoas fizeram.
Onde a coesão acontece
Sua observação é relevante e direta :
ECS / ES não é EC / CE
Há uma diferença entre as arquiteturas baseadas em componentes, "Entity-Component" e "Entity-Component-System". Como esse é um padrão de design em evolução, vi essas definições usadas de forma intercambiável. As arquiteturas "CE" ou "CE" ou "Entidade-componente" colocam o comportamento em componentes , enquanto as arquiteturas "ES" ou "ECS" colocam o comportamento em sistemas . Aqui estão alguns artigos da ECS, os quais usam nomenclatura enganosa, mas transmitem a ideia geral:
Se você estiver tentando entender esses termos em 2015, verifique se a referência de alguém ao "Sistema de componentes de entidades" não significa "Arquitetura de componentes de entidades".
fonte
Os sistemas de componentes de entidade (ECSs) podem ser programados de maneira OOP ou funcional, dependendo de como o sistema está definido.
Maneira OOP:
Eu trabalhei em jogos em que uma entidade era um objeto composto de vários componentes. A entidade possui uma função de atualização que modifica o objeto em vigor, chamando atualização em todos os seus componentes. Isso é claramente OOP em estilo - o comportamento está vinculado aos dados e os dados são mutáveis. Entidades são objetos com construtores / destruidores / atualizações.
Maneira mais funcional:
Uma alternativa é a entidade ser dados sem nenhum método. Essa entidade pode existir por si só ou simplesmente ser um ID vinculado a vários componentes. Dessa forma, é possível (mas não é comumente feito) ser totalmente funcional e ter entidades imutáveis e sistemas puros que geram novos estados de componentes.
Parece (por experiência pessoal) que o último caminho está ganhando mais força e por boas razões. A separação dos dados da entidade do comportamento resulta em um código mais flexível e reutilizável (imo). Em particular, o uso de sistemas para atualizar componentes / entidades em lotes pode ter maior desempenho e evitar completamente as complexidades das mensagens entre entidades que afetam muitos ECSs de OOP.
TLDR: Você pode fazê-lo de qualquer maneira, mas eu argumentaria que os benefícios de bons sistemas de componentes de entidades derivam de sua natureza mais funcional.
fonte
Os sistemas de componentes de entidades orientadas a dados podem coexistir com os paradigmas orientados a objetos: - Os sistemas de componentes se prestam ao polimorfismo. - Os componentes podem ser POD (dados antigos simples) e também objetos (com uma classe e métodos), e a coisa toda ainda é 'orientada a dados', desde que os métodos de classe de componente manipulem apenas os dados pertencentes ao objeto local.
Se você escolher esse caminho, eu recomendo que você evite usar Métodos Virtuais, porque, se os tiver, seu componente não será mais apenas dados de componentes, além disso, esses métodos custarão mais para chamar - isso não é COM. Mantenha suas classes de componentes limpas de qualquer referência a algo externo, como regra.
Exemplo seria vec2 ou vec3, um contêiner de dados com alguns métodos para tocar esses dados e nada mais.
fonte
Penso que o ECS é fundamentalmente distinto do POO e tende a vê-lo da mesma maneira que você, mais próximo da natureza funcional ou especialmente processual, com uma separação muito distinta dos dados da funcionalidade. Há também alguma aparência de programação de um tipo que lida com bancos de dados centrais. Claro que sou a pior pessoa quando se trata de definições formais. Estou preocupado apenas com como as coisas tendem a ser, não com o que elas são definidas conceitualmente.
Estou assumindo um tipo de ECS em que os componentes agregam campos de dados e os tornam acessíveis ao público / globalmente, as entidades agregam componentes e os sistemas fornecem funcionalidade / comportamento nesses dados. Isso leva a características arquitetônicas radicalmente difíceis do que normalmente chamamos de base de código orientada a objetos.
E, é claro, há um pouco de confusão nos limites da maneira como as pessoas projetam / implementam um ECS, e há um debate sobre o que exatamente constitui um ECS em primeiro lugar. No entanto, esses limites também são embaçados no código escrito no que chamamos de linguagens funcionais ou processuais. Entre toda essa imprecisão, a constante fundamental de um ECS com uma separação de dados da funcionalidade parece muito mais próxima da programação funcional ou processual do que OOP.
Uma das principais razões pelas quais não acho útil considerar o ECS pertencer a uma classe de OOP é que a maioria das práticas de SE associadas ao OOP giram em torno da estabilidade da interface pública, com funções de modelagem de interfaces públicas , não dados. A idéia fundamental é que a maior parte das dependências públicas flua para funções abstratas, não para dados concretos. E por causa disso, o OOP tende a tornar muito caro alterar os comportamentos fundamentais do projeto, ao mesmo tempo em que torna muito barato alterar detalhes concretos (como dados e código necessários para implementar a funcionalidade).
O ECS é radicalmente diferente nesse aspecto, considerando como as coisas são acopladas à medida que a maior parte das dependências públicas flui em direção a dados concretos: dos sistemas aos componentes. Como resultado, quaisquer práticas de SE associadas ao ECS girariam em torno da estabilidade dos dados , porque as interfaces (componentes) mais públicas e amplamente usadas são na verdade apenas dados.
Como resultado, um ECS facilita muito a substituição de um mecanismo de renderização OpenGL por um DirectX, mesmo que os dois sejam implementados com funcionalidades radicalmente diferentes e não compartilhem os mesmos designs, desde que os mecanismos DX e GL ter acesso aos mesmos dados estáveis. Enquanto isso, seria muito caro e exigiria a reescrita de vários sistemas para alterar, por exemplo, a representação de dados de a
MotionComponent
.Isso é muito oposto ao que tradicionalmente associamos ao OOP, pelo menos em termos de características de acoplamento e o que constitui "interface pública" versus "detalhes de implementação privada". É claro que nos dois casos os "detalhes da implementação" são fáceis de alterar, mas no ECS é o design de dados que é caro mudar (dados não são um detalhe de implementação no ECS), e no OOP é o design da funcionalidade que é caro mudar (o design das funções não é um detalhe de implementação no OOP). Portanto, essa é uma ideia muito diferente de "detalhes de implementação", e um dos principais recursos para mim de um ECS do ponto de vista de manutenção era o que, em meu domínio, os dados necessários para fazer as coisas eram mais fáceis de estabilizar e projetar corretamente de uma vez por todas, com antecedência, do que todas as várias coisas que poderíamos fazer com esses dados (o que mudava o tempo todo à medida que os clientes mudavam de idéia e novas sugestões de usuários apareciam). Como resultado, achei os custos de manutenção em queda quando começamos a direcionar as dependências para longe das funções abstratas em direção a dados brutos e centrais (mas ainda com cuidado com os sistemas que acessam quais componentes para permitir a manutenção de invariantes em um nível são, apesar de todos os dados serem conceitualmente acessível globalmente).
E, no meu caso, pelo menos, o ECS SDK com a API e todos os componentes são realmente implementados em C e não tem nenhuma semelhança com OOP. Eu achei C mais do que adequado para esse propósito, dada a falta inerente de OO nas arquiteturas do ECS e o desejo de ter uma arquitetura de plug-in que possa ser usada pela maior variedade de idiomas e compiladores. Os sistemas ainda são implementados em C ++, já que o C ++ torna as coisas muito convenientes lá e os sistemas modelam a maior parte da complexidade, e acho isso útil para muitas coisas que podem ser consideradas mais próximas do OOP, mas isso é para detalhes de implementação. O projeto arquitetônico em si ainda se parece muito com o procedimento C.
Então eu acho que é um pouco confuso, no mínimo, tentar dizer que um ECS é OO por definição. No mínimo, os fundamentos fazem coisas que são uma volta completa de 180 graus a partir de muitos dos princípios fundamentais geralmente associados ao POO, começando com o encapsulamento e talvez terminando com o que seria considerado as características de acoplamento desejadas.
fonte