2015-01-26 14 views
6

Mam straszny czas wymyślić dobre pytanie Tytuł ... przepraszam/edytuj jeśli twój mózg jest mniej strzał niż mój.strategia dzielenia mapy, rechunk lag issue

Mam pewne problemy z obsługą strony map mojej gry. Moja gra jest oparta na kaflach z użyciem płytek 32x32 pikseli. Moja pierwsza mapa gry miała wymiary 1750 x 1750. Miałem grupę warstw po stronie klienta, ale udało mi się ją zredukować do 2 (ziemia i budynki). Poprzednio ładowałem całe warstwy mapy do pamięci (krótkie tablice). Kiedy przeskoczyłem do 2200 x 2200 płytek, zauważyłem, że starszy komputer ma problemy z brakującą pamięcią (1 GB +). Chciałbym, żeby był typ danych między bajtem i krótkim (zamierzam na ~ 1000 różnych płytek). Moja gra obsługuje wiele rozdzielczości, więc widzowie mogą wyświetlać 23,17 kafli dla rozdzielczości 800x600, aż do 45,29 klocków dla rozdzielczości 1440x1024 (plus). Używam Kafelki do rysowania moich map i wysyłania 2 warstw do oddzielnych plików tekstowych przy użyciu formatu podobnego do następującego (0, 0, 2, 0, 3, 6, 0, 74, 2 ...) w jednym wierszu.

Za pomocą wielu pytań dotyczących SO i niektórych badań wymyśliłem strategię mapowania kawałków. Używając aktualnych współrzędnych gracza, jako punktu centralnego, ładuję wystarczającą ilość kafli na 5-krotność wielkości mapy wizualnej (największa to 45 * 5,29 * 5 = 225,145 płytek). Gracz zawsze znajduje się w środku, a ziemia porusza się pod nim (kiedy idziesz na wschód, ziemia porusza się na zachód). Minimapa jest rysowana, pokazując jeden ekran we wszystkich kierunkach, aby była trzy razy większa od widocznej mapy. Zobacz poniższą (bardzo pomniejszoną) reprezentację wizualną, aby wyjaśnić lepiej niż prawdopodobnie wyjaśniłem.

enter image description here

Mój problem jest taki: Gdy gracz porusza „1/5th płytki wielkości kawałek away” z oryginalnego punktu środkowego (chunkX/Y) koordynuje, wzywam do gry ponownie przeskanować plik . Nowy skan użyje bieżących współrzędnych gracza, ponieważ jest to punkt środkowy. Obecnie ten problem, który mam, polega na tym, że rechunking trwa jak .5s na moim komputerze (co jest dość wysokim spec). Mapa nie aktualizuje się tak jak 1-2 ruchy płytek.

Aby rozwiązać powyższy problem, spróbowałem uruchomić skanowanie pliku w nowym wątku (przed trafieniem w 1/5 punkt) w tymczasowego arachibra. Następnie, po zakończeniu skanowania, skopiowałbym bufor do prawdziwej tablicy i wywołał repaint(). Losowo widziałem kilka pomijanie problemów z tym, co było nic wielkiego. Co gorsza, zobaczyłem, jak losuje część mapy na 1-2 klatki. Przykładowy kod poniżej:

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; 
} 

Naprawdę jestem na tym wyczerpany. Chcę być w stanie przejść dalej poza tym gównem GUI i pracować nad mechaniką prawdziwej gry. Każda pomoc zostanie ogromnie doceniona. Przepraszam za długi post, ale zaufaj mi, że wiele myśli/nieprzespanych nocy weszło w to. Potrzebuję pomysłów ekspertów SO. Dzięki wielkie!!

p.s. Wpadłem niektórych potencjalnych pomysłów optymalizacyjnych (ale nie jestem pewien, to mogłoby rozwiązać niektóre z kwestii):

  • podzielić pliki map na wielu liniach, więc mogę zadzwonić scanner.nextLine() 1 raz, zamiast skanera .next() 2200 razy

  • wymyślić formułę, która, biorąc pod uwagę 4 rogi kawałka mapy, będzie wiedzieć, czy dana współrzędna jest w nim zawarta. to pozwoliłoby mi wywołać metodę scanner.nextLine(), gdy znajduje się w najdalszym punkcie fragmentu dla danej linii. wymagałoby to powyższego podejścia do plików map wielowierszowych.

  • rzut zaledwie 1/5th na fragmencie, przesunąć tablicę i załadować następny 1/5th z kawałkiem

Odpowiedz

1

upewnić, skanowanie pliku zakończy przed rozpoczęciem nowego skanowania.

Obecnie można rozpocząć skanowanie ponownie (prawdopodobnie w każdej ramce), podczas gdy centrum jest zbyt daleko od poprzedniego centrum skanowania. Aby to naprawić, pamiętaj, że skanujesz , zanim jeszcze zaczniesz i odpowiednio poprawisz swój stan daleko.

// 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(); 
} 

Zapobieganie ponowne skanowanie zanim poprzedni rescan zakończyła prezentuje kolejne wyzwanie: co zrobić, gdy idziesz po przekątnej, czyli do osiągnięcia sytuacji, w której w x jesteś obrad dala warunku rozpocząć skanowanie i podczas tego skanowania spełnisz warunek dla y, aby być daleko. Ponieważ wybierasz następne centrum skanowania zgodnie z aktualną pozycją, problem ten nie powinien występować, dopóki masz wystarczająco duży rozmiar porcji.

Pamiętanie o pracowniku bezpośrednio wiąże się z premią: co zrobić, jeśli chcesz teleportować odtwarzacz/kamerę w pewnym momencie podczas skanowania? Możesz teraz po prostu zakończyć wątek roboczy i rozpocząć skanowanie w nowej lokalizacji: będziesz musiał ręcznie zaznaczyć flagę zakończenia w MyClient.FillGroundBuffer i MyClient.FillBuildingBuffer, odrzucić (częściowo wyliczony) wynik w Runnable i zatrzymać resetowanie MyClient.worker w przypadku Przerwij.

Jeśli chcesz przesłać więcej danych z systemu plików w grze, pomyśl o wdrożeniu usługi przesyłania strumieniowego (rozszerz koncepcję pracownika na taką, która przetwarza arbitralne zadania analizowania plików). Powinieneś również sprawdzić, czy twój dysk twardy jest zdolny do wykonywania odczytu z wielu plików jednocześnie szybciej niż czytanie pojedynczego strumienia z jednego pliku.

Włączenie formatu pliku binarnego jest opcją, ale nie pozwoli zaoszczędzić wiele pod względem rozmiaru pliku. A ponieważ Scanner już używa wewnętrznego bufora do wykonania analizy (parsowanie liczb całkowitych z bufora jest szybsze niż wypełnianie bufora z pliku), powinieneś najpierw skupić się na optymalnym uruchomieniu swojego pracownika.

+0

OMG nadszedł dzień (pracował nad tym przez kilka tygodni) ... to działa jak czar. Byłem więc bardzo blisko, co mnie uszczęśliwia - po prostu spamowanie jest jak szalone. Dziękuję bardzo za pomoc. Przyznaje nagrodę, kiedy mi pozwala. Jeśli jesteś ciekawy, pomogłeś "Kisnard Online" :). Mam nadzieję, że przyjdziesz zagrać jeden dzień i doświadczysz buforowania po stronie klienta w akcji !! Mam zamiar wspomnieć o Tobie (lub Twojej stronie) w moich informacjach o wersji - daj mi znać, jeśli z jakiegoś powodu nie chcesz, abym nie zrobił tego. – KisnardOnline

+0

@KisnardOnline Dzięki, byłbym szczęśliwy, gdybym został uznany za "Lukasa Beyeler'a" i sprawdzi twoją grę, gdy tylko dostanę trochę czasu :) – BeyelerStudios

+0

Nagroda przyznana za Bounty. Będą w notatkach do wydania następnej łatki: D Jeszcze raz dziękuję Lukas. Teraz, gdy mam tę poprawkę do sfinalizowania, będę reklamować więcej. Mam nadzieję, że po dołączeniu do gry są jacyś gracze :) – KisnardOnline

0

Spróbuj przyspieszyć szybkość odczytu, używając pliku binarnego zamiast pliku csv. Użyj do tego celu DataInputStream i readShort(). (Spowoduje to również zmniejszenie rozmiaru mapy).

Można również użyć fragmentów płytek 32x32 i zapisać je w kilku plikach. Nie musisz ładować płytek, które są już załadowane.