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.
Ich kann darauf aufbauen, wenn Sie es wünschen, aber ich habe einen zentralen Renderer, der angewiesen wird, in der Schleife zu zeichnen. Eher, als
Ich habe eher ein System wie
Die Renderer-Klasse enthält einfach eine Liste von Verweisen auf zeichnbare Komponenten von Objekten. Diese sind der Einfachheit halber in den Konstruktoren zugeordnet.
Für Ihr Beispiel hätte ich eine GameBoard-Klasse mit mehreren Kacheln. Jede Kachel kennt offensichtlich ihre Position und ich gehe von einer Art Animation aus. Berücksichtigen Sie dies in einer Art Animationsklasse, die der Kachel gehört, und lassen Sie sie einen Verweis auf eine Renderer-Klasse übergeben. Dort trennten sich alle. Wenn Sie Tile aktualisieren, wird Update für die Animation aufgerufen oder selbst aktualisiert. Wenn
Renderer.Draw()
es aufgerufen wird, wird die Animation gezeichnet.Die rahmenunabhängige Animation sollte nicht zu viel mit der Zeichenschleife zu tun haben.
quelle
Ich habe die Paradigmen in letzter Zeit selbst gelernt. Wenn diese Antwort unvollständig ist, wird sicher jemand etwas hinzufügen.
Die Methode, die für das Spieldesign am sinnvollsten erscheint, besteht darin, die Logik von der Bildschirmausgabe zu trennen.
In den meisten Fällen möchten Sie einen Multithread-Ansatz verwenden. Wenn Sie mit diesem Thema nicht vertraut sind, handelt es sich um eine eigene Frage. Hier ist die Einführung des Wikis . Im Wesentlichen möchten Sie, dass Ihre Spielelogik in einem Thread ausgeführt wird und Variablen sperrt, auf die zugegriffen werden muss, um die Datenintegrität sicherzustellen. Wenn Ihre Logikschleife unglaublich schnell ist (Super-Mega-animiertes 3D-Pong?), Können Sie versuchen, die Frequenz zu korrigieren, die die Schleife ausführt, indem Sie den Thread für kurze Zeit ausschalten (120 Hz wurden in diesem Forum für Spielphysik-Schleifen vorgeschlagen). Gleichzeitig zeichnet der andere Thread den Bildschirm mit den aktualisierten Variablen neu (60 Hz wurden in anderen Themen vorgeschlagen) und fordert erneut eine Sperre für die Variablen an, bevor er auf sie zugreift.
In diesem Fall gehen Animationen oder Übergänge usw. in den Zeichnungsthread, aber Sie müssen durch eine Art Flag (möglicherweise eine globale Statusvariable) signalisieren, dass der Spielelogik-Thread nichts tun darf (oder etwas anderes tun muss ... richten Sie das ein neue Kartenparameter vielleicht).
Sobald Sie sich mit der Parallelität vertraut gemacht haben, ist der Rest ziemlich verständlich. Wenn Sie keine Erfahrung mit Parallelität haben, empfehle ich Ihnen dringend, ein paar einfache Testprogramme zu schreiben, damit Sie verstehen, wie der Ablauf abläuft.
Hoffe das hilft :)
[Bearbeiten] Auf Systemen, die kein Multithreading unterstützen, kann die Animation weiterhin in die Zeichenschleife verschoben werden. Sie möchten den Status jedoch so einstellen, dass der Logik signalisiert wird, dass etwas anderes auftritt, und das nicht weiter verarbeiten aktuelles Level / Karte / etc ...
quelle