2015-01-26 3 views
6

Ich habe eine schreckliche Zeit mit einer guten Frage Titel ... Entschuldigung/bitte bearbeiten, wenn Ihr Gehirn ist weniger geschossen als meins.Karte Chunking Strategie, Rechunk Lag Ausgabe

Ich habe einige Probleme beim Umgang mit der Kartenseite meines Spiels. Mein Spiel basiert auf Kacheln mit 32x32 Pixel Kacheln. Meine erste Spielkarte war 1750 x 1750 Spielsteine. Ich hatte eine Reihe von Schichten Client-Seite, aber es geschafft, es auf 2 (Boden und Gebäude) zu schneiden. Ich habe zuvor die gesamten Layer der Karte in den Speicher geladen (kurze Arrays). Als ich zu 2200 x 2200 Kacheln sprang, bemerkte ich einen älteren PC mit einigen Problemen mit nicht genügend Speicher (1GB +). Ich wünschte, es gäbe einen Datentyp zwischen Byte und Kurz (ich ziele auf ~ 1000 verschiedene Kacheln). Mein Spiel unterstützt mehrere Auflösungen, so dass der sichtbare Platz des Spielers 23,17 Kacheln für eine 800x600 Auflösung bis hin zu 45,29 Kacheln für 1440x1024 (plus) Auflösungen anzeigen kann. Ich verwende Tiled, um meine Karten zu zeichnen und die 2 Ebenen in separate Textdateien auszugeben, die ein ähnliches Format haben wie die folgenden (0, 0, 2, 0, 3, 6, 0, 74, 2 ...) alle in einer Zeile.

Mit Hilfe von vielen SO Fragen und einigen Recherchen habe ich eine Map Chunking Strategie entwickelt. Unter Verwendung der aktuellen Koordinaten des Spielers als Mittelpunkt lade ich genügend Spielsteine ​​für die fünffache Größe der visuellen Karte (die größte wäre 45 * 5,29 * 5 = 225, 145 Spielsteine). Der Spieler wird immer in die Mitte gezogen und der Boden bewegt sich unter ihm (wenn Sie nach Osten gehen, bewegt sich der Boden nach Westen). Die Minikarte wird so gezeichnet, dass sie einen Bildschirm in alle Richtungen zeigt, der dreimal so groß ist wie die sichtbare Karte. Bitte sehen Sie sich die folgende (sehr verkleinerte) visuelle Darstellung an, um besser zu erklären, als ich es wahrscheinlich erklärt habe.

enter image description here

Mein Problem ist folgendes: Wenn der Spieler bewegt sich „1/5. die Chunkgröße Fliesen entfernt“ von dem ursprünglichen Mittelpunkt (chunkX/Y) Koordinaten, rufe ich für das Spiel scannen Sie die Datei erneut . Der neue Scan verwendet die aktuellen Koordinaten des Players als Mittelpunkt. Derzeit ist dieses Problem, das ich habe, das Rechkning dauert wie .5s auf meinem PC (was ziemlich hohe Spezifikation ist). Die Karte wird nicht für 1-2 Kachelzüge aktualisiert.

Um das oben genannte Problem zu bekämpfen, habe ich versucht, die Datei in einem neuen Thread (vor dem 1/5-Punkt) in einen temporären Array-Puffer zu scannen. Nach dem Scannen würde ich den Puffer in das reale Array kopieren und repaint() aufrufen. Zufällig sah ich einige Skipping-Probleme mit diesem, was keine große Sache war. Schlimmer noch, ich sah es einen zufälligen Teil der Karte für 1-2 Frames zeichnen. Codebeispiel unten:

private void checkIfWithinAndPossiblyReloadChunkMap(){ 
    if (Math.abs(MyClient.characterX - MyClient.chunkX) + 10 > (MyClient.chunkWidth/5)){ //arbitrary number away (10) 
     Runnable myRunnable = new Runnable(){ 
      public void run(){ 
       logger.info("FillMapChunkBuffer started."); 

       short chunkXBuffer = MyClient.characterX; 
       short chunkYBuffer = MyClient.characterY; 

       int topLeftChunkIndex = MyClient.characterX - (MyClient.chunkWidth/2) + ((MyClient.characterY - (MyClient.chunkHeight/2)) * MyClient.mapWidth); //get top left coordinate of chunk 
       int topRightChunkIndex = topLeftChunkIndex + MyClient.chunkWidth - 1; //top right coordinate of chunk 

       int[] leftChunkSides = new int[MyClient.chunkHeight]; 
       int[] rightChunkSides = new int[MyClient.chunkHeight]; 

       for (int i = 0; i < MyClient.chunkHeight; i++){ //figure out the left and right index points for the chunk 
        leftChunkSides[i] = topLeftChunkIndex + (MyClient.mapWidth * i); 
        rightChunkSides[i] = topRightChunkIndex + (MyClient.mapWidth * i); 
       } 

       MyClient.groundLayerBuffer = MyClient.FillGroundBuffer(leftChunkSides, rightChunkSides); 
       MyClient.buildingLayerBuffer = MyClient.FillBuildingBuffer(leftChunkSides, rightChunkSides); 

       MyClient.groundLayer = MyClient.groundLayerBuffer; 
       MyClient.buildingLayer = MyClient.buildingLayerBuffer; 
       MyClient.chunkX = chunkXBuffer; 
       MyClient.chunkY = chunkYBuffer; 

       MyClient.gamePanel.repaint(); 

       logger.info("FillMapChunkBuffer done."); 
      } 
     }; 
     Thread thread = new Thread(myRunnable); 
     thread.start(); 
    } else if (Math.abs(MyClient.characterY - MyClient.chunkY) + 10 > (MyClient.chunkHeight/5)){ //arbitrary number away (10) 
     //same code as above for Y 
    } 
} 

public static short[] FillGroundBuffer(int[] leftChunkSides, int[] rightChunkSides){ 
    try { 
     return scanMapFile("res/images/tiles/MyFirstMap-ground-p.json", leftChunkSides, rightChunkSides); 
    } catch (FileNotFoundException e) { 
     logger.fatal("ReadMapFile(ground)", e); 
     JOptionPane.showMessageDialog(theDesktop, getStringChecked("message_file_locks") + "\n\n" + e.getMessage(), getStringChecked("message_error"), JOptionPane.ERROR_MESSAGE); 
     System.exit(1); 
    } 
    return null; 
} 

private static short[] scanMapFile(String path, int[] leftChunkSides, int[] rightChunkSides) throws FileNotFoundException { 
    Scanner scanner = new Scanner(new File(path)); 
    scanner.useDelimiter(", "); 

    int topLeftChunkIndex = leftChunkSides[0]; 
    int bottomRightChunkIndex = rightChunkSides[rightChunkSides.length - 1]; 

    short[] tmpMap = new short[chunkWidth * chunkHeight]; 
    int count = 0; 
    int arrayIndex = 0; 

    while(scanner.hasNext()){ 
     if (count >= topLeftChunkIndex && count <= bottomRightChunkIndex){ //within or outside (east and west) of map chunk 
      if (count == bottomRightChunkIndex){ //last entry 
       tmpMap[arrayIndex] = scanner.nextShort(); 
       break; 
      } else { //not last entry 
       if (isInsideMapChunk(count, leftChunkSides, rightChunkSides)){ 
        tmpMap[arrayIndex] = scanner.nextShort(); 
        arrayIndex++; 
       } else { 
        scanner.nextShort(); 
       } 
      } 
     } else { 
      scanner.nextShort(); 
     } 

     count++; 
    } 

    scanner.close(); 
    return tmpMap; 
} 

Ich bin wirklich am Ende meiner Weisheit damit. Ich möchte in der Lage sein, an diesem GUI-Mist vorbeizukommen und an echten Spielmechaniken zu arbeiten. Jede Hilfe würde sehr geschätzt werden. Sorry für die lange Post, aber glaub mir eine Menge Gedanken/schlaflose Nächte ist in diese gegangen. Ich brauche die Ideen der SO-Experten. Vielen Dank!!

p.s. Ich kam mit einigen potenziellen Optimierung Ideen (aber nicht sicher, ob diese würden einen Teil des Problems lösen):

  • teilten Sie die Map-Dateien in mehrere Zeilen, so kann ich nennen scanner.nextLine() 1 Mal, anstatt Scanner .next() 2200 mal

  • kommen Sie mit einer Formel, dass die gegebenen 4 Ecken der Karte Chunk wird wissen, ob eine gegebene Koordinate darin liegt. Dies würde es mir ermöglichen, scanner.nextLine() aufzurufen, wenn am weitesten entfernten Punkt für eine gegebene Zeile. Dies würde die obige Vorgehensweise mit der mehrzeiligen Kartendatei erfordern.

  • Wurf nur 1/5 des Blocks entfernt, verschieben das Array, und legen Sie die nächste 1/5 des Chunks

Antwort

1

sicher, dass die Datei scannen Stellen beendet, bevor Sie einen neuen Scan starten.

Momentan fangen Sie wieder an zu scannen (möglicherweise in jedem Frame), während Ihr Zentrum zu weit vom vorherigen Scan-Center entfernt ist. Um das zu beheben, erinnern Sie sich, dass Sie scannen, bevor Sie sogar starten und Ihre weit entfernte Bedingung dementsprechend erhöhen.

// MyClient.worker represents the currently running worker thread (if any) 
if(far away condition && MyClient.worker == null) { 
    Runnable myRunnable = new Runnable() { 
     public void run(){ 
      logger.info("FillMapChunkBuffer started."); 

      try { 
       short chunkXBuffer = MyClient.nextChunkX; 
       short chunkYBuffer = MyClient.nextChunkY; 

       int topLeftChunkIndex = MyClient.characterX - (MyClient.chunkWidth/2) + ((MyClient.characterY - (MyClient.chunkHeight/2)) * MyClient.mapWidth); //get top left coordinate of chunk 
       int topRightChunkIndex = topLeftChunkIndex + MyClient.chunkWidth - 1; //top right coordinate of chunk 

       int[] leftChunkSides = new int[MyClient.chunkHeight]; 
       int[] rightChunkSides = new int[MyClient.chunkHeight]; 

       for (int i = 0; i < MyClient.chunkHeight; i++){ //figure out the left and right index points for the chunk 
        leftChunkSides[i] = topLeftChunkIndex + (MyClient.mapWidth * i); 
        rightChunkSides[i] = topRightChunkIndex + (MyClient.mapWidth * i); 
       } 

       // no reason for them to be a member of MyClient 
       short[] groundLayerBuffer = MyClient.FillGroundBuffer(leftChunkSides, rightChunkSides); 
       short[] buildingLayerBuffer = MyClient.FillBuildingBuffer(leftChunkSides, rightChunkSides); 


       MyClient.groundLayer = groundLayerBuffer; 
       MyClient.buildingLayer = buildingLayerBuffer; 
       MyClient.chunkX = chunkXBuffer; 
       MyClient.chunkY = chunkYBuffer; 
       MyClient.gamePanel.repaint(); 
       logger.info("FillMapChunkBuffer done."); 
      } finally { 
       // in any case clear the worker thread 
       MyClient.worker = null; 
      } 
     } 
    }; 

    // remember that we're currently scanning by remembering the worker directly 
    MyClient.worker = new Thread(myRunnable); 
    // start worker 
    MyClient.worker.start(); 
} 

Verhindern eine erneute Prüfung, bevor die vorherigen Rescan abgeschlossen stellt eine weitere Herausforderung: Was, wenn Sie schräg gehen zu tun, dh man die Situation erreichen, wo in x Sie weit weg Zustand sind Treffen, das Scannen starten und während dieser Scan Sie werden die Bedingung für y erfüllen, um weit weg zu sein. Da Sie das nächste Scan-Center entsprechend Ihrer aktuellen Position auswählen, sollte dieses Problem nicht auftreten, solange Sie eine ausreichend große Chunk-Größe haben.

Sich an den Arbeiter zu erinnern kommt direkt mit einem Bonus: Was machen Sie, wenn Sie den Player/die Kamera während des Scannens irgendwann teleportieren müssen? Sie können nun den Worker-Thread einfach beenden und am neuen Standort scannen: Sie müssen das Beendigungsflag manuell in MyClient.FillGroundBuffer und MyClient.FillBuildingBuffer überprüfen, das (teilweise berechnete) Ergebnis im Runnable ablehnen und das Zurücksetzen von MyClient.worker im Falle von ein Abbruch.

Wenn Sie mehr Daten aus dem Dateisystem in Ihrem Spiel streamen müssen, denken Sie daran, einen Streamingdienst zu implementieren (erweitern Sie die Idee des Arbeiters zu einer, die beliebige Dateiparsing-Aufträge verarbeitet). Sie sollten auch überprüfen, ob Ihre Festplatte das Lesen mehrerer Dateien gleichzeitig schneller durchführen kann als das Lesen eines einzelnen Streams aus einer einzelnen Datei.

Die Umstellung auf ein binäres Dateiformat ist eine Option, spart aber nicht viel in Bezug auf die Dateigröße. Und da Scanner bereits einen internen Puffer zum Parsen verwendet (das Analysieren von Ganzzahlen aus einem Puffer ist schneller als das Füllen eines Puffers aus einer Datei), sollten Sie sich zunächst darauf konzentrieren, Ihren Worker optimal laufen zu lassen.

+0

OMG der Tag ist gekommen (seit Wochen daran gearbeitet) ... das funktioniert wie ein Zauber. Also war ich wirklich nah dran, was mich glücklich macht - Spams wie verrückt reinzuschicken. Vielen Dank für die Hilfe. Wird das Kopfgeld vergeben, wenn es mich lässt. Wenn Sie neugierig sind, haben Sie 'Kisnard Online' geholfen :). Hoffe, du wirst eines Tages spielen und deine schicke Klientel in Aktion erleben! Ich plane, Sie (oder Ihre Site) in meinen Release Notes zu erwähnen - lassen Sie mich wissen, wenn Sie aus irgendeinem Grund nicht möchten. – KisnardOnline

+0

@KisnardOnline Thanks Ich würde mich freuen, als 'Lukas Beyeler' bezeichnet zu werden und werde dein Spiel checken, sobald ich etwas Zeit habe :) – BeyelerStudios

+0

Bounty ausgezeichnet und wird es tun. Wird in den Release Notes für den nächsten Patch sein: D Danke nochmal Lukas. Jetzt, wo ich diesen Fix fertigstellen kann, werde ich mehr Werbung machen. Hoffentlich gibt es einige Spieler, wenn du beitrittst :) – KisnardOnline

0

Versuchen Sie, die Lesegeschwindigkeit zu beschleunigen, indem Sie eine binäre Datei anstelle einer CSV-Datei verwenden. Verwenden Sie DataInputStream und readShort() dafür. (Dadurch wird auch die Größe der Karte reduziert.)

Sie können auch 32x32-Bausteine ​​verwenden und sie in mehreren Dateien speichern. Sie müssen also die bereits geladenen Kacheln nicht laden.