¿Cómo implemento características en un sistema de entidad?


31

Después de hacer dos preguntas sobre los sistemas de entidades ( 1 , 2 ) y leer algunos artículos sobre ellos, creo que los entiendo mucho mejor que antes. Todavía tengo algunas incertidumbres, principalmente sobre la construcción de un emisor de partículas, un sistema de entrada y una cámara. Obviamente todavía tengo algunos problemas para comprender los sistemas de entidades, y podrían aplicarse a una gama de objetos completamente diferente, pero elegí estos tres porque son conceptos muy diferentes, deberían cubrir un terreno bastante amplio y ayudarme a comprender los sistemas de entidades y cómo manejar problemas como estos, a medida que surgen.

Estoy construyendo un motor en JavaScript, y he implementado la mayoría de las características principales, que incluyen: manejo de entrada, sistema de animación flexible, emisor de partículas, clases y funciones matemáticas, manejo de escena, una cámara y un render, y un montón de otras cosas que los motores suelen soportar. Leí la respuesta de Byte56, que me interesó en convertir el motor en un sistema de entidad. Seguiría siendo un motor de juego HTML5, con la filosofía básica de la escena, pero debería admitir la creación dinámica de entidades a partir de componentes.


El problema que tengo ahora es adaptar mi viejo concepto de motor a este nuevo paradigma de programación. Estas son algunas de las definiciones de las preguntas anteriores, actualizadas:

  • Una entidad es un identificador. No tiene ningún dato, no es un objeto, es una identificación simple que representa un índice en la lista de escenas de todas las entidades (que realmente planeo implementar como una matriz de componentes).

  • Un Componente es un titular de datos, pero con métodos que pueden operar con esos datos. El mejor ejemplo es un Vector2Dcomponente de "Posición". Tiene datos: xy y, pero también algunos métodos que hacen que operan en los datos un poco más fácil: add(), normalize()y así sucesivamente.

  • Un sistema es algo que puede operar en un conjunto de entidades que cumplen con ciertos requisitos; por lo general, las entidades necesitan tener un conjunto específico de componentes para ser operados. El sistema es la parte "lógica", la parte "algoritmo", toda la funcionalidad suministrada por los componentes es puramente para facilitar la gestión de datos.


Cámara

La cámara tiene una Vector2Dpropiedad de posición, una propiedad de rotación y algunos métodos para centrarla alrededor de un punto. Cada cuadro, se alimenta a un renderizador, junto con una escena, y todos los objetos se traducen según su posición. Luego se representa la escena.

¿Cómo podría representar este tipo de objeto en un sistema de entidad? ¿Sería la cámara una entidad, un componente o una combinación (según mi respuesta )?

Emisor de Partículas

El problema que tengo con mi emisor de partículas es, de nuevo, qué debería ser qué. Estoy bastante seguro de que las partículas en sí mismas no deberían ser entidades, ya que quiero admitir más de 10,000 de ellas, y creo que crear tantas entidades sería un duro golpe para mi rendimiento.

¿Cómo podría representar este tipo de objeto en un sistema de entidad?

Gerente de entrada

El último del que quiero hablar es cómo deben manejarse las entradas. En mi versión actual del motor, hay una clase llamada Input. Es un controlador que se suscribe a los eventos del navegador, como las pulsaciones de teclas y los cambios de posición del mouse, y también mantiene un estado interno. Luego, la clase de jugador tiene un react()método que acepta un objeto de entrada como argumento. La ventaja de esto es que el objeto de entrada podría serializarse en .JSON y luego compartirse a través de la red, lo que permite simulaciones fluidas para varios jugadores.

¿Cómo se traduce esto en un sistema de entidad?

Respuestas:


26
  • Cámara: Hacer de esto un componente sería bastante bueno. Solo tendría unisRenderingbandera y rango de profundidad como dijo Sean. Además del "campo de visión" (supongo que podría llamarse escala en 2D?) Y una zona de salida. La zona de salida podría definir la parte de la ventana del juego en la que se renderiza esta cámara. No tendría una posición / rotación separada como usted menciona. La entidad que cree que tenga un componente de cámara usaría los componentes de posición y rotación de esa entidad. Entonces tendría un sistema de cámara que busca entidades que tengan componentes de cámara, posición y rotación. El sistema toma esa entidad y dibuja todas las entidades que puede "ver" desde su posición, rotación, profundidad de visión y campo de visión, hasta la parte especificada de la pantalla. Eso le ofrece muchas opciones para simular múltiples puertos de vista, ventanas de "vista de personajes", multijugador local,

  • Emisor de partículas: esto también debería ser solo un componente. El sistema de partículas buscaría entidades que tengan una posición, rotación y un emisor de partículas. El emisor tiene todas las propiedades necesarias para reproducir su emisor actual, no estoy seguro de cuáles son, cosas como: velocidad, velocidad inicial, tiempo de caída, etc. No tendría que hacer múltiples pases. El sistema de partículas sabe qué entidades tienen ese componente. Me imagino que podría reutilizar una buena parte de su código existente.

  • Entradas: Debo decir que convertir esto en un componente tiene más sentido dadas las sugerencias que hago anteriormente. Tuinput systemse actualizaría cada cuadro con los eventos de entrada actuales. Luego, cuando pasa por todas sus entidades que tienen el componente de entrada, aplicará esos eventos. El componente de entrada tendría una lista de eventos de teclado y mouse, todas las devoluciones de llamada de método asociadas. No estoy realmente seguro de dónde vivirían las devoluciones de llamada de método. ¿Quizás alguna clase de controlador de entrada? Lo que tenga más sentido para una modificación posterior por parte de los usuarios de su motor. Pero esto le daría el poder de aplicar fácilmente el control de entrada a entidades de cámara, entidades de jugador o lo que sea que necesite. ¿Desea sincronizar el movimiento de un grupo de entidades con el teclado? Simplemente deles todos los componentes de entrada que respondan a las mismas entradas y el sistema de entrada aplica esos eventos de movimiento a todos los componentes que los soliciten.

Entonces, la mayor parte de esto está fuera de mi cabeza, por lo que probablemente no tenga sentido sin más explicaciones. Así que solo hágame saber lo que no tiene claro. Básicamente, te he dado mucho para trabajar :)


Otra gran respuesta! ¡Gracias! Ahora, mi único problema es almacenar y recuperar entidades rápidamente, para que el usuario pueda implementar un bucle / lógica de juego ... Trataré de resolverlo por mí mismo, pero primero debo aprender cómo Javascript trata con matrices, objetos y valores indefinidos en la memoria para hacer una buena suposición ... Eso será un problema porque diferentes navegadores podrían implementarlo de manera diferente.
jcora

Esto se siente arquitectónicamente puro, pero ¿cómo determina el sistema de renderizado que la cámara activa no itera a través de todas las entidades?
Paso

@Pace Dado que me gustaría que la cámara activa se encontrara muy rápidamente, probablemente permitiría que el sistema de cámara mantenga una referencia a las entidades que tienen una cámara activa.
MichaelHouse

¿Dónde pones la lógica para controlar múltiples cámaras (mirar, rotar, mover, etc.)? ¿Cómo controlas las múltiples cámaras?
plasmacel

@plasmacel Si tiene varios objetos que comparten controles, será responsabilidad de su sistema de control determinar qué objeto recibe las entradas.
MichaelHouse

13

Así es como me acerqué a esto:

Cámara

Mi cámara es una entidad como cualquier otra, que tiene componentes adjuntos:

  1. Transformtiene Translation, Rotationy Scalepropiedades, además de otros para velocidad, etc.

  2. Pov(Punto de vista) ha FieldOfView, AspectRatio, Near, Far, y cualquier otra cosa necesaria para producir una matriz de proyección, además de una IsOrthobandera utiliza para cambiar entre la perspectiva y proyecciones ortográficas. Povtambién proporciona una ProjectionMatrixpropiedad de carga diferida utilizada por el sistema de representación que se calcula internamente en lectura y se almacena en caché hasta que se modifique cualquiera de las otras propiedades.

No hay un sistema de cámara dedicado. El sistema de renderizado mantiene una lista de Pov's y contiene lógica para determinar cuál usar al renderizar.

Entrada

Un InputReceivercomponente se puede adjuntar a cualquier entidad. Esto tiene un controlador de eventos adjunto (o lambda si su idioma lo admite) que se utiliza para mantener el procesamiento de entrada específico de la entidad, que toma parámetros para el estado actual y anterior de la tecla, la ubicación actual y anterior del mouse y el estado del botón, etc. (En realidad, hay manejadores separados para mouse y teclado).

Por ejemplo, en un juego de prueba similar a los asteroides que creé cuando me acostumbré a Entity / Component, tengo dos métodos lambda de entrada. Uno maneja la navegación del barco procesando las teclas de flecha y la barra espaciadora (para disparar). El otro maneja la entrada general del teclado: teclas para salir, pausar, etc., reiniciar el nivel, etc. Creo dos componentes, adjunto cada lambda a su propio componente, luego asigno el componente receptor de navegación a la entidad del barco, y el otro a un entidad de procesador de comandos no visible.

Aquí está el controlador de eventos para manejar las teclas que se mantienen entre cuadros que se adjuntan al InputReceivercomponente del barco (C #):

  void ship_input_Hold(object sender, InputEventArgs args)
    {
        var k = args.Keys;
        var e = args.Entity;

        var dt = (float)args.GameTime.ElapsedGameTime.TotalSeconds;

        var verlet = e.As<VerletMotion>();
        var transform = e.As<Transform>();

        if (verlet != null)
        {

        /// calculate applied force 
            var force = Vector3.Zero;
            var forward = transform.RotationMatrix.Up * Settings.ShipSpeedMax;

            if (k.Contains(Keys.W))
                force += forward;

            if (k.Contains(Keys.S))
                force -= forward;

            verlet.Force += force * dt;
        }

        if (transform != null)
        {
            var theta = Vector3.Zero;

            if (k.Contains(Keys.A))
                theta.Z += Settings.TurnRate;

            if (k.Contains(Keys.D))
                theta.Z -= Settings.TurnRate;

            transform.Rotation += theta * dt;
        }

        if (k.Contains(Keys.Space))
        {
            var time = (float)args.GameTime.TotalGameTime.TotalSeconds - _rapidFireLast;

            if (time >= _rapidFireDelay)
            {
                Fire();
                _rapidFireLast = (float)args.GameTime.TotalGameTime.TotalSeconds;
            }
        }
    }

Si la cámara es móvil, darle su propia InputReceivery Transformcomponentes, adjuntar una lambda o controlador que implementa cualquier tipo de control que desea, y ya está.

Esto es bastante bueno, ya que puedes mover el InputReceivercomponente con el controlador de navegación conectado desde la nave a un asteroide, o cualquier otra cosa, y volar en su lugar. O, al asignar un Povcomponente a cualquier otra cosa en su escena, un asteroide, farola, etc., puede ver su escena desde la perspectiva de esa entidad.

Una InputSystemclase que mantiene un estado interno para el teclado, el mouse, etc. InputSystemfiltra su colección de entidades internas a entidades que tienen un InputReceivercomponente. En su Update()método, itera a través de esa colección y llama a los controladores de entrada adjuntos a cada uno de esos componentes de la misma manera que el sistema de representación dibuja a cada entidad con un Renderablecomponente.

Partículas

Esto realmente depende de cómo planeas interactuar con las partículas. Si solo necesita un sistema de partículas que se comporte como un objeto, por ejemplo, un espectáculo de fuegos artificiales que el jugador no puede tocar ni golpear, entonces crearía una sola entidad y un ParticleRenderGroupcomponente que contiene la información que necesita para las partículas. descomposición, etc., eso no está cubierto por su Renderablecomponente. Al renderizar, el sistema de renderizado vería si una entidad tiene el RenderParticleGroupadjunto y lo manejaría en consecuencia.

Si necesita partículas individuales para participar en la detección de colisiones, responder a la entrada, etc., pero solo desea procesarlas como un lote, crearía un Particlecomponente que contiene esa información por partícula, y las crearía como entidades separadas. El sistema de procesamiento aún puede agruparlos, pero los demás sistemas los tratarán como objetos separados. (Esto funciona muy bien con las instancias).

Luego, ya sea en su MotionSystem(o lo que sea que esté usando que maneje la posición de la entidad de actualización, etc.) o en dedicado ParticleSystem, realice el procesamiento requerido para cada partícula por cuadro. El RenderSystemsería responsable de la construcción / de lotes y el almacenamiento en caché colecciones de partículas a medida que se crean y se destruyen, y hacerlos según sea necesario.

Una cosa buena de este enfoque es que no tiene que tener ningún caso especial para colisión, eliminación, etc. de partículas; el código que escribes para cualquier otro tipo de entidad aún se puede usar.

Conclusión

Si está considerando ir multiplataforma, no es súper aplicable a JavaScript, todo el código específico de su plataforma (es decir, representación y entrada) está aislado en dos sistemas. La lógica de su juego permanece en clases agnosíticas de plataforma (movimiento, colisión, etc.), por lo que no debería tener que tocarlas al portarlas.

Entiendo y estoy de acuerdo con la posición de Sean de que las cosas de horquilla de zapatos en un patrón para adherirse estrictamente al patrón, en lugar de ajustar el patrón para satisfacer las necesidades de su aplicación, es malo. Simplemente no veo nada en Entrada, Cámara o Partículas que requiera ese tipo de tratamiento.


¿Dónde pones la lógica para controlar múltiples cámaras (mirar, rotar, mover, etc.)?
plasmacel

7

La lógica de entrada y juego probablemente se manejará en un fragmento de código dedicado fuera del sistema de componentes de la entidad. Es técnicamente posible introducirlo en el diseño, pero hay pocos beneficios: la lógica del juego y la interfaz de usuario son extravagantes y están llenas de abstracciones permeables sin importar lo que hagas, e intentar forzar la clavija cuadrada en un agujero redondo solo por la pureza arquitectónica es un desperdicio de tiempo.

Del mismo modo, los emisores de partículas son bestias especiales, especialmente si te importa el rendimiento. Un componente emisor tiene sentido, pero los gráficos van a hacer algo de magia especial con esos componentes, entremezclados con la magia para el resto del renderizado.

Con respecto a su cámara, simplemente déles a las cámaras una bandera activa y quizás un índice de "profundidad", y deje que el sistema de gráficos muestre todas las que están habilitadas. Esto es realmente útil para muchos trucos, incluidas las GUI (¿quieres que tu GUI se muestre en modo ortográfico en la parte superior del mundo del juego? No hay problema, son solo dos cámaras con máscaras de objetos diferentes y una GUI configurada en una capa superior). También es útil para capas de efectos especiales y demás.


4

¿Sería la cámara una entidad o simplemente un componente?

No estoy seguro de qué es realmente esta pregunta. Dado que las únicas cosas que tienes en el juego son entidades, las cámaras deben ser entidades. La funcionalidad de la cámara se implementa a través de algún tipo de componente de la cámara. No tenga componentes separados de "Posición" y "Rotación", eso es un nivel demasiado bajo. Deben combinarse en algún tipo de componente de WorldPosition que se aplique a cualquier entidad ubicada en el mundo. En cuanto a cuál usar ... tienes que poner la lógica en el sistema de alguna manera. O lo codifica en el sistema de manejo de su cámara, o adjunta scripts, o algo así. Puede tener un indicador habilitado / deshabilitado en un componente de la cámara si es útil.

Estoy bastante seguro de que las partículas en sí mismas no deberían ser entidades

Yo también. Un emisor de partículas sería una entidad, y el sistema de partículas rastrearía las partículas asociadas con una entidad dada. Cosas como esta es donde te das cuenta de que "todo es una entidad" es absurdamente impráctico. En la práctica, las únicas cosas que son entidades son objetos relativamente complejos que se benefician de combinaciones de componentes.

En cuanto a la entrada: la entrada no existe en el mundo del juego como tal, por lo que es manejada por un sistema. No necesariamente es un 'sistema de componentes' porque no todo en tu juego girará en torno a los componentes. Pero habrá un sistema de entrada. Es posible que desee marcar la entidad que responde a la entrada con algún tipo de componente Player, pero la entrada será compleja y completamente específica del juego, por lo que no tiene sentido tratar de crear componentes para esto.


1

Estas son algunas de mis ideas para resolver estos problemas. Probablemente tendrán algo mal con ellos, y probablemente habrá un mejor enfoque, así que, por favor, ¡diríjame a aquellos en su respuesta!

Cámara :

Hay un componente "Cámara", que se puede agregar a cualquier entidad. Sin embargo, no puedo entender qué datos debo poner en este componente: ¡podría tener componentes separados de "Posición" y "Rotación"! ¡ followNo es necesario implementar el método porque ya está siguiendo la entidad a la que está conectado! Y soy libre de moverlo. El problema con este sistema serían muchos objetos de cámara diferentes: ¿cómo puede RendererSystemsaber cuáles usar? Y también, solía pasar el objeto de la cámara, pero ahora parece que RendererSystemtendrá que iterar dos veces sobre todas las entidades: primero para encontrar las que actúan como cámaras y, en segundo lugar, para renderizar todo.

Emisor de partículas :

Habría una ParticleSystemque actualizaría todas las entidades que tenían un componente "Emisor". Las partículas son objetos tontos en un espacio de coordenadas relativo, dentro de ese componente. Hay un problema de renderizado aquí: necesitaría crear un ParticleRenderersistema o ampliar la funcionalidad del existente.

Sistema de entrada :

La principal preocupación para mí aquí era la lógica o el react()método. La única solución que se me ocurrió es un sistema separado para eso, y un componente para cada sistema, que indicaría cuál usar. Esto realmente parece demasiado hacky, y no sé cómo manejarlo bien. Una cosa es que, en lo que a mí respecta, Inputpueden mantenerse implementadas como una clase, pero no veo cómo podría integrarlo al resto del juego.


Realmente no hay una razón para que el RendererSystem itere sobre todas las entidades: ya debe tener una lista de elementos dibujables (y cámaras y luces (a menos que las luces sean dibujables)), o saber dónde están esas listas. Además, es probable que desee realizar el sacrificio de las cámaras que desea renderizar, por lo que tal vez su cámara pueda contener una lista de ID de entidad dibujables que le sean visibles. Podrías tener muchas cámaras y una activa, o una cámara que se conecta a diferentes POV, los cuales podrían ser controlados por cualquier cantidad de cosas, como secuencias de comandos y disparadores y entradas

@ melak47, eso es cierto, yo también lo pensé, pero quería implementarlo de la manera en que Aremis lo hace. Pero esta "sistemas almacenan referencias a las entidades competentes" parece ser cada vez más viciado ...
jcora

¿Artemis no almacena cada tipo de componente en su propia lista? entonces, ¿no tendrías exactamente esas listas de componentes dibujables, componentes de cámara, luces y qué no en alguna parte?
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.