Verwalten von Text-Maps in einem 2D-Array, die auf HTML5 Canvas gezeichnet werden sollen

46

Also mache ich ein HTML5-RPG nur zum Spaß. Die Karte ist eine <canvas>(512 Pixel breite, 352 Pixel hohe | 16 Kacheln breite, 11 Kacheln von oben nach unten). Ich möchte wissen, ob es eine effizientere Methode zum Malen gibt <canvas>.

So habe ich es gerade.

Wie Kacheln auf die Karte geladen und gemalt werden

Die Karte wird von Fliesen (32x32) mit dem Image()Stück gemalt . Die Bilddateien werden über eine einfache forSchleife geladen und in einem Array tiles[]abgelegt, das bei Verwendung von PAINTED aufgerufen wird drawImage().

Zuerst laden wir die Fliesen ...

Bildbeschreibung hier eingeben

und so wird's gemacht:

// SET UP THE & DRAW THE MAP TILES
tiles = [];
var loadedImagesCount = 0;
for (x = 0; x <= NUM_OF_TILES; x++) {
  var imageObj = new Image(); // new instance for each image
  imageObj.src = "js/tiles/t" + x + ".png";
  imageObj.onload = function () {
    console.log("Added tile ... " + loadedImagesCount);
    loadedImagesCount++;
    if (loadedImagesCount == NUM_OF_TILES) {
      // Onces all tiles are loaded ...
      // We paint the map
      for (y = 0; y <= 15; y++) {
        for (x = 0; x <= 10; x++) {
          theX = x * 32;
          theY = y * 32;
          context.drawImage(tiles[5], theY, theX, 32, 32);
        }
      }
    }
  };
  tiles.push(imageObj);
}

Wenn ein Spieler ein Spiel startet, lädt er natürlich die Karte, die er zuletzt verlassen hat. Aber für hier ist es eine reine Graskarte.

Derzeit verwenden die Karten 2D-Arrays. Hier ist eine Beispielkarte.

[[4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 1, 1, 1, 1], 
[1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 1, 1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 13, 13, 1, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 13, 13, 11, 11, 11, 13, 13, 13, 13, 13, 13, 13, 1], 
[13, 13, 13, 1, 1, 1, 1, 1, 1, 1, 13, 13, 13, 13, 13, 1], 
[1, 1, 1, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 1, 1, 1]];

Ich bekomme verschiedene Karten mit einer einfachen ifStruktur. Sobald das 2d-Array oben angezeigt wird return, wird die entsprechende Nummer in jedem Array entsprechend dem darin Image()gespeicherten gemalt tile[]. Dann drawImage()wird es passieren und malen nach dem xund yund mal indem 32man auf die richtige x-yKoordinate malt .

Wie mehrfache Kartenumschaltung erfolgt

Mit meinem Spiel Karten hat fünf Dinge im Auge zu behalten: currentID, leftID, rightID, upID, und bottomID.

  • currentID: Die aktuelle ID der Karte, auf der Sie sich befinden.
  • leftID: Welche ID currentIDsoll geladen werden, wenn Sie auf der linken Seite der aktuellen Karte beenden .
  • rightID: Welche ID currentIDsoll geladen werden, wenn Sie auf der rechten Seite der aktuellen Karte beenden .
  • downID: Welche ID currentIDsoll geladen werden, wenn Sie am unteren Rand der aktuellen Karte beenden .
  • upID: Welche ID currentIDsoll geladen werden, wenn Sie am oberen Rand der aktuellen Karte beenden .

Etwas zu Anmerkung: Wenn entweder leftID, rightID, upID, oder bottomIDist nicht spezifisch, das heißt sie ein sind 0. Das bedeutet, dass sie diese Seite der Karte nicht verlassen können. Es ist nur eine unsichtbare Blockade.

Sobald eine Person eine Seite der Karte verlässt, hängt es davon ab, wo sie sie verlassen hat. Wenn sie beispielsweise unten die Karte verlassen hat, bottomIDwird die Nummer der mapzu ladenden Person auf die Karte gezeichnet.

Hier ist ein gegenständliches .GIF, mit dem Sie sich besser vorstellen können:

Bildbeschreibung hier eingeben

Wie Sie sehen, beschäftige ich mich früher oder später mit vielen Karten mit vielen IDs. Und das kann möglicherweise etwas verwirrend und hektisch werden.

Die offensichtlichen Vorteile sind, dass 176 Kacheln gleichzeitig geladen, eine kleine 512x352-Zeichenfläche aktualisiert und jeweils eine Karte verarbeitet werden. Der Nachteil ist, dass die MAP-IDs beim Umgang mit vielen Maps manchmal verwirrend sein können.

Meine Frage

  • Ist dies eine effiziente Methode zum Speichern von Karten (bei Verwendung von Kacheln), oder gibt es eine bessere Methode zum Behandeln von Karten?

Ich dachte an eine riesige Karte. Die Karte ist groß und es ist alles ein 2D-Array. Das Ansichtsfenster hat jedoch immer noch eine Größe von 512 x 352 Pixel.

Hier ist eine weitere .gif-Datei, die ich (für diese Frage) erstellt habe, um sie zu visualisieren:

Bildbeschreibung hier eingeben

Entschuldigung, wenn Sie mein Englisch nicht verstehen können. Bitte fragen Sie alles, was Sie nicht verstehen können. Hoffentlich habe ich es klar gemacht. Vielen Dank.

Prüfung
quelle
16
Der Aufwand, der mit den Grafiken und allem in diese Frage gesteckt wird, verdient eine Aufwertung. :)
Tapio

Antworten:

5

Bearbeiten: Ich habe gerade gesehen, dass meine Antwort auf Ihrem Code basiert, aber Ihre Frage nicht wirklich beantwortet hat. Ich habe die alte Antwort behalten, falls Sie diese Informationen verwenden können.

Edit 2: Ich habe 2 Probleme mit dem Originalcode behoben: - Die +1-Addition in der Berechnung von Ende x und y war versehentlich in den Klammern, muss aber nach der Division hinzugefügt werden. - Ich habe vergessen, die x- und y-Werte zu validieren.

Sie könnten einfach die aktuelle Karte im Speicher haben und die umgebenden Karten laden. Stellen Sie sich eine Welt vor, die aus einem 2D-Array von einzelnen Ebenen der Größe 5x5 besteht. Der Spieler beginnt in Feld 1. Da sich die obere und die linke Grenze des Levels am Rand der Welt befinden, müssen sie nicht geladen werden. In diesem Fall ist Level 1/1 aktiv und Level 1/2 und 2/1 werden geladen ... Wenn der Spieler sich jetzt nach rechts bewegt, werden alle Level (außer dem, zu dem Sie sich bewegen) entladen und die neue Umgebung wird geladen geladen. Mittelstufe 2/1 ist jetzt aktiv, 1/1, 2/2 und 3/1 sind jetzt geladen.

Ich hoffe, das gibt Ihnen eine Idee, wie es gemacht werden könnte. Dieser Ansatz wird jedoch nicht sehr gut funktionieren, wenn jedes Level während des Spiels simuliert werden muss. Aber wenn Sie nicht verwendete Ebenen einfrieren können, sollte dies gut funktionieren.

Alte Antwort:

Beim Rendern der Ebene (auch kachelbasiert) berechne ich, welche Elemente aus dem Kachelarray das Ansichtsfenster schneiden, und rendere dann nur diese Kacheln. So können Sie große Karten haben, müssen aber nur den Teil auf dem Bildschirm rendern.

//Mock values
//camera position x
//viewport.x = 233f;
//camera position y
//viewport.y = 100f;
//viewport.w = 640
//viewport.h = 480
//levelWidth = 10
//levelHeight = 15
//tilesize = 32;

//startX defines the starting index for the x axis.
// 7 = 233 / 32
int startX = viewport.x / tilesize;

//startY defines the starting index
// 3 = 100 / 32
int startY = viewport.y / tilesize;

//End index y
// 28 = (233 + 640) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//End index y
// 19 = (100 + 480) / 32 + 1
int endX = ((viewport.x + viewport.w) / tilesize) + 1;

//Validation
if(startX < 0) startX = 0;
if(startY < 0) startY = 0;
//endX is set to 9 here
if(endX >= levelWidth) endX = levelWidth - 1;
//endX is set to 14 here
if(endY >= levelHeight) endY = levelHeight - 1;

for(int y = startY; y < yEnd; y++)
    for(int x = startX; x < xEnd; x++)
          [...]

Hinweis: Der obige Code ist nicht getestet, sollte aber eine Idee geben, was zu tun ist.

Nachstehend ein einfaches Beispiel für eine Ansichtsfensterdarstellung:

public class Viewport{
    public float x;
    public float y;
    public float w;
    public float h;
}

Eine Javascript-Darstellung würde so aussehen

<script type="text/javascript">
//Declaration
//Constructor
var Viewport = function(xVal, yVal, wVal, hVal){
    this.x = xVal;
    this.y = yVal;
    this.w = wVal;
    this.h = hVal;
}
//Prototypes
Viewport.prototype.x = null;
Viewport.prototype.y = null;
Viewport.prototype.w = null;
Viewport.prototype.h = null;
Viewport.prototype.toString = function(){
    return ["Position: (", this.x, "/" + this.y + "), Size: (", this.w, "/", this.h, ")"].join("");
}

//Usage
var viewport = new Viewport(23, 111, 640, 480);
//Alerts "Position: (23/111), Size: (640/480)
alert(viewport.toString());
</script>

Wenn Sie mein Codebeispiel nehmen, müssen Sie einfach die int- und float-Deklarationen durch var ersetzen. Stellen Sie außerdem sicher, dass Sie Math.floor für die Berechnungen verwenden, die den int-basierten Werten zugewiesen sind, um dasselbe Verhalten zu erzielen.

Eine Sache, die ich in Betracht ziehen würde (obwohl ich nicht sicher bin, ob dies in Javascript von Bedeutung ist), ist, alle Kacheln (oder so viele wie möglich) in einer großen Textur zu platzieren, anstatt viele einzelne Dateien zu verwenden.

Hier ist ein Link, der erklärt, wie es geht: http://thiscouldbebetter.wordpress.com/2012/02/25/slicing-an-image-into-tiles-in-javascript/

Tom van Green
quelle
Nur zur Verdeutlichung ... viewport.x würde als "531" und viewport.y als "352" und tile = "32" vorgegeben. Und derartige..?
Testen Sie den
Wenn Sie meinen, dass die Position der Kamera 531 ist, dann ja. Ich habe dem obigen Code einige Kommentare hinzugefügt.
Tom van Green
Ich benutze JavaScript ... Ich bin Java ist fast identisch mit diesem.
Testen Sie den
Ja, Sie müssen nur eine js-basierte Ansichtsfenster-Klasse erstellen (oder Sie können nur 4 Variablen x, y, w und h erstellen). Außerdem müssen Sie sicherstellen, dass Sie die Berechnungen, die den int-Werten zugewiesen sind, auf den Boden (Math.floor) setzen. Ich habe ein Codebeispiel hinzugefügt, wie die Viewport-Klasse aussehen würde. Stellen Sie nur sicher, dass Sie die Deklaration vor der Verwendung platzieren.
Tom van Green
Ich sage nur, dass Sie setzen 640, 480.. aber es kann 512, 352richtig funktionieren ?
Testen Sie den
3

Das Speichern von Karten als Kacheln ist nützlich, damit Sie Assets wiederverwenden und die Karte neu gestalten können. Realistisch gesehen bietet es dem Spieler nicht wirklich viel Nutzen. Außerdem zeichnen Sie jedes Mal eine neue Kachel. Folgendes würde ich tun:

Speichern Sie die gesamte Karte als ein großes Array. Es hat keinen Sinn, Karten und Submaps sowie potenzielle Sub-Sub-Karten zu haben (wenn Sie beispielsweise Gebäude betreten). Sie laden sowieso alles und das hält alles schön und einfach.

Rendern Sie die Karte auf einmal. Haben Sie eine Leinwand außerhalb des Bildschirms, auf der Sie die Karte rendern, und verwenden Sie diese dann in Ihrem Spiel. Diese Seite zeigt Ihnen, wie Sie dies mit einer einfachen Javascript-Funktion tun können:

var renderToCanvas = function (width, height, renderFunction) {
  var buffer = document.createElement('canvas');
  buffer.width = width;
  buffer.height = height;
  renderFunction(buffer.getContext('2d'));
  return buffer;
};

Dies setzt voraus, dass sich an Ihrer Karte nicht viel ändert. Wenn Sie Unterkarten an Ort und Stelle rendern möchten (z. B. das Innere eines Gebäudes), rendern Sie diese einfach auch auf einer Zeichenfläche und zeichnen sie dann oben auf. Hier ist ein Beispiel, wie Sie dies zum Rendern Ihrer Karte verwenden können:

var map = renderToCanvas( map_width*32, map_height*32, renderMap );

var MapSizeX = 512;
var MapSizeY = 352;

var draw = function(canvas, mapCentreX, mapCentreY) {
  var context = canvas.getContext('2d');

  var minX = playerX - MapSizeX/2;
  var minY = playerY - MapSizeY/2;

  context.drawImage(map,minX,minY,MapSizeX,MapSizeY,0,0,MapSizeX,MapSizeY);
}

Dadurch wird ein kleiner Teil der Karte zentriert gezeichnet mapCentreX, mapCentreY. Auf diese Weise können Sie reibungslos über die gesamte Karte scrollen und Bewegungen unter den Kacheln ausführen. Sie können die Position des Spielers als 1 / 32stel einer Kachel speichern, um eine reibungslose Bewegung über die Karte zu erzielen (ganz klar, wenn Sie möchten) Bewegen Sie Kacheln für Kacheln, um eine stilistische Auswahl zu treffen.

Sie können Ihre Karte nach wie vor zerlegen, wenn Sie möchten oder wenn Ihre Welt riesig ist und auf Ihren Zielsystemen nicht in den Arbeitsspeicher passt (denken Sie daran, dass eine 512 x 352-Karte selbst für 1 Byte pro Pixel 176 KB groß ist), aber vorab - Wenn Sie Ihre Karte so rendern, werden Sie in den meisten Browsern einen großen Leistungsgewinn feststellen .

Dies gibt Ihnen die Flexibilität, Kacheln auf der ganzen Welt wiederzuverwenden, ohne dass Sie ein großes Bild bearbeiten müssen. Sie können dies jedoch auch schnell und einfach ausführen und nur eine "Karte" (oder so viele "Karten", wie Sie möchten) berücksichtigen. .

Matt Kemp
quelle
2

In allen Implementierungen, die ich gesehen habe, werden Kachelkarten oft als ein großes Bild erzeugt und dann in kleinere Stücke "zerlegt".

https://gamedev.stackexchange.com/a/25035/10055

Diese Chunks haben normalerweise Potenzen von 2, sodass sie optimal in den Speicher passen.

Dustin Kingen
quelle
1

Eine einfache Möglichkeit, den Überblick über die IDs zu behalten, besteht darin, einfach ein anderes 2D-Array mit diesen IDs zu verwenden: Indem Sie nur x, y in diesem "Meta-Array" beibehalten, können Sie die anderen IDs problemlos nachschlagen.

Abgesehen davon ist hier Googles Version von 2D-Spielen auf Kacheln (nun ja, eigentlich HTML5-Multiplayer, aber die Kachel-Engine wird auch behandelt) aus dem letzten I / O 2012: http://www.youtube.com/watch?v=Prkyd5n0P7k It hat auch Quellcode zur Verfügung.

Tapio
quelle
1

Ihre Idee, die gesamte Karte zuerst in ein Array zu laden, ist ein effizienter Weg, aber es wird einfach sein, JavaScript Injection'd zu erhalten. Jeder kann die Karte kennen und los geht das Abenteuer.

Ich arbeite auch an einem Web-basierten RPG-Spiel. Ich lade meine Karte über Ajax von einer PHP-Seite, die die Karte aus der Datenbank lädt, in der die X-, Y-, Z-Position und der Wert angegeben sind Zeichnen Sie das Plättchen und bewegen Sie den Spieler.

Ich arbeite immer noch daran, es effizienter zu machen, aber die Karte wird schnell genug geladen, um es bis jetzt so zu halten.

BernaMariano
quelle
1
Kann ich eine Demo sehen?
Test
1

Nein , ein Array ist die effizienteste Methode zum Speichern einer Kachelkarte .

Es kann einige Strukturen geben, die etwas weniger Speicher benötigen, jedoch nur auf Kosten der wesentlich höheren Kosten für das Laden und den Zugriff auf die Daten.


Was jedoch berücksichtigt werden muss, ist, wann Sie die nächste Karte laden werden. Angesichts der Tatsache, dass die Karten nicht besonders groß sind, wird es am längsten dauern, bis der Server die Karte bereitstellt. Das eigentliche Laden dauert nur wenige Millisekunden. Aber dieses Warten kann asynchron erfolgen, es muss nicht den Spielfluss blockieren. Das Beste ist also, die Daten nicht bei Bedarf, sondern vorher zu laden. Der Spieler befindet sich in einer Karte, jetzt laden Sie alle angrenzenden Karten. Der Spieler bewegt sich in der nächsten Karte, lädt alle angrenzenden Karten erneut usw. usw. Der Spieler bemerkt nicht einmal, dass Ihr Spiel im Hintergrund geladen wird.

Auf diese Weise können Sie auch die Illusion einer Welt ohne Grenzen erzeugen, indem Sie auch die angrenzenden Karten zeichnen. In diesem Fall müssen Sie jedoch nicht nur die angrenzenden Karten, sondern auch die angrenzenden Karten laden.

API-Biest
quelle
0

Ich bin mir nicht sicher, warum Sie denken: Wie Sie sehen, werde ich früher oder später mit vielen Karten mit vielen Ausweisen zu tun haben. Und das kann möglicherweise etwas verwirrend und hektisch werden.

Die LeftID ist immer -1 der CurrentID, die RightID ist immer +1 der CurrentID.

Die UpID ist immer - (gesamte Kartenbreite) der aktuellen ID und die DownID ist immer + (gesamte Kartenbreite) der aktuellen ID, mit Ausnahme von Null, was bedeutet, dass Sie die Kante erreicht haben.

Eine einzelne Karte kann in mehrere Karten unterteilt werden und wird nacheinander mit mapIDXXXX gespeichert, wobei XXXX die ID ist. Auf diese Weise gibt es nichts zu verwechseln. Ich war auf Ihrer Website und es scheint, dass ich mich irren könnte, dass das Problem an Ihrem Karteneditor liegt, der eine Größenbeschränkung auferlegt, die Ihre Automatisierung daran hindert, eine große Karte beim Speichern in mehrere kleine zu zerlegen, und diese Einschränkung ist wahrscheinlich eine technische Lösung einschränken.

Ich habe einen Javascript-Karteneditor geschrieben, der in alle Richtungen scrollt, 1000 x 1000 (ich würde diese Größe nicht empfehlen) Kacheln und es bewegt sich immer noch so gut wie 50 x 50 Karten und das mit 3 Ebenen einschließlich Parallaxen-Scrolling. Es speichert und liest im json-Format. Ich sende Ihnen eine Kopie, wenn Sie sagen, dass Ihnen eine E-Mail von ca. 2meg nichts ausmacht. Es soll auf Github sein, aber ich habe kein anständiges (legales) Kachelset, weshalb ich mich noch nicht darum gekümmert habe, es dort anzubringen.

John
quelle