¿Composición OOP pesada frente a sistemas de componentes de entidad pura? [cerrado]

13

Lo admito, he cometido el pecado de abusar e incluso abusar de la herencia. El primer proyecto de juego (de texto) que hice cuando estaba tomando mi curso de OOP fue tan lejos como "Puerta bloqueada" y "puerta desbloqueada" desde "Puerta" y "Habitación con una puerta", "Habitación con dos puertas", y etc. desde "Habitación".

Con el juego (gráfico) en el que trabajé recientemente, pensé que había aprendido mi lección y puse un límite al uso de la herencia. Sin embargo, noté que los problemas pronto comenzaron a aparecer. Mi clase raíz comenzaba a hincharse más y más, y mis clases hoja estaban llenas de códigos duplicados.

Pensé que todavía estaba haciendo las cosas mal, y después de buscarlo en línea descubrí que no era el único con este problema. Así es como terminé descubriendo los sistemas de entidades después de una investigación exhaustiva (léase: googlefu)

Cuando comencé a leer sobre él, pude ver cuán claramente fue capaz de resolver los problemas que tenía con la jerarquía OOP tradicional con componentes. Sin embargo, estos estaban en las primeras lecturas. Cuando me encontré con más ... “radical” ES enfoques, tales como el de T-máquina .

Empecé a estar en desacuerdo con los métodos que estaban usando. Un sistema de componentes puros parecía excesivo o, más bien, poco intuitivo, que probablemente sea la fuerza de la POO. El autor va tan lejos como para decir que el sistema ES es lo opuesto a OOP, y si bien puede usarse junto con OOP, realmente no debería. No digo que esté mal, pero no me pareció una solución que me gustaría implementar.

Entonces, para mí, y resolver los problemas que tenía al principio de la publicación, sin ir en contra de mis intuiciones, es seguir usando una jerarquía, sin embargo, no será una jerarquía monolítica como las que usé antes, sino más bien uno polilítico (no pude encontrar una palabra opuesta a monolítico), que consiste en varios árboles más pequeños.

El siguiente ejemplo muestra lo que quiero decir (esto está inspirado en un ejemplo que encontré en Game Engine Architecture, Capítulo 14).

Tendría un pequeño árbol para vehículos. La clase de vehículo raíz tendría un componente de representación, un componente de colisión, un componente de posición, etc.

Luego, un tanque, una subclase de vehículo heredaría esos componentes de él, y se le daría su propio componente "cañón".

Lo mismo ocurre con los personajes. Un personaje tendría sus propios componentes, luego la clase de jugador lo heredaría y recibiría un controlador de entrada, mientras que otras clases enemigas heredarían de la clase de personaje y recibirían un controlador de IA.

Realmente no veo ningún problema con este diseño. A pesar de no utilizar un sistema de controlador de entidad puro, el problema con el efecto de burbujeo y la gran clase de raíz se resuelve mediante el uso de una jerarquía de múltiples árboles, y el problema de las pesadas hojas de duplicación de código desaparece ya que las hojas no para empezar, tiene cualquier código, solo componentes. Si es necesario realizar un cambio a nivel de hoja, entonces es tan simple como cambiar un solo componente, en lugar de copiar y pegar el código en todas partes.

Por supuesto, siendo tan inexperto como yo, no vi ningún problema cuando comencé a usar el modelo jerárquico pesado de herencia, por lo que si hay problemas con el modelo que estoy pensando implementar, no lo haría. ser capaz de verlo

¿Tus opiniones?

PD: Estoy usando Java, por lo que no es posible usar herencia múltiple para implementar esto en lugar de usar componentes normales.

PPS: las comunicaciones entre componentes se realizarán vinculando los componentes dependientes entre sí. Esto conducirá al acoplamiento, pero creo que es una buena compensación.

Midori Ryuu
fuente
2
Todo esto me parece bien. ¿Hay algún ejemplo específico en su sistema de jerarquía o entidad que le "huela"? Al final, se trata de hacer que el juego se haga más que una sensación de pureza.
drhayes
No, pero como dije, apenas tengo experiencia y estoy lejos de ser el mejor juez para esto.
Midori Ryuu
3
He votado para cerrar esta pregunta porque es subjetiva y está abierta a una amplia discusión. Aprecio que se haya pedido desde una posición sincera, pero elegir cómo proceder aquí es una cuestión de opinión personal. El único inconveniente real de fijar sus componentes en una subclase es que la disposición de los componentes no se puede cambiar en tiempo de ejecución, pero eso seguramente debe ser evidente para usted y es una elección que usted hace.
Kylotan
44
Yo también voté para cerrar. Es una pregunta buena y bien escrita, pero no es apropiada para GDSE. Puede intentar volver a publicar esto en GameDev.net.
Sean Middleditch
Tal como está escrito, con la pregunta '¿Sus opiniones?', Es de hecho abierto e incontestable. Sin embargo, es responsable si la responde como si la pregunta fuera '¿Hay alguna trampa abierta en la que pueda caer sin darme cuenta?' (Al menos, si crees que de hecho hay alguna diferencia empírica entre varias arquitecturas.)
John Calsbeek

Respuestas:

8

Considere este ejemplo:

Estás haciendo un RTS. En un ajuste de lógica completa, usted decide hacer una clase base GameObject, y luego dos subclases, Buildingy Unit. Esto funciona bien, por supuesto, y terminas con algo que se parece a esto:

  • GameObject
    • ModelComponent
    • CollisionComponent
    • ...
  • Building
    • ProductionComponent
    • ...
  • Unit
    • AimingAIComponent
    • WeaponComponent
    • ParticleEmitterComponent
    • AnimationComponent
    • ...

Ahora cada subclase Unitque hagas ya tiene todos los componentes que necesitas. Y como beneficio adicional, tiene un solo lugar para colocar todo ese tedioso código de configuración que newestá arriba WeaponComponent, lo conecta a un AimingAIComponenty ParticleEmitterComponent¡maravilloso!

Y, por supuesto, debido a que todavía tiene en cuenta la lógica en los componentes, cuando finalmente decide que desea agregar una Towerclase que es un edificio pero tiene un arma, puede administrarlo. Puede agregar un AimingAIComponent, a WeaponComponenty a ParticleEmitterComponenta su subclase de Building. Por supuesto, entonces tienes que pasar y desenterrar el código de inicialización de la Unitclase y ponerlo en otro lugar, pero eso no es una gran pérdida.

Y ahora, dependiendo de la cantidad de previsión que haya tenido, puede terminar o no con errores sutiles. Resulta que podría haber algún código en otra parte del juego que se parecía a esto:

if (myGameObject instanceof Unit)
    doSomething(((Unit)myGameObject).getWeaponComponent());

Eso silenciosamente no funciona para su Toweredificio, a pesar de que realmente quería que funcionara para cualquier cosa con un arma. Ahora tiene que verse así:

WeaponComponent weapon = myGameObject.getComponent(WeaponComponent.class);
if (weapon)
    doSomething(weapon);

¡Mucho mejor! Después de cambiar esto, se te ocurre que cualquiera de los métodos de acceso que hayas escrito podría terminar en esta situación. Entonces, lo más seguro es eliminarlos a todos y acceder a cada componente a través de este getComponentmétodo, que puede funcionar con cualquier subclase. (Alternativamente, puede agregar todo tipo de get*Component()a la superclase GameObjecty anularlos en subclases para devolver el componente. Sin embargo, supondré el primero).

Ahora tu Buildingy Unitclases son prácticamente solo bolsas de componentes, sin siquiera accesores en ellos. Y tampoco una inicialización sofisticada, porque la mayor parte de eso necesitaba ser arrastrada para evitar la duplicación de código con su otro objeto de caso especial en algún lugar.

Si sigues esto al extremo, siempre terminas haciendo que tus subclases sean bolsas de componentes completamente inertes, momento en el que ni siquiera tiene sentido tener las subclases alrededor. Puede obtener casi todos los beneficios de tener una jerarquía de clases con una jerarquía de métodos que cree los componentes adecuados. En lugar de tener una Unitclase, tiene un addUnitComponentsmétodo que hace un AnimationComponenty también llama addWeaponComponents, que hace un AimingAIComponent, a WeaponComponenty a ParticleEmitterComponent. El resultado es que tiene todos los beneficios de un sistema de entidad, todo el código de reutilización de una jerarquía y ninguna de la tentación de verificar instanceofo emitir a su tipo de clase.

John Calsbeek
fuente
Bien dicho. Además de esto, creo que la solución ideal es inicializar cada entidad con un script o con archivos descriptores. Yo uso archivos de texto para la inicialización de la entidad. Es rápido y permite a los no programadores probar diferentes parámetros.
Coyote
¡Wow, leer tu publicación me dio una pesadilla! Por supuesto, lo digo en el buen sentido, ¡ya que me abriste los ojos ante el tipo de pesadilla con la que podría terminar! Desearía poder responder más a una respuesta tan detallada, ¡pero no hay mucho que agregar realmente!
Midori Ryuu el
De una manera más simple. Terminas usando Herencia como patrón de fábrica de hombre pobre.
Zardoz89
2

Composición sobre herencia es la terminología de OOP (al menos la forma en que lo aprendí / me lo enseñaron). Para elegir entre composición y herencia, usted dice en voz alta "El automóvil es un tipo de rueda" (herencia) y "El automóvil tiene ruedas" (composición). Uno debería sonar más correcto que el otro (composición en este caso), y si no puede decidir, seleccione la composición por defecto hasta que decida lo contrario.

Daniel Carlsson
fuente
1

Cuando se trata de integrar sistemas basados ​​en componentes a juegos existentes basados ​​en objetos de juego, hay un artículo en la programación de vaqueros http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/ que podría darle algunos consejos sobre forma en que se puede hacer la transición.

En cuanto a los problemas, tu modelo aún necesitará manejar al jugador de manera diferente a otro enemigo. No podrás cambiar el estado (es decir, que el jugador se vuelva controlado por la IA) cuando un jugador se desconecta o en casos especiales sin manejarlo de manera diferente a los otros personajes.

Además de la gran reutilización de componentes en diferentes entidades, la idea de los juegos basados ​​en componentes es la uniformidad de todo el sistema. Cada entidad tiene componentes que se pueden conectar y desconectar a voluntad. Todos los componentes del mismo tipo se manejan exactamente de la misma manera con un conjunto de llamadas establecidas por la interfaz (componente base) de cada tipo (posición, renderizado, script ...)

La combinación de la herencia de objetos del juego con un enfoque basado en componentes le ofrece algunas ventajas del enfoque basado en componentes con inconvenientes de ambos enfoques.

Puede funcionar para ti. Pero no mezclaría ambos fuera de un período de transición de una arquitectura a otra.

PD escrito en un teléfono, por lo que me resulta difícil vincularme a recursos externos.

Coyote
fuente
¡Gracias por su respuesta a pesar de que estaba hablando por teléfono! Entiendo lo que quieres decir. Las cosas basadas en componentes de movimiento son, más flexibilidad tengo para alterar las cosas. Es la razón por la que voy a seguir adelante y probar un enfoque de composición con una herencia mínima. (Incluso más que la que propuse.)
Midori Ryuu
@MidoriRyuu evita toda herencia en los objetos de entidad. Tienes la suerte de estar usando un lenguaje con reflexión (e introspección) incorporada. Puede usar archivos (txt o XML) para inicializar sus objetos con los parámetros correctos. No es demasiado trabajo y permitirá un mejor ajuste del juego sin volver a compilar. Pero mantenga la clase Entidad, al menos para la comunicación entre componentes y otras tareas de utilidad. PD todavía en el teléfono :(
Coyote