He leído en muchos lugares que DrawableGameComponents debe guardarse para cosas como "niveles" o algún tipo de gerentes en lugar de usarlos, por ejemplo, para personajes o mosaicos (como dice este tipo aquí ). Pero no entiendo por qué esto es así. Leí esta publicación y tenía mucho sentido para mí, pero son minoría.
Por lo general, no prestaría demasiada atención a cosas como estas, pero en este caso me gustaría saber por qué la mayoría aparente cree que este no es el camino a seguir. Tal vez me estoy perdiendo algo.
xna
game-component
Kenji Kina
fuente
fuente
DrawableGameComponent
bloqueos en un solo conjunto de firmas de métodos y un modelo específico de datos retenidos. Estas son típicamente la elección incorrecta inicialmente, y el bloqueo dificulta significativamente la evolución del código en el futuro". "Pero déjame decirte lo que está pasando aquí ...DrawableGameComponent
es parte de la API central de XNA (nuevamente: principalmente por razones históricas). Los programadores novatos lo usan porque está "allí" y "funciona" y no saben nada mejor. Ven mi respuesta diciéndoles que están " equivocados " y, debido a la naturaleza de la psicología humana, lo rechazan y eligen el otro "lado". Que resulta ser la no respuesta argumentativa de VeraShackle; que cobró impulso porque no lo llamé de inmediato (ver esos comentarios). Siento que mi eventual refutación sigue siendo suficiente.Respuestas:
Los "Componentes del juego" fueron una parte muy temprana del diseño de XNA 1.0. Se suponía que debían funcionar como controles para WinForms. La idea era que podría arrastrarlos y soltarlos en su juego usando una GUI (algo así como el bit para agregar controles no visuales, como temporizadores, etc.) y establecer propiedades en ellos.
Entonces, ¿podría tener componentes como una cámara o un ... contador FPS? ... o ...?
¿Lo ves? Desafortunadamente, esta idea realmente no funciona para los juegos del mundo real. El tipo de componentes que deseas para un juego no son realmente reutilizables. Es posible que tenga un componente de "jugador", pero será muy diferente al componente de "jugador" del juego de los demás.
Me imagino que fue por esta razón, y por una simple falta de tiempo, que la GUI se retiró antes de XNA 1.0.
Pero la API todavía es utilizable ... He aquí por qué no deberías usarla:
Básicamente se trata de lo que es más fácil de escribir, leer, depurar y mantener como desarrollador de juegos. Haciendo algo como esto en su código de carga:
Es mucho menos claro que simplemente hacer esto:
Especialmente cuando, en un juego del mundo real, podrías terminar con algo como esto:
(Es cierto que podría compartir la cámara y el spriteBatch usando una
Services
arquitectura similar . Pero esa también es una mala idea por la misma razón).Esta arquitectura, o falta de ella , es mucho más ligera y flexible!
Puedo cambiar fácilmente el orden de dibujo o agregar múltiples ventanas gráficas o agregar un efecto de objetivo de renderizado, simplemente cambiando algunas líneas. Para hacerlo en el otro sistema me requiere piense qué están haciendo todos esos componentes, distribuidos en varios archivos, y en qué orden.
Es como la diferencia entre la programación declarativa y la imperativa. Resulta que, para el desarrollo de juegos, la programación imperativa es mucho más preferible.
fuente
DrawableGameComponent
.player.DrawOrder = 100;
método sobre el imperativo para el orden de dibujo. Estoy terminando un juego de Flixel en este momento, que dibuja objetos en el orden en que los agregas a la escena. El sistema FlxGroup alivia algo de esto, pero tener algún tipo de clasificación de índice Z integrado hubiera sido bueno.Hmmm Tengo miedo de desafiar a este por un poco de equilibrio.
Parece que hay 5 cargos en la respuesta de Andrew, así que trataré de tratar cada uno por turno:
1) Los componentes son un diseño obsoleto y abandonado.
La idea del patrón de diseño de componentes existía mucho antes de XNA; en esencia, es simplemente una decisión de hacer que los objetos, en lugar del objeto general del juego, sean el hogar de su propio comportamiento de Actualización y Dibujo. Para los objetos en su colección de componentes, el juego llamará a estos métodos (y a algunos otros) automáticamente en el momento apropiado; crucialmente, el objeto del juego no tiene que 'conocer' nada de este código. Si desea reutilizar sus clases de juego en diferentes juegos (y, por lo tanto, diferentes objetos del juego), este desacoplamiento de objetos sigue siendo una buena idea desde el punto de vista financiero. Un vistazo rápido a las últimas demostraciones (producidas profesionalmente) de XNA4.0 de AppHub confirma mi impresión de que Microsoft todavía está (con razón, en mi opinión) firmemente detrás de esta.
2) Andrew no puede pensar en más de 2 clases de juegos reutilizables.
Yo puedo. De hecho, puedo pensar en cargas! Acabo de comprobar mi lib compartida actual, y comprende más de 80 componentes y servicios reutilizables . Para darle un sabor, podría tener:
Cualquier idea de juego en particular necesitará al menos 5-10 cosas de este conjunto, garantizado, y pueden ser completamente funcionales en un juego completamente nuevo con una línea de código.
3) Controlar el orden de dibujo por el orden de las llamadas de dibujo explícitas es preferible a usar la propiedad DrawOrder del componente.
Bueno, básicamente estoy de acuerdo con Andrew en este caso. PERO, si deja todas las propiedades DrawOrder de sus componentes como valores predeterminados, aún puede controlar explícitamente su orden de dibujo por el orden en que los agrega a la colección. Esto es realmente idéntico en términos de 'visibilidad' al pedido explícito de sus llamadas de extracción manual.
4) Llamar una carga de métodos de extracción de objetos usted mismo es más "ligero" que hacer que los llame un método de marco existente.
¿Por qué? Además, en todos los proyectos, excepto en los más triviales, su lógica de actualización y el código de Draw eclipsarán por completo su método de llamadas en términos de éxito en cualquier caso.
5) Colocar su código en un archivo es preferible, al depurar, que tenerlo en el archivo del objeto individual.
Bueno, en primer lugar, cuando estoy depurando en Visual Studio, la ubicación de un punto de interrupción en términos de archivos en el disco es casi invisible en estos días: el IDE trata la ubicación sin ningún drama. Pero también considere por un momento que tiene un problema con su dibujo de Skybox. Ir al método de dibujo de Skybox es realmente más oneroso que localizar el código de dibujo de skybox en el maestro (presumiblemente muy largo) método de dibujo en el objeto Juego? No, en realidad no, de hecho diría que es todo lo contrario.
En resumen, por favor, eche un vistazo a los argumentos de Andrew aquí. No creo que realmente den bases sustanciales para escribir código de juego enredado. Las ventajas del patrón de componente desacoplado (usado juiciosamente) son enormes.
fuente
Drawable
)GameComponent
para proporcionar su arquitectura de juego, debido a la pérdida del control explícito sobre el orden de sorteo / actualización (que está de acuerdo en el n. ° 3) de sus interfaces (que no se abordan).No estoy de acuerdo un poco con Andrew, pero también creo que hay un momento y un lugar para todo. No usaría un
Drawable(GameComponent)
para algo tan simple como un Sprite o un Enemigo. Sin embargo, lo usaría para unSpriteManager
oEnemyManager
.Volviendo a lo que Andrew dijo sobre el orden de dibujo y actualización, creo
Sprite.DrawOrder
que sería muy confuso para muchos sprites. Además, es relativamente fácil configurar unDrawOrder
yUpdateOrder
para una pequeña (o razonable) cantidad de Gerentes que lo son(Drawable)GameComponents
.Creo que Microsoft los habría eliminado si realmente fueran tan rígidos y nadie los usara. Pero no lo han hecho porque funcionan perfectamente bien, si el papel es el correcto. Yo mismo los uso para desarrollar
Game.Services
que se actualizan en segundo plano.fuente
Estaba pensando en esto también, y se me ocurrió: por ejemplo, para robar el ejemplo en su enlace, en un juego RTS podría hacer de cada unidad una instancia de componente del juego. Bueno.
Pero también puede crear un componente UnitManager, que mantiene una lista de unidades, las dibuja y las actualiza según sea necesario. Supongo que la razón por la que quieres convertir tus unidades en componentes del juego es para compartimentar el código, entonces, ¿esta solución lograría adecuadamente ese objetivo?
fuente
En los comentarios, publiqué una versión resumida de mi respuesta anterior:
El uso lo
DrawableGameComponent
bloquea en un solo conjunto de firmas de métodos y un modelo específico de datos retenidos. Estas son típicamente la elección incorrecta inicialmente, y el bloqueo dificulta significativamente la evolución del código en el futuro.Aquí intentaré ilustrar por qué este es el caso publicando un código de mi proyecto actual.
El objeto raíz que mantiene el estado del mundo del juego tiene un
Update
método que se ve así:Hay varios puntos de entrada para
Update
, dependiendo de si el juego se ejecuta en la red o no. Es posible, por ejemplo, que el estado del juego se revierta a un punto anterior en el tiempo y luego se vuelva a simular.También hay algunos métodos adicionales de actualización, como:
Dentro del estado del juego hay una lista de actores para actualizar. Pero esto es más que una simple
Update
llamada. Hay pases adicionales antes y después para manejar la física. También hay algunas cosas elegantes para el desove / destrucción diferida de actores, así como transiciones de nivel.Fuera del estado del juego, hay un sistema de interfaz de usuario que debe administrarse de forma independiente. Pero algunas pantallas en la interfaz de usuario también deben poder ejecutarse en un contexto de red.
Para dibujar, debido a la naturaleza del juego, existe un sistema de clasificación y selección personalizado que toma entradas de estos métodos:
Y luego, una vez que descubre qué dibujar, llama automáticamente:
los
tag
parámetro es obligatorio porque algunos actores pueden aferrarse a otros actores y, por lo tanto, deben poder dibujar tanto por encima como por debajo del actor retenido. El sistema de clasificación asegura que esto suceda en el orden correcto.También existe el sistema de menús para dibujar. Y un sistema jerárquico para dibujar la interfaz de usuario, alrededor del HUD, alrededor del juego.
Ahora compare esos requisitos con lo que
DrawableGameComponent
proporciona:No hay una forma razonable de pasar de un sistema
DrawableGameComponent
al sistema que describí anteriormente. (Y esos no son requisitos inusuales para un juego de complejidad incluso modesta).Además, es muy importante tener en cuenta que esos requisitos no surgieron simplemente al comienzo del proyecto. Evolucionaron durante muchos meses de trabajo de desarrollo.
Lo que la gente suele hacer, si comienzan por la
DrawableGameComponent
ruta, es comenzar a construir sobre hacks:En lugar de agregar métodos, hacen algunos cambios feos a su propio tipo base (¿por qué no usarlo directamente?).
En lugar de reemplazar el código para ordenar e invocar
Draw
/Update
, hacen algunas cosas muy lentas para cambiar los números de pedido sobre la marcha.Y lo peor de todo: en lugar de agregar parámetros a los métodos, comienzan a "pasar" "parámetros" en ubicaciones de memoria que no son la pila (el uso de
Services
esto es popular). Esto es complicado, propenso a errores, lento, frágil, raramente seguro para subprocesos y es probable que se convierta en un problema si agrega redes, crea herramientas externas, etc.También hace que la reutilización del código sea más difícil , porque ahora sus dependencias están ocultas dentro del "componente", en lugar de publicarse en el límite de la API.
O, casi ven lo loco que es todo esto, y comienzan a hacer "gerente"
DrawableGameComponent
para solucionar estos problemas. Excepto que todavía tienen estos problemas en los límites del "gerente".Invariablemente, estos "gerentes" podrían reemplazarse fácilmente con una serie de llamadas a métodos en el orden deseado. Cualquier otra cosa es demasiado complicada.
Por supuesto, una vez que comience a emplear esos hacks, se necesita un trabajo real para deshacer esos hacks una vez que se da cuenta de que necesita más de lo que
DrawableGameComponent
proporciona. Te vuelves " encerrado ". Y a menudo la tentación es seguir acumulando hacks, lo que en la práctica lo aturdirá y limitará los tipos de juegos que puede hacer a los relativamente simples.Muchas personas parecen llegar a este punto y tienen una reacción visceral, posiblemente porque usan
DrawableGameComponent
y no quieren aceptar estar "equivocados". Entonces se aferran al hecho de que al menos es posible hacer que "funcione".¡Por supuesto que se puede hacer que funcione! Somos programadores, hacer que las cosas funcionen bajo restricciones inusuales es prácticamente nuestro trabajo. Pero esta restricción no es necesaria, útil o racional ...
Lo que es particularmente flabbergasting sobre es que es sorprendentemente trivial lado a paso todo este asunto. Aquí, incluso te daré el código:
(Es simple agregar en la clasificación según
DrawOrder
yUpdateOrder
. Una forma simple es mantener dos listas y usarlasList<T>.Sort()
. También es trivial manejarLoad
/UnloadContent
.)No es importante lo que el código realmente es . Lo importante es que puedo modificarlo trivialmente .
Cuando me doy cuenta de que necesito un sistema de sincronización diferente para
gameTime
, o necesito más parámetros (o unUpdateContext
objeto completo con alrededor de 15 cosas), o necesito un orden de operaciones completamente diferente para dibujar, o necesito algunos métodos adicionales para diferentes pases de actualización, puedo seguir adelante y hacer eso .Y puedo hacerlo de inmediato. No necesito preocuparme por elegir
DrawableGameComponent
mi código. O peor: piratearlo de alguna manera que lo hará aún más difícil la próxima vez que surja esa necesidad.En realidad, solo hay un escenario en el que es una buena opción usar
DrawableGameComponent
: y eso es si literalmente está haciendo y publicando el estilo de componente de arrastrar y soltar middleware de para XNA. Cuál fue su propósito original. Lo cual, agregaré, ¡ nadie lo está haciendo!(Del mismo modo, solo tiene sentido usarlo
Services
al hacer tipos de contenido personalizados paraContentManager
).fuente
DrawableGameComponent
ciertos componentes, especialmente para elementos como pantallas de menú (menús, muchos botones, opciones y demás), o las superposiciones de FPS / depuración Mencionaste. En estos casos, generalmente no necesita un control detallado sobre el orden de dibujo y termina con menos código que necesita escribir manualmente (lo cual es bueno, a menos que necesite escribir ese código). Pero supongo que es más o menos el escenario de arrastrar y soltar que estabas mencionando.