Die Animation kann immer noch perfekt zwischen Logik und Rendering aufgeteilt werden. Der abstrakte Datenstatus der Animation ist die Information, die Ihre Grafik-API zum Rendern der Animation benötigt.
In 2D-Spielen kann dies beispielsweise ein rechteckiger Bereich sein, der den Bereich markiert, in dem der aktuelle Teil Ihres Sprite-Blattes angezeigt wird, der gezeichnet werden muss (wenn Sie ein Blatt haben, das beispielsweise aus 30 80x80-Zeichnungen besteht, die die verschiedenen Schritte Ihres Charakters enthalten springen, sitzen, sich bewegen etc.). Es können auch alle Arten von Daten sein, die Sie nicht zum Rendern benötigen, sondern möglicherweise zum Verwalten der Animationszustände selbst, z. B. die verbleibende Zeit bis zum Ablauf des aktuellen Animationsschritts oder der Name der Animation ("Gehen", "Stehen"). etc.) All das kann beliebig dargestellt werden. Das ist der logische Teil.
Im Rendering-Teil machen Sie es einfach wie gewohnt, holen dieses Rechteck aus Ihrem Modell und verwenden Ihren Renderer, um die Aufrufe der Grafik-API tatsächlich auszuführen.
Im Code (hier mit C ++ - Syntax):
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.
};
Das sind die Daten. Ihr Renderer nimmt diese Daten und zeichnet sie. Da sowohl normale als auch animierte Sprites auf dieselbe Weise gezeichnet werden, können Sie hier die Polymorphie verwenden!
class Renderer
{
//etc.
public:
void Draw(const Sprite &spr)
{
graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
}
};
TMV:
Ich habe mir ein anderes Beispiel ausgedacht. Angenommen, Sie haben ein Rollenspiel. Ihr Modell, das beispielsweise die Weltkarte darstellt, müsste wahrscheinlich die Position des Charakters in der Welt als Kachelkoordinaten auf der Karte speichern. Wenn Sie den Charakter jedoch bewegen, gehen sie jeweils einige Pixel zum nächsten Quadrat. Speichern Sie diese Position "zwischen Kacheln" in einem Animationsobjekt? Wie aktualisiere ich das Modell, wenn der Charakter endlich an der nächsten Kachelkoordinate auf der Karte "angekommen" ist?
Die Weltkarte kennt die Position des Spielers nicht direkt (sie hat keinen Vector2f oder ähnliches, in dem die Position des Spielers direkt gespeichert ist =, stattdessen verweist sie direkt auf das Spielerobjekt selbst, das wiederum von AnimatedSprite abgeleitet ist So können Sie es einfach an den Renderer übergeben und erhalten alle erforderlichen Daten.
Im Allgemeinen sollte Ihre Tilemap jedoch nicht alles können - ich hätte eine Klasse "TileMap", die sich um die Verwaltung aller Kacheln kümmert, und möglicherweise auch eine Kollisionserkennung zwischen Objekten, die ich ihr übergebe, und die Kacheln auf der Karte. Dann hätte ich eine andere "RPGMap" -Klasse, oder wie auch immer Sie sie nennen möchten, die sowohl einen Verweis auf Ihre Tilemap als auch den Verweis auf den Player enthält und die eigentlichen Update () -Aufrufe an Ihren Player und an Ihren Tilemap.
Wie Sie das Modell aktualisieren möchten, wenn sich der Player bewegt, hängt davon ab, was Sie tun möchten.
Darf sich Ihr Spieler unabhängig zwischen den Plättchen bewegen (Zelda-Stil)? Behandeln Sie einfach die Eingabe und bewegen Sie den Player in jedem Frame entsprechend. Oder soll der Spieler "rechts" drücken und dein Charakter bewegt automatisch ein Plättchen nach rechts? Lassen Sie Ihre RPGMap-Klasse die Position des Spielers interpolieren, bis er an seinem Ziel ankommt, und sperren Sie währenddessen die gesamte Eingabe der Bewegungstasten.
In beiden Fällen verfügen alle Modelle über Update () -Methoden, wenn Sie tatsächlich eine Logik benötigen, um sich selbst zu aktualisieren (anstatt nur die Werte von Variablen zu ändern). Sie geben den Controller nicht weiter Auf diese Weise verschieben Sie im MVC-Muster einfach den Code von "einem Schritt über" (dem Controller) nach unten zum Modell, und der Controller ruft lediglich diese Update () -Methode des Modells auf (in unserem Fall wäre dies der Controller) die RPGMap). Sie können den Logikcode weiterhin problemlos austauschen. Sie können den Code der Klasse direkt ändern oder, wenn Sie ein völlig anderes Verhalten benötigen, einfach von Ihrer Modellklasse ableiten und nur die Update () -Methode überschreiben.
Dieser Ansatz reduziert Methodenaufrufe und ähnliches erheblich - was früher einer der Hauptnachteile des reinen MVC-Musters war (am Ende rufen Sie GetThis () GetThat () sehr, sehr oft auf) - und verlängert den Code sowohl länger als auch länger ein bisschen schwieriger zu lesen und auch langsamer - auch wenn Ihr Compiler sich darum kümmert, der viele solche Dinge optimiert.