Vou falar com um pouco de experiência, passando de um design rígido de OO para um design de sistema de componentes de entidades (ECS).
Um tempo atrás eu era como você , eu tinha um monte de tipos diferentes de coisas que tinham propriedades semelhantes e construí vários objetos e tentei usar a herança para resolvê-lo. Uma pessoa muito inteligente me disse para não fazer isso e, em vez disso, use o Entity-Component-System.
Agora, o ECS é um grande conceito e é difícil de acertar. Há muito trabalho nisso, criando adequadamente entidades, componentes e sistemas. Antes de podermos fazer isso, porém, precisamos definir os termos.
- Entidade : é isso , o jogador, o animal, o NPC, o que for . É algo que precisa de componentes anexados a ele.
- Componente : este é o atributo ou propriedade , como "Nome" ou "Idade" ou "Pais", no seu caso.
- Sistema : essa é a lógica por trás de um componente ou comportamento . Normalmente, você cria um sistema por componente, mas isso nem sempre é possível. Além disso, às vezes os sistemas precisam influenciar outros sistemas.
Então, aqui é onde eu iria com isso:
Em primeiro lugar, crie um ID
para seus personagens. Um int
, o Guid
que você quiser. Esta é a "entidade".
Segundo, comece a pensar nos diferentes comportamentos que você está tendo. Coisas como a "Árvore Familiar" - isso é um comportamento. Em vez de modelar isso como atributos na entidade, construa um sistema que mantenha todas essas informações . O sistema pode então decidir o que fazer com ele.
Da mesma forma, queremos criar um sistema para "O personagem está vivo ou morto?" Este é um dos sistemas mais importantes no seu design, porque influencia todos os outros. Alguns sistemas podem excluir os caracteres "mortos" (como o sistema "sprite"), outros sistemas podem reorganizar internamente as coisas para melhor suportar o novo status.
Você criará um sistema "Sprite" ou "Desenho" ou "Renderização", por exemplo. Este sistema terá a responsabilidade de determinar com qual sprite o personagem precisa ser exibido e como exibi-lo. Então, quando um personagem morrer, remova-o.
Além disso, um sistema de "IA" que pode dizer a um personagem o que fazer, para onde ir etc. Isso deve interagir com muitos dos outros sistemas e tomar decisões com base neles. Novamente, os personagens mortos provavelmente podem ser removidos deste sistema, pois não estão mais fazendo nada.
Seu sistema "Nome" e o sistema "Árvore Familiar" provavelmente devem manter o personagem (vivo ou morto) na memória. Esse sistema precisa recuperar essas informações, independentemente do estado do personagem. (Jim ainda é Jim, mesmo depois de enterrá-lo.)
Isso também oferece o benefício de alterar quando um sistema reage com mais eficiência: o sistema possui seu próprio temporizador. Alguns sistemas precisam disparar rapidamente, outros não. É aqui que começamos a entender o que faz um jogo funcionar com eficiência. Não precisamos recalcular o clima a cada milissegundo, provavelmente podemos fazer isso a cada 5 ou mais.
Também oferece uma alavancagem mais criativa: você pode criar um sistema "Pathfinder" que pode lidar com o cálculo de um caminho de A para B e pode atualizar conforme necessário, permitindo que o sistema Movement diga "onde eu preciso Próximo?" Agora podemos separar completamente essas preocupações e argumentar sobre elas de maneira mais eficaz. O movimento não precisa encontrar o caminho, apenas o leva até lá.
Você deseja expor algumas partes de um sistema para o exterior. No seu Pathfinder
sistema, você provavelmente desejará a Vector2 NextPosition(int entity)
. Dessa forma, você pode manter esses elementos em matrizes ou listas rigidamente controladas. Você pode usar struct
tipos menores, que podem ajudá-lo a manter os componentes em blocos de memória menores e contíguos, o que pode tornar as atualizações do sistema muito mais rápidas. (Especialmente se as influências externas a um sistema são mínimas, agora ele só precisa se preocupar com seu estado interno, como Name
.)
Mas, e não posso enfatizar isso o suficiente, agora um Entity
é apenas um ID
, incluindo blocos, objetos etc. Se uma entidade não pertencer a um sistema, o sistema não o rastreará. Isso significa que podemos criar nossos objetos "Árvore", armazená-los nos sistemas Sprite
e Movement
(as árvores não se moverão, mas elas têm um componente "Posição") e mantê-los fora dos outros sistemas. Não precisamos mais de uma lista especial de árvores, pois renderizar uma árvore não é diferente de um personagem, além da boneca de papel. (Que o Sprite
sistema pode controlar, ou o Paperdoll
sistema pode controlar.) Agora, NextPosition
podemos ser ligeiramente reescritos: Vector2? NextPosition(int entity)
e pode retornar uma null
posição para as entidades que não interessam. Também aplicamos isso ao nosso NameSystem.GetName(int entity)
, ele retorna null
para árvores e rochas.
Vou encerrar isso, mas a idéia aqui é fornecer alguns conhecimentos sobre o ECS e como você pode realmente aproveitá-lo para oferecer um design melhor no seu jogo. Você pode aumentar o desempenho, dissociar elementos não relacionados e manter as coisas de maneira mais organizada. (Isso também combina bem com linguagens / configurações funcionais, como F # e LINQ, que eu recomendo verificar o F # se você ainda não o fez, ele combina muito bem com o C # quando você os usa em conjunto.)