Wie gehe ich mit beliebig großen Bildern in 2D-Spielen um?

13

Frage

Was machst du, wenn du ein 2D-Spiel hast, das sehr große Bilder verwendet (die größer sind, als deine Hardware unterstützt)? Vielleicht gibt es eine interessante Lösung für dieses Problem, die mir noch nie zuvor aufgefallen ist.

Im Grunde arbeite ich an einer Art Grafik-Adventure-Game-Maker. Meine Zielgruppe sind Leute mit wenig bis gar keiner Erfahrung in der Spieleentwicklung (und Programmierung). Wenn Sie mit einer solchen Anwendung arbeiten, möchten Sie nicht, dass sie das Problem intern löst, anstatt Ihnen mitzuteilen, dass Sie "Ihre Bilder aufteilen" sollen?

Kontext

Es ist allgemein bekannt, dass Grafikkarten eine Reihe von Beschränkungen hinsichtlich der Größe der Texturen auferlegen, die sie verwenden können. Wenn Sie beispielsweise mit XNA arbeiten, sind Sie auf eine maximale Texturgröße von 2048 oder 4096 Pixel beschränkt, je nachdem, welches Profil Sie auswählen.

In 2D-Spielen ist dies normalerweise kein großes Problem, da die meisten Level aus kleineren Einzelstücken (z. B. Kacheln oder transformierten Sprites) aufgebaut werden können.

Denken Sie jedoch an einige klassische Point'n'Click-Grafik-Abenteuerspiele (z. B. Monkey Island). Räume in grafischen Abenteuerspielen sind in der Regel als Ganzes gestaltet, mit wenigen oder keinen wiederholbaren Abschnitten, genau wie ein Maler, der an einer Landschaft arbeitet. Oder vielleicht ein Spiel wie Final Fantasy 7, das große vorgerenderte Hintergründe verwendet.

Einige dieser Räume können ziemlich groß werden und häufig drei oder mehr Bildschirme mit einer Breite von bis zu 30 cm umfassen. Wenn wir diese Hintergründe nun im Kontext eines modernen High-Definition-Spiels betrachten, überschreiten sie leicht die maximal unterstützte Texturgröße.

Die naheliegende Lösung besteht nun darin, den Hintergrund in kleinere Abschnitte zu unterteilen, die den Anforderungen entsprechen, und diese nebeneinander zu zeichnen. Aber sollten Sie (oder Ihr Künstler) derjenige sein, der alle Ihre Texturen aufteilt?

Wenn Sie mit einem High-Level-API arbeiten, sollte es dann nicht vielleicht darauf vorbereitet sein, mit dieser Situation umzugehen und die Texturen intern aufzuteilen und zusammenzusetzen (entweder als Vorprozess wie die Inhaltspipeline von XNA oder zur Laufzeit)?

Bearbeiten

Hier ist, was ich dachte, aus der Sicht der XNA-Implementierung.

Meine 2D-Engine benötigt nur einen kleinen Teil der Funktionen von Texture2D. Ich kann es tatsächlich auf diese Schnittstelle reduzieren:

public interface ITexture
{
    int Height { get; }
    int Width { get; }
    void Draw(SpriteBatch spriteBatch, Vector2 position, Rectangle? source, Color  color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth);
}

Die einzige externe Methode, die ich verwende und die auf Texture2D basiert, ist SpriteBatch.Draw (), sodass ich eine Brücke zwischen ihnen erstellen kann, indem ich eine Erweiterungsmethode hinzufüge:

    public static void Draw(this SpriteBatch spriteBatch, ITexture texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
    {
        texture.Draw(spriteBatch, position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth);
    }

Auf diese Weise kann ich eine ITexture austauschbar verwenden, wenn ich zuvor eine Texture2D verwendet habe.

Dann könnte ich zwei verschiedene Implementierungen von ITexture erstellen, z. B. RegularTexture und LargeTexture, wobei RegularTexture einfach eine reguläre Texture2D umschließt.

Andererseits würde LargeTexture eine Liste von Texture2Ds führen, die jeweils einem der aufgeteilten Teile des Vollbilds entsprechen. Der wichtigste Teil ist, dass das Aufrufen von Draw () in der LargeTexture in der Lage sein sollte, alle Transformationen anzuwenden und alle Teile so zu zeichnen, als wären sie nur ein zusammenhängendes Bild.

Um den gesamten Prozess transparent zu machen, würde ich eine einheitliche Texture Loader-Klasse erstellen, die als Factory-Methode fungieren würde. Es würde den Pfad der Textur als Parameter nehmen und eine ITexture zurückgeben, die entweder eine RegularTexture oder eine LargeTexture ist, abhängig davon, ob die Bildgröße die Grenze überschreitet oder nicht. Der Benutzer musste jedoch nicht wissen, um welches es sich handelte.

Ein bisschen übertrieben oder klingt es okay?

David Gouveia
quelle
1
Wenn Sie das Bild aufteilen möchten, erweitern Sie die Inhaltspipeline, um dies programmgesteuert zu tun. Es sind nicht alle Funktionen vorhanden, um jedes einzelne Szenario abzudecken, und irgendwann müssen Sie sie selbst erweitern. Das persönliche Teilen klingt nach der einfachsten Lösung, da Sie dann Bilder wie Kacheln verarbeiten können, auf denen sich zahlreiche Ressourcen befinden.
DMan
Dies in der Inhalts-Pipeline zu tun, ist der sinnvollste Ansatz. Aber in meinem Fall musste ich meine Bilder zur Laufzeit laden und es ist mir bereits gelungen, eine Laufzeitlösung zu finden (weshalb ich diese Frage gestern gestellt habe). Aber die wahre Schönheit liegt darin, dass man diese "Kachelkarte" nimmt und sie wie eine normale Textur verhält. Dh man kann es immer noch Unentschieden Spritebatch passieren zu, und Sie können nach wie vor Position angeben, Rotation, Skalierung, src / dest Rechtecke usw. bin ich etwa auf halber Strecke dort jetzt :)
David Gouveia
Aber um ehrlich zu sein, ich bin bereits an einem Punkt angelangt, an dem ich mich frage, ob sich das alles wirklich lohnt oder ob ich dem Benutzer einfach eine Warnung anzeigen soll, dass die maximal unterstützte Bildgröße 2048 x 2048 beträgt, und damit fertig bin.
David Gouveia
Das Design liegt ganz bei Ihnen. Wenn Sie das Projekt beenden und nicht zwischen den Räumen scrollen können, ist es besser, als den Editor zu beenden, mit dem Sie große Texturen scrollen können: D
Aleks
Das Verstecken in der API klingt nach einer guten Möglichkeit, das Leben der Spieleautoren zu erleichtern. Ich würde es wahrscheinlich in den Klassen Polygon (Sprite) und Texture implementieren und die kleineren Bilder nicht als Sonderfall, sondern nur als Kachel mit dem Wert 1x1. Die Texturklasse ist also eine Texturgruppe, die alle Kacheln und wie viele Zeilen / Spalten beschreibt. Die Polygon-Klasse betrachtet dann diese Informationen, um sich aufzuteilen und jede Kachel mit der entsprechenden Textur zu zeichnen. Auf diese Weise es ist die gleiche Klasse. Bonuspunkte, wenn Ihre Sprite-Klasse nur sichtbare Kacheln zeichnet und Texturgruppen Texturen erst nach Bedarf laden.
uliwitness

Antworten:

5

Ich denke, Sie sind auf dem richtigen Weg. Sie müssen die Bilder in kleinere Teile teilen. Da Sie einen Game Maker erstellen, können Sie die Content-Pipeline von XNA nicht verwenden. Es sei denn, Ihr Hersteller ist eher eine Engine, für die die Entwickler weiterhin Visual Studio verwenden müssen. Sie müssen also Ihre eigenen Routinen erstellen, die die Aufteilung durchführen. Sie sollten sogar in Betracht ziehen, die Teile vorher zu streamen, damit Sie immer noch nicht einschränken, wie viel Videospeicher die Player haben sollen.

Es gibt einige Tricks, die Sie speziell für Abenteuerspiele machen können. Ich stelle mir vor, wie bei allen klassischen Abenteuerspielen werden Sie einige Schichten dieser Texturen haben, damit ... Ihr Charakter hinter dem Baum oder dem Gebäude wandeln kann ...

Wenn Sie möchten, dass diese Ebenen einen Paralaxing-Effekt haben, überspringen Sie beim Teilen Ihrer Fliesen einfach alle durchscheinenden Bereiche einmal. Hier muss man sich die Größe der Fliesen überlegen. Kleinere Kacheln erhöhen den Verwaltungsaufwand und zeichnen Anrufe, enthalten jedoch weniger Daten, während größere Kacheln GPU-freundlicher sind, aber eine hohe Transparenz aufweisen.

Wenn Sie keinen Paralaxing-Effekt haben, können Sie, anstatt die gesamte Szene mit 2-4 riesigen Bildern mit einer Tiefe von 32 Bit abzudecken, nur eine 32-Bit-Tiefe erstellen. Sie können auch eine Schablonen- oder Tiefenebene mit nur 8 Bit pro Pixel erstellen.

Ich weiß, dass es für Nicht-Spieleentwickler schwierig ist, Assets zu verwalten, aber das muss es eigentlich nicht sein. Wenn Sie zum Beispiel eine Straße mit 3 Bäumen haben und der Spieler hinter 2 davon und vor dem dritten Baum und dem Rest des Hintergrunds steht ... Stellen Sie das Szenenlayout in Ihrem Editor so ein, wie Sie es möchten und dann in einem Bau Schritt von Ihrem Game Maker können Sie diese riesigen Bilder (auch kleine Kacheln eines großen Bildes) backen.

Wie die klassischen Spiele das machten. Ich bin mir nicht sicher, ob sie wahrscheinlich ähnliche Tricks gemacht haben, aber man muss bedenken, dass die Spiele zu der Zeit 320x240 Farben hatten, was es bei Verwendung palattelisierter Texturen ermöglicht, 2 Pixel in einem Byte zu kodieren. Aber so funktionieren die aktuellen Architekturen nicht: D

Viel Glück

Aleks
quelle
Vielen Dank, viele sehr interessante Ideen hier! Ich habe tatsächlich die Schablonen-Technik gesehen, die vorher in Adventure Game Studio verwendet wurde. Was meine Bewerbung betrifft, mache ich die Dinge ein bisschen anders. Zimmer haben nicht unbedingt ein Hintergrundbild. Stattdessen gibt es eine Palette von Bildern, die der Benutzer importiert hat, und eine Hintergrund- / Vordergrundebene. Dem Benutzer steht es frei, Teile in den Raum zu ziehen und dort abzulegen, um sie zu erstellen. Also, wenn er wollte, konnte er es auch aus kleineren Stücken komponieren, ohne nicht interaktiv Spielobjekte erstellen zu müssen. Es gibt jedoch keine Parallaxe, da es nur diese beiden Schichten gibt.
David Gouveia
Und ich habe mir die Datendateien für Monkey Island Special Edition (das HQ-Remake) angesehen. Jeder Hintergrund wird in genau 1024 x 1024 Teile aufgeteilt (auch wenn ein großer Teil des Bildes ungenutzt bleibt). Wie auch immer, überprüfe meine Bearbeitung, um den gesamten Prozess transparent zu machen.
David Gouveia
Ich würde wahrscheinlich etwas Vernünftigeres wie 256x256 auswählen. Auf diese Weise werde ich keine Ressourcen verschwenden und wenn ich mich später entscheide, Streaming zu implementieren, werden mehr vernünftige Blöcke geladen. Vielleicht habe ich es nicht gut genug erklärt, aber ich schlug vor, dass Sie all diese kleinen statischen Spielobjekte zu einer großen Textur backen könnten. Was Sie speichern, ist, falls Sie eine große Hintergrundtextur haben, alle Pixel, die von den statischen Objekten oben abgedeckt werden.
Aleks
Ich sehe, also füge alle statischen Objekte in den Hintergrund ein, teile sie und erstelle einen Texturatlas. Ich könnte nach diesem Gedankengang auch alles andere in einen Texturatlas backen. Das ist auch eine schöne Idee. Aber ich lasse es für später, weil ich momentan keinen "Build" -Schritt habe, dh sowohl der Editor als auch der Spieleclient laufen auf derselben Engine und demselben Datenformat.
David Gouveia
Ich mag den Stencil-Ansatz, aber wenn ich Adventure-Game-Editor mache, würde ich ihn wahrscheinlich nicht verwenden ... Vielleicht, weil ich, wenn ich ein solches Tool baue, für Spieleentwickler gedacht bin. Nun, es hat Vor- und Nachteile. Sie können zum Beispiel ein anderes Bild verwenden, auf dem die Bits Trigger pro Pixel definieren können, oder wenn Sie etwas Wasser animiert haben, können Sie es einfach herausschablonieren. Hinweis: Ein seltsamer, nicht verwandter Vorschlag, der sich mit den UML-Zustands- / Aktivitätsdiagrammen für Ihre Skripterstellung befasst.
Aleks
2

Schneiden Sie sie auf, damit sie nicht mehr beliebig groß sind, wie Sie sagen. Es gibt keine Magie. Diese alten 2D-Spiele haben dies entweder getan oder sie wurden in Software gerendert. In diesem Fall gab es keine Hardwareeinschränkungen über die RAM-Größe hinaus. Bemerkenswert ist, dass viele dieser 2D-Spiele keinen großen Hintergrund hatten, sondern nur langsam scrollten, sodass sie sich riesig anfühlten.

Was Sie in Ihrem Grafik-Adventure-Game-Maker tun möchten, ist, diese großen Bilder in einer Art "Build" - oder "Integrations" -Schritt vorzubereiten, bevor das Spiel zum Laufen gebracht wird.

Wenn Sie eine vorhandene API und Bibliothek verwenden möchten, anstatt Ihre eigenen zu schreiben, empfehlen wir Ihnen, diese großen Bilder während dieses "Builds" in Kacheln umzuwandeln und eine 2D-Kachel-Engine zu verwenden, von denen es viele gibt.

Wenn Sie Ihre eigenen 3D-Techniken verwenden möchten, möchten Sie möglicherweise trotzdem in Kacheln aufteilen und eine bekannte und gut konzipierte 2D-API verwenden, um Ihre eigene 3D-Bibliothek dafür zu schreiben. Auf diese Weise können Sie sicher sein, dass Sie zumindest damit beginnen Ein gutes API-Design und weniger Design von Ihrer Seite.

Patrick Hughes
quelle
Es wäre ideal, wenn ich es zur Build-Zeit machen würde ... Aber mein Editor verwendet auch XNA zum Rendern, es ist nicht nur die Engine. Das heißt, sobald der Benutzer das Bild importiert und in einen Raum zieht, sollte XNA es bereits rendern können. Mein großes Problem ist also, ob beim Laden des Inhalts die Aufteilung in den Speicher oder beim Importieren des Inhalts auf die Festplatte erfolgen soll. Außerdem würde das physische Aufteilen des Bildes in mehrere Dateien bedeuten, dass ich diese auf eine andere Weise speichern müsste, wohingegen das Speichern zur Laufzeit keine Änderung an den Datendateien des Spiels erforderlich machen würde.
David Gouveia
1

Wenn Sie wissen, wie ein Quadtree in der visuellen Praxis aussieht, habe ich eine Methode verwendet, mit der detaillierte große Bilder in kleinere Bilder geschnitten werden, die dem Aussehen eines Proofs / einer Visualisierung eines Quadtrees relativ ähnlich sind. Meine wurden jedoch so erstellt, dass bestimmte Bereiche geschnitten wurden, die je nach Farbe unterschiedlich codiert oder komprimiert werden konnten. Sie können diese Methode verwenden und das tun, was ich beschrieben habe, da sie auch nützlich ist.

Wenn sie zur Laufzeit gekürzt werden, ist vorübergehend mehr Speicher erforderlich, und die Ladezeiten werden verlangsamt. Ich würde jedoch sagen, dass es davon abhängt, ob Sie mehr Wert auf physischen Speicher oder die Ladezeit eines mit Ihrem Editor erstellten Benutzerspiels legen.

Lade- / Laufzeit: Die Art und Weise, wie ich die Ladung ausführen würde, besteht darin, sie in proportional große Stücke zu schneiden. Rechnen Sie mit einem Pixel ganz rechts, wenn die Anzahl der Pixel jedoch ungerade ist. Niemand mag das Ausschneiden seiner Bilder, insbesondere unerfahrene Benutzer, die möglicherweise nicht wissen, dass es nicht ganz an ihnen liegt. Machen Sie sie halb so breit ODER halb so hoch (oder 3rds, 4rds, was auch immer). Der Grund dafür, dass sie nicht in Quadrate oder 4 Abschnitte (2 Zeilen, 2 Spalten) aufgeteilt werden, ist, dass dadurch der Kachelspeicher kleiner wird, da Sie sich nicht um x und y kümmern würden zur gleichen Zeit (und abhängig davon, wie viele Bilder so groß sind, könnte es ziemlich wichtig sein)

Vor der Hand / Automatisch: In diesem Fall gefällt mir die Idee, dem Benutzer die Wahl zu geben, ob er es in einem Bild oder als separate Teile speichern möchte (ein Bild sollte die Standardeinstellung sein). Das Speichern des Bildes als .gif würde sehr gut funktionieren. Das Bild würde sich in einer Datei befinden, und anstatt dass jeder Frame eine Animation ist, würde es sich eher um ein komprimiertes Archiv desselben Bildes handeln (was im Wesentlichen das ist, was ein GIF ist). Dies würde den physischen Datentransport und das Laden vereinfachen:

  1. Es ist eine einzelne Datei
  2. Alle Dateien hätten fast die gleiche Formatierung
  3. Sie können leicht wählen, wann UND ob Sie es in einzelne Texturen aufteilen möchten
  4. Es ist möglicherweise einfacher, auf die einzelnen Bilder zuzugreifen, wenn sie sich bereits in einem einzelnen geladenen GIF befinden, da nicht so viele Bilddateien im Speicher erforderlich sind

Oh, und wenn Sie die GIF-Methode verwenden, sollten Sie die Dateierweiterung in etwas anderes ändern (GIF -> Kittens), um weniger Verwirrung zu stiften, wenn Ihr GIF wie eine kaputte Datei animiert wird. (Mach dir keine Sorgen über die Legalität, .gif ist ein offenes Format)

Joshua Hedges
quelle
Hallo und danke für deinen Kommentar! Tut mir leid, wenn ich falsch verstanden habe, aber befasst sich das, was Sie geschrieben haben, nicht hauptsächlich mit Komprimierung und Speicherung? In diesem Fall mache ich mir nur Sorgen, dass der Benutzer meiner Anwendung jedes Bild importieren kann und die Engine (in XNA) jedes Bild zur Laufzeit laden und anzeigen kann . Ich habe es geschafft, es einfach zu lösen, indem ich mit GDI verschiedene Abschnitte des Bildes in separate XNA-Texturen eingelesen habe und sie in eine Klasse einbaute, die sich wie eine normale Textur verhält (dh das Zeichnen und die Transformation insgesamt unterstützt).
David Gouveia
Könnten Sie jedoch aus Neugier heraus etwas genauer erläutern, nach welchen Kriterien Sie den Bereich des Bildes ausgewählt haben, der in die einzelnen Abschnitte aufgenommen wurde?
David Gouveia
Einiges davon kann über Ihrem Kopf liegen, wenn Sie weniger Erfahrung mit dem Umgang mit Bildern haben. Aber im Grunde habe ich beschrieben, wie man das Bild in Segmente aufteilt. Und ja, der GIF-Punkt beschreibt sowohl das Speichern als auch das Laden, aber er ist als Methode gedacht, mit der Sie das Bild ausschneiden und dann wie es ist auf dem Computer speichern können. Wenn Sie es als GIF speichern, habe ich gesagt, dass Sie es in die einzelnen Bilder schneiden und dann als animiertes GIF speichern können, wobei jeder Schnitt des Bildes als separate Animationsrahmen gespeichert wird. Es funktioniert gut und ich habe sogar andere Funktionen hinzugefügt, bei denen es hilfreich sein könnte.
Joshua Hedges
Das Quadtree-System, wie ich es beschrieben habe, dient nur als Referenz, da ich von dort eine Idee habe. Anstatt eine möglicherweise nützliche Frage für Leser mit ähnlichen Problemen zu verdrängen, sollten wir Private Messaging verwenden, wenn Sie mehr wissen möchten.
Joshua Hedges
Roger, ich habe den Teil über die Verwendung von GIF-Frames als eine Art Archiv. Aber ich frage mich auch, ist GIF nicht ein indiziertes Format mit begrenzter Farbpalette? Kann ich in jedem Frame nur ganze 32-Bit-RGBA-Bilder speichern? Und ich stelle mir den Ladevorgang sogar noch komplizierter vor, da XNA nichts davon unterstützt.
David Gouveia
0

Das wird sich natürlich anhören, aber warum teilen Sie Ihren Raum nicht einfach in kleinere Teile auf? Wählen Sie eine beliebige Größe, die keine Grafikkarten (z. B. 1024 x 1024 oder höher) trifft, und teilen Sie Ihre Räume einfach in mehrere Teile auf.

Ich weiß, dass dies das Problem vermeidet, und ehrlich gesagt ist dies ein sehr interessantes Problem, das es zu lösen gilt. Auf jeden Fall ist dies eine triviale Lösung, die manchmal für Sie hilfreich sein kann.

ashes999
quelle
Ich denke, ich hätte den Grund vielleicht irgendwo in diesem Thread erwähnt, aber da ich nicht sicher bin, ob ich es wiederhole. Grundsätzlich ist es kein Spiel, sondern ein Game Maker für die breite Öffentlichkeit (denken Sie an etwas im gleichen Umfang wie RPG Maker). Anstatt die Beschränkung der Texturgröße zu erzwingen, wenn die Benutzer ihre eigenen Assets importieren, ist es möglicherweise hilfreich, damit umzugehen das Teilen und Zusammenfügen automatisch hinter den Kulissen, ohne sie jemals mit den technischen Details der Texturgrößenbeschränkungen zu belasten.
David Gouveia
Okay, das macht Sinn. Entschuldigung, das habe ich wohl verpasst.
Asche999