2011-02-09 27 views
16

Estoy trabajando en un pequeño juego escrito en Java (pero la pregunta es independiente del idioma). Como quería explorar varios patrones de diseño, me colgué en el sistema Composite pattern/Entity (que originalmente leí sobre here y here) como una alternativa a la típica herencia jerárquica profunda.El sistema de patrón/entidad compuesto y OOP tradicional

Ahora, después de escribir varios miles de líneas de código, estoy un poco confundido. Creo que entiendo el patrón y me gusta usarlo. Creo que es genial y Starbucks-ish, pero parece que el beneficio que proporciona es de corta duración y (lo que más me molesta) depende en gran medida de su granularidad.

Aquí tenemos una imagen del segundo artículo anterior: enter image description here

Me encanta la forma de objetos (entidades de juego, o como quieran llamarlos) tienen un conjunto mínimo de componentes y la idea inferida es que se puede escribir código que se ve algo como:

BaseEntity Alien = new BaseEntity(); 
BaseEntity Player = new BaseEntity(); 

Alien.addComponent(new Position(), new Movement(), new Render(), new Script(), new Target()); 
Player.addComponent(new Position(), new Movement(), new Render(), new Script(), new Physics()); 

.. que sería muy agradable ... pero en realidad, el código termina buscando algo así como

BaseEntity Alien = new BaseEntity(); 
BaseEntity Player = new BaseEntity(); 

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target()); 
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer()); 

Parece que termino teniendo algunos componentes muy especializados que se componen de componentes menores. Muchas veces, tengo que hacer algunos componentes que tienen dependencias de otros componentes. Después de todo, ¿cómo puedes renderizar si no tienes una posición? No solo eso, sino la forma en que terminas representando un jugador contra un extraterrestre vs. una granada puede ser fundamentalmente diferente. No puede tener UN componente que dicte los tres, a menos que haga un componente muy grande (en cuyo caso ... ¿por qué está usando el patrón compuesto de todos modos?)

Para dar otro ejemplo de la vida real. Tengo personajes en mi juego que pueden equipar varios equipos. Cuando se equipa una pieza de equipo, se cambian algunas estadísticas y se muestran visualmente. Esto es lo que mi código es el momento:

billy.addControllers(new Movement(), new Position(), new CharacterAnimationRender(), new KeyboardCharacterInput()); 

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY); 
billy.get(CharacterAnimationRender.class).setFace(FACE.BLUSH_FACE); 
billy.get(CharacterAnimationRender.class).setHair(HAIR.RED_HAIR); 
billy.get(CharacterAnimationRender.class).setDress(DRESS.DRAGON_PLATE_ARMOR); 

Lo anterior CharacterAnimationRender.class sólo afecta a lo que se muestra visual. Entonces claramente necesito hacer otro Componente que maneje las estadísticas de los cambios. Sin embargo, ¿por qué debería hacer algo como:

billy.addControllers(new CharacterStatistics()); 

billy.get(CharacterAnimationRender.class).setBody(BODY.NORMAL_BODY); 
billy.get(CharacterStatistics.class).setBodyStats(BODY_STATS.NORMAL_BODY); 

Cuando sólo puedo hacer una CharacterGearStuff controlador/componente que maneja tanto la distribución de las estadísticas, así como el cambio visual?

En pocas palabras, no estoy seguro de cómo se supone que esto ayuda a la productividad, ya que a menos que desee manejar todo manualmente, aún debe crear "metacomponentes" que dependan de más de 2 componentes (y modificar/cruzar modificar todos sus subcomponentes, devolviéndonos a OOP). O tal vez estoy pensando en eso completamente mal. ¿Soy yo?

Respuesta

5

Parece que ha entendido mal el patrón de componentes.

Los componentes son datos SOLAMENTE, sin código. Si tiene código en su componente, ya no es un componente, es algo más complejo.

Así, por ejemplo, debe ser capaz de compartir su trivialmente CharacterAnimationRender y CharacterStatistics, por ejemplo:

CharacterStats { int BODY } 
CharacterGameStats { ...not sure what data you have that affects gameplay, but NOT the rendering... } 
CharacterVisualDetails { int FACE, int HAIR } 

... pero no hay ninguna necesidad que éstos sean conscientes de la existencia de cada uno otro. En el momento en que hablas de "dependencias" entre componentes, sospecho que te has perdido.¿Cómo puede una estructura de ints "depender de" otra estructura de ints? Ellos no pueden. Son solo pedazos de datos.

...

Volviendo a sus preocupaciones en la salida, que termina con:

Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target()); 
Player.addComponent(new Position(), new KeyboardInputMovement(), new RenderPlayer(), new ScriptPlayer(), new PhysicsPlayer()); 

... eso es perfecto. ASUMIENDO que ha escrito esos componentes correctamente, ha dividido los datos en pequeños fragmentos que son fáciles de leer/depurar/editar/codificar.

Sin embargo, esto es hacer conjeturas porque no ha especificado qué hay dentro de esos componentes ... p. Ej. AlienAIMovement - ¿Qué hay en eso? Normalmente, espero que tengas un "AIMovement()" y luego lo edites para convertirlo en una versión de Alien, p. cambie algunos indicadores internos en ese componente para indicar que está usando las funciones "Alien" en su sistema AI.

+0

Hola Adam, gracias por responder a esta pregunta: D - He dividido adecuadamente los datos y la lógica de mi código (como mencionas en tu blog, en realidad). De hecho, 'Position()', 'Movement()', etc. son lo que yo llamo controladores que "dependen" de modelos de datos (que podríamos llamar componentes, supongo, que son clases simples que forman parte de SÓLO privadas miembros de datos (en el caso de posición, 'private double x;' y 'private double y;') - hago todo este enlace internamente a los controladores. –

1

Creo que está utilizando un enfoque equivocado aquí. Se supone que los patrones deben ser adoptados para satisfacer sus necesidades, no al revés. Lo simple siempre es mejor que lo complejo, y si sientes que algo no funciona bien, esto significa que debes darte unos pasos atrás y quizás comenzar desde el principio.

Para mí, esto tiene un olor código ya:

BaseEntity Alien = new BaseEntity(); 
Alien.addComponent(new Position(), new AlienAIMovement(), new RenderAlien(), new ScriptAlien(), new Target()); 

Yo esperaría código orientado a objetos para que a ser algo como esto:

Alien alien = new AlienBuilder() 
    .withPosition(10, 248) 
    .withTargetStrategy(TargetStrategy.CLOSEST) 
    .build(); 

//somewhere in the main loop 
Renderer renderer = getRenderer(); 
renderer.render(alien); 

Cuando se utiliza clases genéricas para todos sus entidades, tendrá una API muy genérica y difícil de usar para manejar sus objetos.

Además, se siente mal tener cosas como Posición, Movimiento y Renderizador bajo el mismo paraguas Componente. La posición no es un componente, es un atributo. El movimiento es un comportamiento, y Renderer es algo que no está relacionado con su modelo de dominio en absoluto, es parte del subsistema de gráficos. El componente podría ser una rueda para un automóvil, partes del cuerpo y pistolas para un extraterrestre.

El desarrollo del juego es algo muy sofisticado y es realmente difícil hacerlo bien desde el primer intento. Reescribe tu código desde cero, aprende de tus errores y siente lo que estás haciendo, no solo intenta adaptar un patrón de algún artículo. Y si quieres mejorar en los patrones, debes probar algo más aparte del desarrollo del juego. Escribe un editor de texto simple, por ejemplo.

+5

No creo que las entidades/componentes del juego estén destinados a ser orientados a objetos per se, están destinados a ser manejados por datos. Por lo menos de lo que reuní leyendo el primer artículo anterior. También leí un montón de código de código abierto que implementó el patrón de sistema/componente de la entidad (http://gamadu.com/temp/es.zip, http://code.google.com/p/spartanframework/, y más) y parecía que lo estaba implementando de la misma manera. –

+0

"Reescribe tu código desde cero" es casi siempre un consejo terrible. También está claro que no está familiarizado con el patrón de ECP. – vaughandroid

2

David,

primero gracias por su pregunta perfecto.

Entiendo su problema y creo que no usó el patrón correctamente. Lea este artículo: http://en.wikipedia.org/wiki/Composite_pattern

Si, por ejemplo, no puede implementar el movimiento de clase general y necesita AlienAIMovement y KeyboardMovement, probablemente debería utilizar el patrón Visitor. Pero antes de comenzar la refacturación de miles de líneas de código, compruebe si puede hacer lo siguiente.

¿Es una oportunidad para escribir la clase Movimiento que acepta el parámetro de tipo BaseEntity? ¿Probablemente la diferencia entre todas las implementaciones de Movimiento es solo un parámetro, una bandera o más? En este caso el código se verá así:

Alien.addComponent(new Position(), new Movement(Alien), new Render(Alien), new Script(Alien), new Target());

creo que no es tan malo.

Si no es posible, tratar de crear instancias con la fábrica, por lo

Alien.addComponent(f.createPosition(), f.createMovement(Alien), f.createRender(Alien), f.createRenderScript(Alien), f.createTarget());

Espero que mis sugerencias ayudan.

+0

Me gusta la idea de 'nuevo Movimiento (Alien)' pero creo que al hacerlo así se rompe el patrón del sistema de entidades, al menos al leer el primer artículo y los comentarios anteriores. –

2

Ents parece estar diseñado para hacer exactamente lo que usted desea. Si aún quieres tu propia biblioteca, al menos podrías aprender de su diseño.

Todas las respuestas anteriores parecen torpes y crean toneladas de objetos innecesarios, IMO.