PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : 2 Probleme mit der eigenen Tilemap



Cornix
29.06.2010, 22:30
Schönen guten Abend,
erneut wage ich es die lieben Leute dieser Internet-Gemeinschaft mit bohrenden Fragen zu durchlöchern.

Dieses Mal habe ich mir Gedanken zu einer eigenen Umsetzung einer art Tilemap gemacht. Das Problem mit dem RMXP ist, dass ein einzelnes Tile der angezeigten Karte nicht einfach ausgetauscht werden kann, ganz zu schweigen von Auto-Tiles welche wohl noch viel unmöglicher ersetzt werden können.

Wie schon so oft zuvor ist es nicht direkt die Umsetzung mit welcher ich Probleme habe sondern interessieren mich an dieser Stelle Fragen zu Performance und Effektivität meiner kleinen Kreation.
Ich bitte hiermit um Kritik, Anregungen und Vorschläge zu folgenden beiden Problemstellungen:

1). Das Speichern der einzelnen Tiles.
Die Karte ist zweidimensional, jedes Tile soll eine eigene Instanz einer Klasse darstellen.
Um ein Tile zu speichern sind mir persönlich zwei Methoden eingefallen,
A). Ich erstelle ein Array (Als Zeile, für die X-Achse) in welchem ich weitere Arrays (jeweils für eine Spalte, Y-Achse) speichere.
Diese weiteren Arrays wiederum beinhalten die einzelnen Tiles.
Als Beispiel:

Tilemap = []
for i in 0...@map.width
row = []
for j in 0...@map.height
row.push(Tile.new)
end
Tilemap.push(row)
endAngesprochen wird das ganze wie folgt:

local_tile = Tilemap[x][y]und würde ein Tile liefern.

und B).
Ich erstelle ein einzelnes Array welches jedes Tile speichert.
Zusätzlich erstelle ich eine zweidimensionale Tabelle, in dieser Tabelle speichere ich, passend zu X- und Y-Koordinate den Index des zugehörigen Tiles innerhalb des Arrays.
Will ich also das Tile mit den Koordinaten (3,5) erhalten müsste ich folgendes tuen:

tile = Array[Table[3,5]]Da die Tabelle des RPG-Makers nur ganze Zahlen speichern kann.

Problem 2).
Um eigene Auto-Tiles richtig darstellen zu können muss ich die einzelnen 32*32 Sprites in vier gleiche Teile teilen.
Je nach der Umgebung des besagten Tiles muss jede 16*16 Pixel große Ecke passend verändert werden.
Ich dachte zuerst daran für jedes Tile ein neues Bitmap zu erstellen und anschließend einen Blocktransfer pro Ecke durch zu führen allerdings habe ich erst vor kurzem gelesen, dass der RPG-Maker seine Probleme damit besitzt falls zu viele Bitmaps erstellt werden. Grundsätzlich sei es sehr uneffektiv neue Bitmaps zu erstellen um diese dann mit dem Inhalt anderer Bitmaps zu füllen.

Die einzige Alternative welche ich dazu sehe, wäre es anstatt einem nun vier Sprites pro Tile zu verwenden.
Zugegebener Maßen würde ich dadurch kein einziges unnötiges Bitmap erstellen müssen allerdings würden sich meine Spritezahlen vervierfachen.
Was soll ich tuen?


Soweit dazu, vielen Dank für eure Aufmerksamkeit, ich freue mich auf jeden Kommentar.
Cornix.

Cornix
30.06.2010, 14:58
Sollte ich diese Frage vielleicht lieber in einem anderen Sub-Forum (http://www.multimediaxis.de/forumdisplay.php?f=91) stellen?

Entschuldigt den Doppelten Beitrag bitte.

MagicMagor
30.06.2010, 15:15
Zu 1)
Es gibt noch eine dritte Möglichkeit, die ich persönlich nutzen würde. Ein Eindimensoniales Array und das Umrechnen der Koordinaten.



tilemap[(y*breite)+x]

Damit würdest du dir die Unmengen an zusätzlichen Arrays bei der 2d-Array-Variante sparen. Solange deine Karten aber nicht plötzlich Dimensionen von um die 1000 Tiles annehmen sollte es wohl keinerlei großen Performance-Probleme kommen.

Generell, mach dir Gedanken um die Performance wenn es wirklich darauf ankommt, also wenn du merkst, daß es irgendwo im Spiel hakt.

Zu 2)
Ich nehme an du willst im Prinzip eine Tilemap haben, die es dir erlaubt dynamisch während des Spielverlaufs Tiles zu ändern, oder?
Im Prinzip benötigst du dafür nur 1 Sprite und 2 Bitmaps.
Das Sprite repräsentiert deine Karte, deine beiden Bitmaps sind das Bitmap im Sprite (die aktuelle Karte) und das Tileset. Die Tiles beinhalten dann einfach die Information von welchen SRC_RECT aus dem Tileset ein 32x32 Klumpen auf die Map geblittet werden soll.
Bei den Autotiles kannst du es ähnlich machen, nur das du anstatt der direkten Zeichen-Operationen du dir die Nachbartiles anguckst und dementsprechend entscheidest welche AutoTile-Kachel verwendet werden muss.
Damit hast du zwar etliche Blit-Operationen aber du erstellst und löschst nicht dauern Bilder und du musst diese Blit-Operationen nur durchführen wenn die Karte das erste mal geladen wird, oder du irgend eine Kachel änderst.

Ansonsten kannst du dir ja mal angucken wie Spriteset_Map arbeitet.

Cornix
30.06.2010, 15:25
Vielen Dank für die Antwort.

1). Diese Methode benutze ich derzeit.
Allerdings finde ich sie persönlich etwas "unsauber". Außerdem sind dadurch die Tiles am Rande des Bildschirms doppelt mit den Tiles über beziehungsweise unter ihnen verbunden, für eine richtige Abfrage würde ich dann eine Pufferfläche erstellen müssen.

2).
Genau dies war meine Frage, wie du es derzeit darstellst ist genau die Methode welche ich verwende. Allerdings wurde mir, wie oben bereits erwähnt, gesagt, dass es unvorteilhaft ist neue Bitmaps erstellen zu lassen und andauernde Blocktransfers durch zu führen.


Generell, mach dir Gedanken um die Performance wenn es wirklich darauf ankommt, also wenn du merkst, daß es irgendwo im Spiel hakt.
Das sehe ich leider anders, am liebsten wäre mir wenn ich nicht in der Mitte der Arbeit feststellen muss, dass ich meine Systeme vollständig neu schreiben muss da sie nicht effektiv genug sind.
Ich wüsste all diese Informationen am liebsten bevor ich anfange sie tatsächlich um zu setzen.

-KD-
30.06.2010, 21:45
Ich verstehe nicht ganz dein Problem. Du kannst die Tilemap im Spiel doch verändern, auch während sie angezeigt wird.


$game_map.data[x, y, z]
Gibt die die ID des Tiles im Tileset zurück, welche an der Position x/y im Layer z liegt. Du kannst diese ID natürlich auch neu setzen.

$game_map.data[x, y, z] = NEUE_ID

Das geht sowohl mit Autotiles, wie auch mit normalen Tiles.

Cornix
30.06.2010, 22:15
Ich verstehe nicht ganz dein Problem. Du kannst die Tilemap im Spiel doch verändern, auch während sie angezeigt wird.


$game_map.data[x, y, z]Gibt die die ID des Tiles im Tileset zurück, welche an der Position x/y im Layer z liegt. Du kannst diese ID natürlich auch neu setzen.

$game_map.data[x, y, z] = NEUE_IDDas geht sowohl mit Autotiles, wie auch mit normalen Tiles.
vielen Dank für die Information, das habe ich noch nicht gewusst, allerdings muss ich sagen, dass meine ersten Tests mit dieser Methode leider enttäuschend waren.
Ein bereits bestehendes Tile in ein Autotile umzuwandeln bringt leider nicht den gewünschten Erfolg. (Betrachtet bitte hierfür den angehängten Screenshot)
Das Autotile passt sich nicht der Umgebung an.

Außerdem dienen diese Fragen nicht nur ausschließlich praktischem Nutzen, ich interessiere mich auch sehr für die reine Theorie dahinter.
Ich würde einfach aus Prinzip nicht nur gerne etwas programmieren sondern auch etwas programmieren können.

-KD-
01.07.2010, 00:10
Autotiles werden mehr oder weniger wie normale Tiles behandelt. Du musst ihre Umgebung schon manuell anpassen. Es gibt afair auch Scripte, die das können. Wenn du es selbst machen willst, dann probier mal die verschiedenen Tile-IDs aus und untersuche den Mechanismus, der hinter der Erstellung der Autotiles steckt.


Außerdem dienen diese Fragen nicht nur ausschließlich praktischem Nutzen, ich interessiere mich auch sehr für die reine Theorie dahinter.
Ich würde einfach aus Prinzip nicht nur gerne etwas programmieren sondern auch etwas programmieren können. Aber jedes Tile als einzelnes Objekt mit eigenem Sprite ist aber keine Lösung. Zum einen werden in Ruby Objekte nicht gerade sonderlich effizient angelegt (vor allem in Ruby 1.8), zum anderen kommt auch die RGSS mit zu vielen Sprites nicht klar. Hier bietet sich die Vorgehensweise an, die die RGSS selbst intern vermutlicht verwendet:
- ein Bitmap pro Tile
- bei Autotiles wird für jede Kombinationsmöglichkeit ein Bitmap erzeugt (ist erstmal viel, aber man kann ja auch davon ausgehen, dass jede Möglichkeit mindestens einmal auf der Map gebraucht wird)
- die Tilemap wird aus Streifen von Sprites zusammengesetzt. Jeweils ein Streifen von 32 Pixeln Höhe wird per Blocktransfer aus den Bitmaps erzeugt und mit einem Sprite angezeigt. Du bräuchtest also für die Anzeige des Bildschirms 15 Streifen, dann aber nochmal für jeden Layer einen, also insgesamt 45 Sprites. Das ist noch vertretbar. Neue Blocktransfers müssen nur ausgeführt werden, wenn ein neuer Streifen in den Bildschirm rückt (und dann muss auch nur ein Streifen neu gezeichnet werden, die anderen rutschen einfach nach unten).
Dennoch wird die Performance dabei nicht berauschend sein.

Cornix
01.07.2010, 15:49
Ich benutze allerdings keine Bewegung die auf 32x32 Pixel Tiles aufbaut sondern pixelgenau ist. Sprich würde ich zumindest 16 Reihen (bei einem 640*480 standard) brauchen oder mehr je nachdem wie groß ich das Window erstellen lasse.

Würde es vielleicht besser gehen die gesamte Tilemap als einzelnen großen Sprite dar zu stellen und jeweils per Blocktransfer an diesem einen Bitmap zu arbeiten?

Wenn ein Tile dann gegen ein anderes ausgetauscht werden würde müsste ich lediglich für 9 Tiles, das heist eine 64*64 Pixelfläche einen Blocktransfer durchführen.

-KD-
01.07.2010, 19:55
Wenn die ganze Map ein großes Bitmap ist, bekommst du Probleme mit der Höhe/Tiefe der Events auf der Map. Ein Baum beispielsweise sollte für Events, die davor stehen, einen geringeren Z-Wert haben als für Events die dahinter stehen. Du kannst aber nicht einem Map-Layer mehrere Z-Werte zuordnen. Dafür müsstest du ihn schon in einzelne Streifen zerlegen.

Cornix
01.07.2010, 20:39
ja das stimmt, dabei hast du natürlich recht, ich vergas zu erwähnen, dass mich der Z-Wert der Tiles in meinem Project nicht interessiert, beziehungsweise sie gar keinen besitzen.

Nun, da ich diese neue Information nachgereicht habe, denkst du (oder auch andere Mitglieder welche unserer Unterhaltung folgen) dass die von mir beschriebene Methode die Performance verbessern würde?

-KD-
02.07.2010, 17:44
Ich denke wenn die Map ein riesiges Bitmap ist, müsste es recht effizient sein. Das kommt aber auf die Implementierung der RGSS an. Ich könnte mir z.B. vorstellen, dass die RGSS versucht ihre Bitmaps in der Grafikkarte zwischenzuspeichern. Und die hat nur begrenzt viel Arbeitsspeicher. Wenn das der Fall ist, könnte es bei Grafikkarten mit weniger Arbeitsspeicher effizienter sein, die Map als Mosaik einzelner großer Bitmaps (die durchaus auch größer als der Bildschirm sein können) darzustellen. Denn dann müssen im Ernstfall maximal 4 kleinere Bitmaps gleichzeitig angezeigt werden, was evtl. weniger Speicher kostet als ein großes. Aber ob dem wirklich so ist, musst du ausprobieren.

Cornix
03.07.2010, 00:26
Vielen Dank, völlig befriedigende Antwort darauf.

Nun noch einmal zu meiner ersten Frage, stimmts du zu, dass es die beste Methode sei ein einziges Array zu verwenden in welchem alle Tiles gespeichert sind? (Tile speichern in dieser Hinsicht nicht die Bitmaps oder stellen Sprites dar, sondern beinhalten weitere Spieltechnisch wichtige Informationen)
Eine Art, Zweidimensionales Array Marke Eigenbau?

-KD-
03.07.2010, 12:41
Ich persönlich finde ein eindimensionales Array eleganter. In einem Array aus Arrays ist der Zugriff vielleicht etwas einfacher, aber es lässt sich nicht so schön iterieren.
Die Frage ist eher, ob du wirklich ein Array aus Tile-Objekten verwenden solltest. Vor allem in Ruby 1.8 werden Objekte sehr ineffizient im Speicher abgelegt. Effizienter wären vermutlich einfach mehrere Arrays die jeweils ein Tile-Attribut enthalten. Am effizientesten ist es natürlich, wenn du die Klasse Table der RGSS nutzt. Mit der kannst du auch zweidimensionale Arrays hocheffizient abspeichern. Das Problem: Die Klasse darf nur Fixnum-Werte abspeichern. Du kannst also keine Strings oder andere Objekte darin hinterlegen.