Ich versuche, eine flexible, auf Kacheln basierende Spiel-Engine zu erstellen, um alle Arten von Nicht-Echtzeit-Puzzlespielen zu erstellen, genau wie Bejeweled, Civilization, Sokoban und so weiter.
Der erste Ansatz, den ich hatte, bestand darin, ein 2D-Array von Kachelobjekten zu haben und dann Klassen von Kacheln zu erben, die die Spielobjekte repräsentierten. Leider konnte ich auf diese Weise nicht mehr Spielelemente auf demselben Plättchen stapeln, ohne ein 3D-Array zu haben.
Dann habe ich etwas anderes gemacht: Ich hatte immer noch das 2D-Array von Kachelobjekten, aber jedes Kachelobjekt enthielt eine Liste, in die ich sie legte, und verschiedene Objekte. Dies hat gut funktioniert, bis ich vor 20 Minuten feststellte, dass es zu teuer ist, viele Dinge zu tun. Schauen Sie sich dieses Beispiel an:
Ich habe eine Wall-Entität. Bei jedem Update muss ich die 8 benachbarten Kacheln überprüfen, dann alle Entitäten in der Kachelliste überprüfen, prüfen, ob eine dieser Entitäten eine Wand ist, und schließlich das richtige Sprite zeichnen. (Dies geschieht, um Wände zu zeichnen, die nahtlos nebeneinander liegen.)
Die einzige Lösung, die ich jetzt sehe, ist ein 3D-Array mit vielen Ebenen, das für jede Situation geeignet ist. Auf diese Weise kann ich jedoch nicht zwei Objekte stapeln, die dieselbe Ebene auf derselben Kachel haben. Wann immer ich das tun möchte, muss ich eine neue Ebene erstellen.
Gibt es eine bessere Lösung? Was würden Sie tun?
quelle
Antworten:
Hast du tatsächlich gesehen dieses Problem oder haben Sie es sich gerade ausgedacht? Weil ich nicht sehe, wie sich das Durchlaufen der Objektliste spürbar auf die Leistung auswirkt. Sie müssten Hunderte von Objekten pro Kachel haben, damit dies eine Rolle spielt. Die meisten Spiele, die ich mir vorstellen kann, haben 10 Tops.
Wenn Sie wirklich ein Problem haben, ist es am besten, Daten zwischenzuspeichern und nicht die Darstellung zu ändern. In Ihrem Beispiel ändert die Wandkonfiguration wahrscheinlich nicht jeden Frame. Cache einfach den richtigen Sprite-Typ zwischen und berechne ihn nicht ständig neu.
Wenn Sie ein verrücktes Spiel mit vielen Objekten erstellen möchten, die sich ständig ändern, können Sie verschiedene Ebenen mit unterschiedlicher Semantik erstellen. Beispielsweise enthält die Wandebene nur Wände, nicht mehr als 1 pro Kachel, und die Dekorationsebene enthält beispielsweise Listen von Objekten, die sich nie ändern.
Wenn Sie eine ultra-flexible Architektur wünschen, die jedes mögliche Spiel ideal unterstützt - Pech -, gibt es so etwas nicht.
quelle
Zwei Vorschläge. Zunächst sollten Sie das Sprite auflösen, mit dem jede Kachel beim Laden Ihrer Kachelkarte / -ebene gezeichnet wird. Es sollte einfach genug sein, das 2D-Array zu durchlaufen, wenn Ihr Level geladen ist, und zu entscheiden, dass eine Certian-Wand ein L sein muss Form und eine andere eine | haben mussgestalten.
Zweitens würde ich statische Daten, die sich auf eine Kachel beziehen, an einem anderen Ort als aktive Daten speichern. Ein Kachelobjekt kann also eine Liste von Objekten haben, die von der Kachel kommen und gehen, aber es besteht keine Notwendigkeit, diese zu durchlaufen, um zu sehen, ob z. B. wir nur wissen möchten, ob die Kachel begehbar ist oder nicht.
Dieser Psudo-Code ist mehr oder weniger so, wie ich mich einem kachelbasierten System genähert habe.
Dies setzt voraus, dass es in Ihrem Spiel statische Kacheln gibt, aber das ist eine ziemlich gute Annahme in vielen kachelbasierten Spielen, in denen Sie mit Wänden und dergleichen zu tun haben.
quelle
Du sagtest:
Dies ist genau die Situation, die Sie mithilfe des MVC-Musters (Model View Controller) lösen möchten, um Ihr Modell von Ihrer Ansicht zu trennen. MVC macht dieses Problem aus zwei Teilen:
Anstatt ein 2D-Array von Kacheln oder ein 2D-Array einer Liste von Kacheln zu haben, würde Ihr Modell ein 2D-Array von Spielobjekten speichern. Dann betrachtet Ihr 2D-Array von Kachelklassen die entsprechenden Spielobjekte und entscheidet, wie sie gerendert werden sollen.
Anstatt dass eine einzelne Tile-Instanz ein einzelnes Spielobjekt rendert, können Sie eine einzelne Tile-Instanz etwas rendern lassen, das wie ein Stapel von Objekten aussieht . Dies wäre effizienter und würde Ihnen eine saubere Trennung Ihrer Kerncodelogik und ihres Aussehens ermöglichen.
Genau wie Wände. Die Logik ist sehr trivial, aber das Rendern ist komplexer.
quelle
Ich stimme @Omnion zu. Verwenden Sie den Listenansatz, aber sortieren Sie ihn, wenn die Leistung ein Problem darstellt. Verwenden Sie also einen hybriden Ansatz. Verwenden Sie die Liste als dritte Dimension, identifizieren Sie jedoch nur die ersten 5 Elemente als einen bestimmten Typ. Danach handelt es sich um einen tinteneigenen Typ oder um einen Typ, der nicht mehrmals pro Frame überprüft werden muss.
quelle
Sie sollten "nie" separate Klassen für Dinge wie Kacheln erstellen. Sie sollten eine Struktur aus Bytes, Shorts und Ints erstellen, die sich auf den Typ der Kachel und der darauf befindlichen Objekte beziehen. Leistungsbezogene Ints sind am besten für 64-Bit-Systeme geeignet oder sogar lang. Wenn Sie Ihre Karte jedoch jemals speichern möchten, sollten Sie Bytes oder Shorts verwenden, wo immer Sie können, insbesondere bei großen Karten.
In meinen Spielen habe ich eine Kartenklasse, die nur Felder enthält wie: byte tileType; 256 verschiedene Geländetypen reichen für dieses Spiel aus. Nimmt 1 Byte. ushort tileObject; 65.536 verschiedene Objekte sind ausreichend. Nimmt 2 Bytes usw.
Wenn ich das obige Beispiel nehme, benötigt jede Kachel nur 3 Bytes im Speicher. Ein 10000x10000 wäre also 300 MB im Speicher und sollte kein Problem sein. Wann immer Sie nach etwas suchen müssen, bestimmen Sie, wo auf der Karte Sie suchen möchten und wonach Sie suchen, und iterieren die Arrays entsprechend.
Wenn Sie überall auf der Karte dynamische Dinge haben, fragen Sie sich, ob der Spieler diese Dinge wirklich weit außerhalb seines Ansichtsfensters bemerkt.
quelle