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.
fuente
Respuestas:
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,Building
yUnit
. 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
Unit
que 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 quenew
está arribaWeaponComponent
, lo conecta a unAimingAIComponent
yParticleEmitterComponent
¡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
Tower
clase que es un edificio pero tiene un arma, puede administrarlo. Puede agregar unAimingAIComponent
, aWeaponComponent
y aParticleEmitterComponent
a su subclase deBuilding
. Por supuesto, entonces tienes que pasar y desenterrar el código de inicialización de laUnit
clase 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:
Eso silenciosamente no funciona para su
Tower
edificio, a pesar de que realmente quería que funcionara para cualquier cosa con un arma. Ahora tiene que verse así:¡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
getComponent
método, que puede funcionar con cualquier subclase. (Alternativamente, puede agregar todo tipo deget*Component()
a la superclaseGameObject
y anularlos en subclases para devolver el componente. Sin embargo, supondré el primero).Ahora tu
Building
yUnit
clases 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
Unit
clase, tiene unaddUnitComponents
método que hace unAnimationComponent
y también llamaaddWeaponComponents
, que hace unAimingAIComponent
, aWeaponComponent
y aParticleEmitterComponent
. 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 verificarinstanceof
o emitir a su tipo de clase.fuente
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.
fuente
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.
fuente