La animación aún se puede dividir perfectamente entre lógica y renderizado. El estado de los datos abstractos de la animación sería la información necesaria para que su API de gráficos represente la animación.
En los juegos 2D, por ejemplo, podría ser un área rectangular que marca el área que muestra la parte actual de la hoja de sprites que debe dibujarse (cuando tiene una hoja que consta de 30 dibujos de 80x80 que contienen los diversos pasos de su personaje saltar, sentarse, moverse, etc.). También puede ser cualquier tipo de datos que no necesite para renderizar, pero tal vez para administrar los estados de animación, como el tiempo restante hasta que caduque el paso actual de la animación o el nombre de la animación ("caminar", "estar de pie" etc.) Todo eso se puede representar de la forma que desee. Esa es la parte de la lógica.
En la parte de renderizado, simplemente hazlo como de costumbre, obtén ese rectángulo de tu modelo y usa tu renderizador para hacer las llamadas a la API de gráficos.
En código (usando la sintaxis de C ++ aquí):
class Sprite //Model
{
private:
Rectangle subrect;
Vector2f position;
//etc.
public:
Rectangle GetSubrect()
{
return subrect;
}
//etc.
};
class AnimatedSprite : public Sprite, public Updatable //arbitrary interface for classes that need to change their state on a regular basis
{
AnimationController animation_controller;
//etc.
public:
void Update()
{
animation_controller.Update(); //Good OOP design ;) It will take control of changing animations in time etc. for you
this.SetSubrect(animation_controller.GetCurrentAnimation().GetRect());
}
//etc.
};
Esa es la información. Su procesador tomará esos datos y los dibujará. Dado que tanto los Sprites normales como los animados se dibujan de la misma manera, ¡puedes usar polimorfía aquí!
class Renderer
{
//etc.
public:
void Draw(const Sprite &spr)
{
graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
}
};
TMV:
Se me ocurrió otro ejemplo. Digamos que tienes un juego de rol. Su modelo que representa el mapa mundial, por ejemplo, probablemente necesite almacenar la posición del personaje en el mundo como coordenadas de mosaico en el mapa. Sin embargo, cuando mueves el personaje, caminan unos pocos píxeles a la vez hasta el siguiente cuadrado. ¿Almacena esta posición "entre mosaicos" en un objeto de animación? ¿Cómo se actualiza el modelo cuando el personaje finalmente "llegó" a la siguiente coordenada del mapa?
El mapa mundial no conoce la posición de los jugadores directamente (no tiene un Vector2f o algo así que almacena directamente la posición de los jugadores =, en cambio, tiene una referencia directa al objeto del jugador en sí, que a su vez deriva de AnimatedSprite para que pueda pasarlo fácilmente al renderizador y obtener todos los datos necesarios.
Sin embargo, en general, su mapa de mosaicos no debería ser capaz de hacer todo: tendría una clase "TileMap" que se encarga de administrar todos los mosaicos, y tal vez también detecte colisiones entre los objetos que le entregue y Los azulejos en el mapa. Entonces, tendría otra clase "RPGMap", o como quiera llamarlo, que tiene tanto una referencia a su mapa de mosaico como a la referencia al jugador y hace que la actualización () real llame a su jugador y a su mapa de mosaico.
La forma en que desea actualizar el modelo cuando el jugador se mueve depende de lo que quiera hacer.
¿Tu jugador puede moverse entre fichas independientemente (estilo Zelda)? Simplemente maneje la entrada y mueva el reproductor en consecuencia en cada cuadro. ¿O quieres que el jugador presione "derecha" y tu personaje mueve automáticamente una ficha a la derecha? Deje que su clase RPGMap interpole la posición de los jugadores hasta que llegue a su destino y, mientras tanto, bloquee todo el manejo de entrada de la tecla de movimiento.
De cualquier manera, si desea que sea más fácil para usted, todos sus modelos tendrán métodos Update () si realmente necesitan algo de lógica para actualizarse (en lugar de simplemente cambiar los valores de las variables): no regala el controlador en el patrón MVC de esa manera, simplemente mueve el código de "un paso arriba" (el controlador) hacia el modelo, y todo lo que hace el controlador es llamar a este método Update () del modelo (El controlador en nuestro caso sería el RPGMap). Todavía puede cambiar fácilmente el código lógico: puede cambiar directamente el código de la clase o, si necesita un comportamiento completamente diferente, puede derivar de su clase de modelo y solo anular el método Update ().
Ese enfoque reduce las llamadas a métodos y cosas por el estilo, que solía ser una de las principales desventajas del patrón MVC puro (terminas llamando a GetThis () GetThat () muy a menudo): hace que el código sea más largo y un poco más difícil de leer y también más lento, a pesar de que su compilador podría encargarse de eso, ya que optimiza muchas cosas como esa.