Essa é uma pergunta difícil de responder, porque todos têm sua própria idéia sobre como um sistema de componentes de entidade deve ser estruturado. O melhor que posso fazer é compartilhar com você algumas das coisas que considero mais úteis para mim.
Entidade
Eu adoto a abordagem de classe gorda do ECS, provavelmente porque acho que métodos extremos de programação são altamente ineficientes (em termos de produtividade humana). Para esse fim, uma entidade para mim é uma classe abstrata a ser herdada por classes mais especializadas. A entidade possui várias propriedades virtuais e um sinalizador simples que informa se essa entidade deve ou não existir. Portanto, em relação à sua pergunta sobre um sistema de renderização, é Entity
assim:
public abstract class Entity {
public bool IsAlive = true;
public virtual SpatialComponent Spatial { get; set; }
public virtual ImageComponent Image { get; set; }
public virtual AnimationComponent Animation { get; set; }
public virtual InputComponent Input { get; set; }
}
Componentes
Os componentes são "estúpidos", pois não fazem nem sabem nada. Eles não têm referências a outros componentes e normalmente não têm funções (trabalho em C #, então uso propriedades para manipular getters / setters - se eles tiverem funções, serão baseados na recuperação de dados que possuem).
Sistemas
Os sistemas são menos "estúpidos", mas ainda são autômatos estúpidos. Eles não têm contexto do sistema geral, não têm referências a outros sistemas e não mantêm dados, exceto por alguns buffers de que talvez precisem para fazer seu processamento individual. Dependendo do sistema, ele pode ter um método especializado Update
ou Draw
, em alguns casos, ambos.
Interfaces
As interfaces são uma estrutura importante no meu sistema. Eles são usados para definir o que um System
processo pode e o que um Entity
é capaz. As interfaces que são relevantes para a renderização são: IRenderable
e IAnimatable
.
As interfaces simplesmente informam ao sistema quais componentes estão disponíveis. Por exemplo, o sistema de renderização precisa conhecer a caixa delimitadora da entidade e a imagem a desenhar. No meu caso, isso seria oe SpatialComponent
o ImageComponent
. Então fica assim:
public interface IRenderable {
SpatialComponent Component { get; }
ImageComponent Image { get; }
}
O sistema Rendering
Então, como o sistema de renderização desenha uma entidade? Na verdade, é bastante simples, então mostrarei a classe simplificada para ter uma idéia:
public class RenderSystem {
private SpriteBatch batch;
public RenderSystem(SpriteBatch batch) {
this.batch = batch;
}
public void Draw(List<IRenderable> list) {
foreach(IRenderable obj in list) {
this.batch.draw(
obj.Image.Texture,
obj.Spatial.Position,
obj.Image.Source,
Color.White);
}
}
}
Olhando para a classe, o sistema de renderização nem sabe o que Entity
é. Tudo o que sabe é IRenderable
e é simplesmente dada uma lista deles para desenhar.
Como Tudo Funciona
Também pode ajudar a entender como eu crio novos objetos de jogo e como os alimento para os sistemas.
Criando entidades
Todos os objetos de jogo herdam da Entidade e quaisquer interfaces aplicáveis que descrevem o que esse objeto de jogo pode fazer. Quase tudo o que é animado na tela fica assim:
public class MyAnimatedWidget : Entity, IRenderable, IAnimatable {}
Alimentando os sistemas
Eu mantenho uma lista de todas as entidades que existem no mundo do jogo em uma única lista chamada List<Entity> gameObjects
. Cada quadro, então, vasculho essa lista e copio as referências de objetos para mais listas com base no tipo de interface, como List<IRenderable> renderableObjects
, e List<IAnimatable> animatableObjects
. Dessa forma, se sistemas diferentes precisarem processar a mesma entidade, eles poderão. Depois, simplesmente entrego essas listas a cada um dos sistemas Update
ou Draw
métodos e deixo que os sistemas façam seu trabalho.
Animação
Você pode estar curioso para saber como o sistema de animação funciona. No meu caso, você pode querer ver a interface IAnimatable:
public interface IAnimatable {
public AnimationComponent Animation { get; }
public ImageComponent Image { get; set; }
}
O principal a notar aqui é o ImageComponent
aspecto da IAnimatable
interface não é somente leitura; tem um levantador .
Como você deve ter adivinhado, o componente de animação contém apenas dados sobre a animação; uma lista de quadros (que são componentes da imagem), o quadro atual, o número de quadros por segundo a ser desenhado, o tempo decorrido desde o último incremento do quadro e outras opções.
O sistema de animação tira proveito do sistema de renderização e da relação do componente de imagem. Simplesmente altera o componente de imagem da entidade à medida que incrementa o quadro da animação. Dessa forma, a animação é renderizada indiretamente pelo sistema de renderização.