Card breakdown strategy, re-lag problem

I have a terrible time, suitable for a good question. Title ... sorry / edit if your brain has less shot than mine.

I'm having some problems processing my game cards on the client side. My game is based on using 32x32 pixel tiles. My first game card was 1750 x 1750 tiles. I had a bunch of layers on the client side, but I managed to reduce it to 2 (land and buildings). I previously loaded all map layers into memory (short arrays). When I jumped to 2200 x 2200 tiles, I noticed that the old computer was having problems with out of memory (1 GB +). I would like to have a data type between byte and short (I am aiming for ~ 1000 different tiles). My game supports several resolutions, so the visible space of the players can display 23.17 tiles for 800x600 resolution up to 45.29 tiles for 1440x1024 resolutions (plus). I use Tiled to draw my maps and output two layers into separate text files, using a format similar to the following (0, 0, 2, 0, 3, 6, 0, 74, 2 ...) all on one line.

Through many SO questions and some research, I came up with a map splitting strategy. Using the playerโ€™s current coordinates as the center point, I load a sufficient number of tiles 5 times the size of the visual map (the largest will be 45 * 5.29 * 5 = 225,145 tiles). The player is always drawn in the center, and the earth moves below it (when you go east, the earth moves west). The minimum is drawn, showing one screen in all directions, three times the size of the visible map. Please see below (very reduced) visual presentation to explain better than I probably explained it.

enter image description here

My problem is this: when a player moves "1/5 fragment fragment size" from the origin coordinates of the center point (chunkX / Y), I challenge the game to re-view the file. A new scan will use the playerโ€™s current coordinates as the center point. Currently, this question that I have is interception, which takes about 5 seconds on my computer (this is a pretty high specification). The map is not updated for 1-2 tile steps.

To deal with the problem above, I tried to start scanning files in a new stream (before hitting the 1 / 5th point) into a temporary massive buffer. Then, as soon as this was done, I copied the buffer to the real array and called back (). In random order, I saw some problems with this, which did not really matter. Even worse, I saw him draw a random part of the map for 1-2 frames. Sample code below:

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

I really agree with that. I want to be able to move beyond this crap GUI and work on real game mechanics. Any help would be greatly appreciated. Sorry for the long post, but trust me, a lot of thoughts / sleepless nights went for it. I need ideas from SO experts. Thank you very much!

ps I came up with some potential optimization ideas (but not sure if this will solve part of the problem):

  • split map files into several lines, so that I can call scanner.nextLine () 1 time, and not scanner.next () 2200 times

  • come up with a formula that would give the 4 corners of the map fragment, will know if the given coordinate is in it. this would allow me to call scanner.nextLine () when at the farthest point on the piece for a given line. this will require a multi-line map file file above.

  • throw away only the 1/5th piece, move the array and load the next 1/5 part
+6
source share
2 answers

Before starting a scan, make sure the scan file is complete.

You will now start scanning again (perhaps in every frame), while your center is too far from the previous scan center. To fix this, remember that you scan before you begin , and accordingly improve your remote status.

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

Preventing rescanning before completing the previous rescanning is another problem: what to do if you go diagonally, i.e. you reach a situation where at x you meet a distant state, start a scan and during this scan you, ll fulfill the condition for y to be far away. Since you select the next center of scanning according to your current position, this problem should not occur provided that you have a sufficiently large block size.

Remember that the employee directly receives a bonus: what do you do if you need to teleport the player / camera at some point during the scan? Now you can just stop the workflow and start scanning in a new place: you will need to check the completion flag manually in MyClient.FillGroundBuffer and MyClient.FillBuildingBuffer , reject the (partially calculated) result in Runnable and stop reset MyClient.worker in case of interruption.

If you need to transfer more data from the file system in your game, consider introducing a streaming service (extend the idea of โ€‹โ€‹a worker to the point that it processes arbitrary files to parse files). You should also check if your hard drive can read from multiple files at the same time faster than reading a single stream from a single file.

Accessing a binary file is an option, but not saving a lot in terms of file size. And since Scanner already uses the internal buffer to parse it (parsing integers from the buffer is faster than filling the buffer from the file), you should first focus on making your work optimally.

+1
source

Try to speed up the reading speed by using a binary instead of a csv file. To do this, use DataInputStream and readShort() . (This will also reduce the size of the card.)

You can also use fragments of 32x32 fragments and save them in multiple files. This way you do not load already loaded tiles.

0
source

Source: https://habr.com/ru/post/981530/


All Articles