Okey, lo que sé hasta ahora; La entidad contiene un componente (almacenamiento de datos) que contiene información como; - Textura / sprite - Shader - etc.
Y luego tengo un sistema de representación que dibuja todo esto. Pero lo que no entiendo es cómo debe diseñarse el renderizador. ¿Debo tener un componente para cada "tipo visual". ¿Un componente sin sombreador, uno con sombreador, etc.?
Solo necesito alguna información sobre cuál es la "forma correcta" de hacer esto. Consejos y trampas a tener en cuenta.
Respuestas:
Esta es una pregunta difícil de responder porque todos tienen su propia idea sobre cómo debe estructurarse un sistema de componentes de la entidad. Lo mejor que puedo hacer es compartir con ustedes algunas de las cosas que me han resultado más útiles.
Entidad
Tomo el enfoque de clase gorda de ECS, probablemente porque encuentro que los métodos extremos de programación son altamente ineficientes (en términos de productividad humana). Para ese fin, una entidad para mí es una clase abstracta que heredarán clases más especializadas. La entidad tiene varias propiedades virtuales y un indicador simple que me dice si esta entidad debería existir o no. Entonces, en relación con su pregunta sobre un sistema de renderizado, esto es lo que
Entity
parece:Componentes
Los componentes son "estúpidos" porque no hacen ni saben nada. No tienen referencias a otros componentes y, por lo general, no tienen funciones (trabajo en C #, por lo que utilizo propiedades para manejar getters / setters; si tienen funciones, se basan en la recuperación de datos que tienen).
Sistemas
Los sistemas son menos "estúpidos", pero siguen siendo autómatas tontos. No tienen contexto del sistema en general, no tienen referencias a otros sistemas y no tienen datos, excepto por algunos buffers que pueden necesitar para su procesamiento individual. Dependiendo del sistema, que puede tener un especializado
Update
, oDraw
método, o en algunos casos, ambas cosas.Interfaces
Las interfaces son una estructura clave en mi sistema. Se utilizan para definir qué
System
puede procesar y de quéEntity
es capaz. Las interfaces que son relevantes para la representación son:IRenderable
yIAnimatable
.Las interfaces simplemente le dicen al sistema qué componentes están disponibles. Por ejemplo, el sistema de representación necesita conocer el cuadro delimitador de la entidad y la imagen a dibujar. En mi caso, ese sería el
SpatialComponent
y elImageComponent
. Entonces se ve así:El sistema de renderizado
Entonces, ¿cómo dibuja el sistema de representación una entidad? En realidad es bastante simple, así que solo te mostraré la clase simplificada para darte una idea:
Mirando la clase, el sistema de renderizado ni siquiera sabe qué
Entity
es un . Todo lo que sabe esIRenderable
y simplemente se le da una lista de ellos para dibujar.Cómo funciona todo
Puede ser útil comprender también cómo creo nuevos objetos de juego y cómo los alimento a los sistemas.
Crear entidades
Todos los objetos del juego heredan de Entity, y cualquier interfaz aplicable que describa lo que ese objeto del juego puede hacer. Casi todo lo que se anima en la pantalla se ve así:
Alimentando los sistemas
Mantengo una lista de todas las entidades que existen en el mundo del juego en una sola lista llamada
List<Entity> gameObjects
. Cada cuadro, luego examino esa lista y copio referencias de objetos a más listas basadas en el tipo de interfaz, comoList<IRenderable> renderableObjects
, yList<IAnimatable> animatableObjects
. De esta manera, si diferentes sistemas necesitan procesar la misma entidad, pueden hacerlo. Luego simplemente entrego esas listas a cada uno de los sistemasUpdate
oDraw
métodos y dejo que los sistemas hagan su trabajo.Animación
Quizás tengas curiosidad por saber cómo funciona el sistema de animación. En mi caso, es posible que desee ver la interfaz IAnimatable:
La clave para notar aquí es que el
ImageComponent
aspecto de laIAnimatable
interfaz no es de solo lectura; Tiene un setter .Como habrás adivinado, el componente de animación solo contiene datos sobre la animación; una lista de fotogramas (que son componentes de imagen), el fotograma actual, el número de fotogramas por segundo que se dibujarán, el tiempo transcurrido desde el último incremento de fotogramas y otras opciones.
El sistema de animación aprovecha el sistema de representación y la relación del componente de imagen. Simplemente cambia el componente de imagen de la entidad a medida que incrementa el marco de la animación. De esa manera, la animación se representa indirectamente por el sistema de representación.
fuente
Vea esta respuesta para ver el tipo de sistema del que estoy hablando.
El componente debe contener los detalles de qué dibujar y cómo dibujarlo. El sistema de representación tomará esos detalles y dibujará la entidad en la forma especificada por el componente. Solo si usara tecnologías de dibujo significativamente diferentes tendría componentes separados para estilos separados.
fuente
La razón clave para separar la lógica en componentes es crear un conjunto de datos que, cuando se combinan en una entidad, producen un comportamiento útil y reutilizable. Por ejemplo, separar una Entidad en un Componente de Física y un Componente de Render tiene sentido, ya que es probable que no todas las entidades tengan Física y algunas entidades no tengan Sprite.
Para responder a su pregunta, debe mirar su arquitectura y hacerse dos preguntas:
Al dividir un componente es importante hacer esta pregunta, si la respuesta a 1. es sí, entonces probablemente tenga un buen candidato para crear dos componentes separados, uno con un Shader y otro con Texture. La respuesta a 2. generalmente es sí para componentes como Position, donde varios componentes pueden usar position.
Por ejemplo, tanto Physics como Audio pueden usar la misma posición, en lugar de que ambos componentes almacenen posiciones duplicadas, las refactorice en un PositionComponent y requiera que las entidades que usan PhysicsComponent / AudioComponent también tengan un PositionComponent.
Según la información que nos ha proporcionado, no parece que su RenderComponent sea un buen candidato para dividirse en un TextureComponent y un ShaderComponent, ya que los sombreadores dependen totalmente de Texture y nada más.
Suponiendo que esté usando algo similar a T-Machine: Entity Systems, una implementación de muestra de RenderComponent & RenderSystem en C ++ se vería así:
fuente
Peligro # 1: código sobrediseñado Piense si realmente necesita todo lo que implementa porque va a tener que vivir con él durante bastante tiempo.
Peligro # 2: demasiados objetos. No usaría un sistema con demasiados objetos (uno para cada tipo, subtipo y lo que sea) porque solo dificulta el procesamiento automatizado. En mi opinión, es mucho mejor que cada objeto controle un determinado conjunto de características (en lugar de una característica). Por ejemplo, hacer componentes para cada bit de datos incluidos en el renderizado (componente de textura, componente de sombreado) está demasiado dividido; de todos modos, normalmente tendría que tener todos esos componentes juntos, ¿no estaría de acuerdo?
Peligro # 3: control externo demasiado estricto. Prefiere cambiar los nombres a objetos de sombreado / textura porque los objetos pueden cambiar con el renderizador / tipo de textura / formato de sombreador / lo que sea. Los nombres son identificadores simples: depende del procesador decidir qué hacer con ellos. Es posible que algún día desee tener materiales en lugar de sombreadores simples (agregue sombreadores, texturas y modos de fusión a partir de datos, por ejemplo). Con una interfaz basada en texto, es mucho más fácil implementar eso.
En cuanto al renderizador, puede ser una interfaz simple que crea / destruye / mantiene / renderiza objetos creados por componentes. La representación más primitiva de la misma podría ser algo como esto:
Esto le permitiría administrar estos objetos desde sus componentes y mantenerlos lo suficientemente lejos como para que pueda renderizarlos de la manera que desee.
fuente