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í:
void Update(UpdateContext updateContext, MultiInputState currentInput)
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:
void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)
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:
virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)
Y luego, una vez que descubre qué dibujar, llama automáticamente:
virtual void Draw(DrawContext drawContext, int tag)
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:
public class DrawableGameComponent
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
public int UpdateOrder { get; set; }
public int DrawOrder { get; set; }
}
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:
public class Actor
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
}
List<Actor> actors = new List<Actor>();
foreach(var actor in actors)
actor.Update(gameTime);
foreach(var actor in actors)
actor.Draw(gameTime);
(Es simple agregar en la clasificación según DrawOrder
y UpdateOrder
. Una forma simple es mantener dos listas y usarlas List<T>.Sort()
. También es trivial manejar Load
/ 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 un UpdateContext
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 elegirDrawableGameComponent
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 usarloServices
al hacer tipos de contenido personalizados para ContentManager
).