L'animation peut toujours parfaitement être partagée entre la logique et le rendu. L'état abstrait des données d'animation serait l'information nécessaire à votre API graphique pour restituer l'animation.
Dans les jeux 2D par exemple, cela pourrait être une zone rectangle qui marque la zone qui affiche la partie actuelle de votre feuille de sprite qui doit être dessinée (lorsque vous avez une feuille composée de disons 30 dessins 80x80 contenant les différentes étapes de votre personnage sauter, s'asseoir, bouger, etc.). Il peut également s'agir de tout type de données dont vous n'avez pas besoin pour le rendu, mais peut-être pour la gestion des états d'animation eux-mêmes, comme le temps restant jusqu'à l'expiration de l'étape d'animation en cours ou le nom de l'animation ("marche", "debout" etc.) Tout cela peut être représenté comme vous le souhaitez. C'est la partie logique.
Dans la partie de rendu, vous le faites comme d'habitude, récupérez ce rectangle à partir de votre modèle et utilisez votre moteur de rendu pour effectuer les appels à l'API graphique.
Dans le code (en utilisant ici la syntaxe C ++):
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.
};
Voilà les données. Votre moteur de rendu prendra ces données et les dessinera. Puisque les Sprites normaux et animés sont dessinés de la même manière, vous pouvez utiliser la polymorphie ici!
class Renderer
{
//etc.
public:
void Draw(const Sprite &spr)
{
graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
}
};
TMV:
J'ai trouvé un autre exemple. Disons que vous avez un RPG. Votre modèle qui représente la carte du monde, par exemple, aurait probablement besoin de stocker la position du personnage dans le monde sous forme de coordonnées de tuile sur la carte. Cependant, lorsque vous déplacez le personnage, il marche de quelques pixels à la fois jusqu'au carré suivant. Stockez-vous cette position "entre les tuiles" dans un objet d'animation? Comment mettez-vous à jour le modèle lorsque le personnage est finalement «arrivé» à la coordonnée de tuile suivante sur la carte?
La carte du monde ne connaît pas directement la position des joueurs (elle n'a pas de Vector2f ou quelque chose comme ça qui stocke directement la position des joueurs =, au lieu de cela, elle a une référence directe à l'objet joueur lui-même, qui dérive à son tour d'AnimatedSprite afin que vous puissiez le transmettre facilement au moteur de rendu et en obtenir toutes les données nécessaires.
En général cependant, votre tilemap ne devrait pas être capable de tout faire - j'aurais une classe "TileMap" qui prend en charge la gestion de toutes les tuiles, et peut-être aussi la détection de collision entre les objets que je lui remets et les tuiles sur la carte. Ensuite, j'aurais une autre classe "RPGMap", ou comme vous voudriez l'appeler, qui a à la fois une référence à votre tilemap et la référence au joueur et fait les appels Update () réels à votre joueur et à votre tilemap.
La façon dont vous souhaitez mettre à jour le modèle lorsque le joueur se déplace dépend de ce que vous voulez faire.
Votre joueur est-il autorisé à se déplacer entre les tuiles indépendamment (style Zelda)? Manipulez simplement l'entrée et déplacez le lecteur en conséquence à chaque image. Ou voulez-vous que le joueur appuie sur "droite" et que votre personnage déplace automatiquement une tuile vers la droite? Laissez votre classe RPGMap interpoler la position des joueurs jusqu'à ce qu'il arrive à destination et en attendant verrouillez toute la gestion des entrées de clé de mouvement.
Quoi qu'il en soit, si vous voulez vous faciliter la tâche, tous vos modèles auront des méthodes Update () s'ils ont réellement besoin de logique pour se mettre à jour (au lieu de simplement changer les valeurs des variables) - Vous ne donnez pas le contrôleur dans le modèle MVC de cette façon, vous déplacez simplement le code de "une étape au-dessus" (le contrôleur) vers le modèle, et tout ce que le contrôleur fait est d'appeler cette méthode Update () du modèle (le contrôleur dans notre cas serait le RPGMap). Vous pouvez toujours facilement échanger le code logique - vous pouvez simplement changer directement le code de la classe ou si vous avez besoin d'un comportement complètement différent, vous pouvez simplement dériver de votre classe de modèle et remplacer uniquement la méthode Update ().
Cette approche réduit beaucoup les appels de méthode et des choses comme ça - ce qui était l'un des principaux inconvénients du modèle MVC pur (vous finissez par appeler très souvent GetThis () GetThat ()) - cela rend le code à la fois plus long et plus un peu plus difficile à lire et aussi plus lent - même si cela peut être pris en charge par votre compilateur qui optimise beaucoup de choses comme ça.