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.
Puedo desarrollar esto si lo desea, pero tengo un procesador central que me dice que dibuje en el bucle. Más bien que
Tengo un sistema mas como
La clase de renderizador simplemente contiene una lista de referencias a componentes dibujables de objetos. Estos se asignan en los constructores por simplicidad.
Para su ejemplo, tendría una clase de GameBoard con varios Tiles. Cada mosaico obviamente conoce su posición, y supongo algún tipo de animación. Factorice eso en algún tipo de clase de animación que posea el mosaico, y haga que pase una referencia de sí mismo a una clase Renderer. Allí, todos separados. Cuando actualiza Tile, llama a Update en la animación ... o la actualiza por sí misma. Cuando
Renderer.Draw()
se llama, dibuja la animación.La animación independiente del fotograma no debería tener mucho que ver con el ciclo de dibujo.
fuente
Últimamente he estado aprendiendo los paradigmas, así que si esta respuesta es incompleta, estoy seguro de que alguien la agregará.
La metodología que parece tener más sentido para el diseño del juego es separar la lógica de la salida de la pantalla.
En la mayoría de los casos, desearía utilizar un enfoque de subprocesos múltiples, si no está familiarizado con ese tema, es una pregunta propia, aquí está el manual de wiki . Esencialmente, desea que la lógica de su juego se ejecute en un hilo, bloqueando las variables a las que necesita acceder para garantizar la integridad de los datos. Si su bucle lógico es increíblemente rápido (¿super mega animada 3d pong?), Puede intentar arreglar la frecuencia que ejecuta el bucle durmiendo el hilo durante pequeñas duraciones (se han sugerido 120 hz en este foro para bucles de física de juegos). Al mismo tiempo, el otro hilo vuelve a dibujar la pantalla (se han sugerido 60 hz en otros temas) con las variables actualizadas, nuevamente solicitando un bloqueo en las variables antes de acceder a ellas.
En ese caso, las animaciones o transiciones, etc., entran en el hilo de dibujo, pero debe indicar a través de una bandera de algún tipo (una variable de estado global tal vez) que el hilo lógico del juego no debe hacer nada (o hacer algo diferente ... configurar el nuevos parámetros del mapa tal vez).
Una vez que entiendes la concurrencia, el resto es bastante comprensible. Si no tiene experiencia con la concurrencia, le sugiero que escriba algunos programas de prueba simples para que pueda entender cómo sucede el flujo.
Espero que esto ayude :)
[editar] En los sistemas que no admiten subprocesos múltiples, la animación aún puede entrar en el ciclo de dibujo, pero desea establecer el estado de tal manera que indique a la lógica que está ocurriendo algo diferente y no siga procesando el nivel actual / mapa / etc ...
fuente