¿Cómo actualizar estados de entidad y animaciones en un juego basado en componentes?

10

Estoy tratando de diseñar un sistema de entidad basado en componentes para fines de aprendizaje (y luego usarlo en algunos juegos) y tengo algunos problemas a la hora de actualizar los estados de la entidad.

No quiero tener un método update () dentro del Componente para evitar dependencias entre Componentes.

Lo que actualmente tengo en mente es que los componentes contienen datos y los componentes de actualización de sistemas.

Entonces, si tengo un juego 2D simple con algunas entidades (por ejemplo, jugador, enemigo1, enemigo2) que tienen componentes de Transformación, Movimiento, Estado, Animación y Representación, creo que debería tener:

  • Un sistema de movimiento que mueve todos los componentes de movimiento y actualiza los componentes de estado
  • Y un RenderSystem que actualiza los componentes de Animación (el componente de animación debe tener una animación (es decir, un conjunto de cuadros / texturas) para cada estado y actualizarlo significa seleccionar la animación correspondiente al estado actual (por ejemplo, saltar, mover_izquierda, etc.), y actualizar el índice del marco). Luego, RenderSystem actualiza los componentes de Render con la textura correspondiente al cuadro actual de la Animación de cada entidad y renderiza todo en la pantalla.

He visto algunas implementaciones como el marco de Artemis, pero no sé cómo resolver esta situación:

Digamos que mi juego tiene las siguientes entidades. Cada entidad tiene un conjunto de estados y una animación para cada estado:

  • jugador: "inactivo", "moving_right", "saltando"
  • enemigo1: "movimiento_abajo", "movimiento_abajo"
  • enemy2: "moving_left", "moving_right"

¿Cuáles son los enfoques más aceptados para actualizar el estado actual de cada entidad? Lo único que se me ocurre es tener sistemas separados para cada grupo de entidades y componentes separados de Estado y Animación, por lo que tendría PlayerState, PlayerAnimation, Enemy1State, Enemy1Animation ... PlayerMovementSystem, PlayerRenderingSystem ... pero creo que esto es malo solución y rompe el propósito de tener un sistema basado en componentes.

Como puede ver, estoy bastante perdido aquí, así que agradecería cualquier ayuda.

EDITAR: Creo que la solución para hacer que esto funcione como tengo la intención es esta:

Hace que el componente de estado y el componente de animación sean lo suficientemente genéricos como para ser utilizados en todas las entidades. Los datos que contienen serán el modificador para cambiar cosas como qué animaciones se reproducen o qué estados están disponibles. - Byte56

Ahora, estoy tratando de descubrir cómo diseñar estos 2 componentes lo suficientemente genéricos como para poder reutilizarlos. ¿Podría ser una buena solución tener un UID para cada estado (por ejemplo, caminar, correr ...) y almacenar animaciones en un mapa en el AnimationComponent con este identificador?

miviclin
fuente
Supongo que has visto esto: ¿ Cambios de estado en entidades o componentes ? ¿Es su pregunta fundamentalmente diferente de esa?
MichaelHouse
@ Byte56 Sí, lo leí hace unas horas. La solución que sugirió allí es similar a la idea que he expuesto aquí. Pero mi problema surge cuando StateComponent y AnimationComponent no son iguales para todas las entidades dentro del sistema. ¿Debería dividir ese sistema en sistemas más pequeños que procesen grupos de entidades que tengan los mismos estados y animaciones posibles? (vea la última parte de mi publicación original para una mejor aclaración)
miviclin
1
Haces statecomponenty animationcomponentlo suficientemente genérico que debe utilizarse para todas las entidades. Los datos que contienen serán el modificador para cambiar cosas como qué animaciones se reproducen o qué estados están disponibles.
MichaelHouse
Cuando habla de dependencia, ¿quiere decir dependencia de datos o dependencia de orden de ejecución? Además, en su solución propuesta, ¿el MovementSystem ahora tiene que implementar todas las diferentes formas en que algo puede moverse? Parece que está rompiendo la idea del sistema basado en componentes ...
ADB
@ADB Estoy hablando de la dependencia de datos. Para actualizar la animación (por ejemplo, cambiar de animación move_right a animación move_left) necesito saber el estado actual de la entidad y no veo cómo hacer que estos 2 componentes sean más genéricos.
miviclin

Respuestas:

5

En mi humilde opinión, el Movementcomponente debe mantener el estado actual ( Movement.state) y el Animationcomponente debe observar los cambios Movement.statey actualizar su animación actual ( Animation.animation) en consecuencia, utilizando una simple búsqueda de id de estado a la animación (como se sugiere al final del OP). Obviamente, esto significa Animationdependerá de Movement.

Una estructura alternativa sería tener un Statecomponente genérico , que Animationobserva y Movementmodifica, que es básicamente el modelo-vista-controlador (estado-animación-movimiento en este caso).

Otra alternativa sería hacer que la entidad envíe un evento a sus componentes cuando cambie su estado. Animationescucharía este evento y actualizaría su animación en consecuencia. Esto elimina la dependencia, aunque podría argumentar que la versión dependiente es un diseño más transparente.

Buena suerte.

Tormentoso
fuente
Entonces, la animación observa al Estado y el Estado observa al Movimiento ... Las dependencias todavía están ahí, pero podría intentarlo. ¿Sería la última alternativa algo así: el movimiento notifica los cambios a la entidad y la entidad envía un evento al Estado, y luego se repetiría el mismo proceso para Estado y Animación? ¿Cómo podría este enfoque afectar el rendimiento?
miviclin
Primer caso: Movementque el control State (no observar). Último caso: Sí, Movementmás entity.dispatchEvent(...);o menos, y todos los demás componentes que escuchen ese tipo de evento lo recibirán. Por supuesto, el rendimiento es peor que las llamadas a métodos puros, pero no mucho. Puede agrupar objetos de eventos, por ejemplo. Por cierto, no tiene que usar la entidad como el "nodo de eventos", también podría usar un "bus de eventos" dedicado, dejando su clase de entidad completamente fuera de él.
Torious
2

Sobre su problema, si el ESTADO solo se usa en animaciones, entonces ni siquiera necesita exponerlo a otros componentes. Si tiene más de un uso, debe exponerlo.

El sistema de Componentes / Subsistema que describe se siente más basado en la jerarquía que en los componentes. Después de todo, lo que usted describe como componentes son, de hecho, estructuras de datos. No significa que sea un mal sistema, solo que no creo que se ajuste demasiado bien al enfoque basado en componentes.

Como notó, las dependencias son un gran problema en los sistemas basados ​​en componentes. Hay diferentes formas de lidiar con eso. Algunos requieren que cada componente declare sus dependencias y realice una verificación estricta. Otros consultan por componentes que implementan una interfaz específica. Aún otros pasan referencia a los componentes dependientes cuando instancian cada uno de ellos.

Independientemente del método que utilice, necesitará un GameObject de algún tipo para actuar como una colección de Componentes. Lo que proporciona GameObject puede variar mucho y puede simplificar sus dependencias entre componentes al impulsar algunos datos de uso frecuente al nivel de GameObject. Unity hace eso con la transformación, por ejemplo, obliga a todos los objetos del juego a tener uno.

En cuanto al problema que pides de diferentes estados / animación para diferentes objetos del juego, esto es lo que haría. Primero, no me gustaría demasiado en esta etapa de la implementación: solo implemente lo que necesita ahora para que funcione, luego agregue campanas y silbatos cuando los necesite.

Entonces, comenzaría con un componente 'State': PlayerStateComponent, Enemy1State, Enemy2State. El componente estatal se encargaría de cambiar el estado en el momento apropiado. El estado es algo que prácticamente todos sus objetos tendrán, por lo que puede residir en el GameObject.

Entonces, habría un AnimationCompoment. Esto tendría un diccionario de animaciones relacionadas con el estado. En update (), cambie la animación si el estado cambia.

Hay un gran artículo sobre el marco de construcción que no puedo encontrar. Decía que cuando no tienes experiencia en el dominio, debes elegir un problema y hacer la implementación más simple que resuelva el problema actual . Luego agrega otro problema / caso de uso y expande el marco a medida que avanza, para que crezca orgánicamente. Realmente me gusta ese enfoque, particularmente cuando trabajas con un nuevo concepto como lo estás haciendo.

La implementación que propuse es bastante ingenua, pero aquí hay algunas posibles mejoras a medida que agrega más casos de uso:

  • reemplace la variable GameObject con un diccionario. Cada componente usa el diccionario para almacenar valores. (asegúrese de manejar la colisión correctamente ...)
  • reemplace el diccionario de valores simples con referencias en su lugar: clase FloatVariable () {public value [...]}
  • En lugar de múltiples componentes de estado, cree un StateComponent genérico en el que pueda construir máquinas de estado variable. Debe tener un conjunto genérico de condiciones sobre las cuales un estado puede cambiar: pulsaciones de teclas, entrada del mouse, cambios de variables (puede vincular eso con FloatVariable arriba).
ADB
fuente
Este enfoque funciona, implementé algo similar hace un año, pero el problema es que casi todos los componentes dependen de otros componentes, por lo que me parece menos flexible. También pensé en insertar los componentes más comunes (por ejemplo, transformar, renderizar, estado ...) en la entidad, pero creo que esto rompe el propósito de los componentes porque algunos de ellos están vinculados a la entidad y algunas entidades pueden no necesitarlos. Es por eso que estoy tratando de rediseñarlo con sistemas responsables de actualizar la lógica para que los componentes no se sepan entre sí, ya que no se actualizan.
miviclin
0

Además de la respuesta de ADB, puede usar http://en.wikipedia.org/wiki/Dependency_injection , que ayuda cuando necesita construir muchos componentes pasándolos como referencias a sus constructores. Obviamente, aún dependerán entre sí (si eso es necesario en su base de código), pero puede poner toda esa dependencia en un lugar donde las dependencias están configuradas y el resto de su código no necesita saber sobre la dependencia.

Este enfoque también funciona bien si usa interfaces porque cada clase de componente solo solicita lo que necesita o dónde debe registrarse y solo el marco de inyección de dependencia (o el lugar donde configura todo, generalmente la aplicación) sabe quién necesita qué .

Para sistemas simples que podría escapar sin usar DI o código limpio, sus clases de RenderingSystem suenan como si necesita llamarlas estáticamente o al menos tenerlas disponibles en cada componente, lo que las hace dependientes entre sí y difíciles de cambiar. Si está interesado en un enfoque más limpio, consulte los enlaces del enlace DI wiki arriba y lea sobre Clean Code: http://clean-code-developer.com/

exDreamDuck
fuente
Ya tengo un sistema donde los componentes son bastante dependientes entre sí. Hice un uso intensivo de la inyección de dependencia allí, y aunque lo prefiero a las jerarquías profundas, estoy tratando de crear una nueva para evitar el acoplamiento de componentes si fuera posible. No llamaría a nada estáticamente. Tendría un ComponentManager al que todos los sistemas tienen acceso (todos los sistemas deberían tener una referencia), y RendererSystem obtendría todos los componentes de animación del administrador de componentes y representaría el estado actual de cada animación.
miviclin