有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java映射分块策略,rechunk滞后问题

我很难想出一个好问题的标题。。。抱歉/如果你的大脑比我的少,请编辑

我在处理游戏地图客户端时遇到了一些问题。我的游戏是基于32x32像素瓷砖的。我的第一张游戏地图是1750 x 1750瓷砖。我在客户端有很多层,但设法把它减少到2层(地面和建筑物)。我之前将整个地图的图层加载到内存中(短数组)。当我跳到2200 x 2200块时,我注意到一台旧电脑内存不足(1GB+)。我希望字节和短字符之间有一个数据类型(我的目标是~1000个不同的分片)。我的游戏支持多种分辨率,因此玩家的可见空间可以显示23,17个瓷砖,分辨率为800x600,最高可达45,29个瓷砖,分辨率为1440x1024(加上)。我使用Tiled绘制地图,并使用类似于以下格式(0、0、2、0、3、6、0、74、2…)将这两个图层输出到单独的文本文件中都在一条线上

在很多问题和一些研究的帮助下,我想出了一个地图组块策略。使用玩家的当前坐标作为中心点,我加载了足够5倍于视觉地图大小的瓷砖(最大值为45*5,29*5=225145瓷砖)。玩家总是被画在中间,地面在他/她下方移动(当你向东走时,地面会向西移动)。绘制的小地图显示了一个屏幕之外的所有方向,大小是可见地图的三倍。请看下面(非常缩小)的视觉表现,以更好地解释我可能解释了它

enter image description here

我的问题是:当玩家从原始中心点(chunkX/Y)坐标移动“块大小的1/5块”时,我要求游戏重新扫描文件。新的扫描将使用玩家的当前坐标作为中心点。目前我遇到的问题是,在我的电脑上进行重新调整需要大约0.5秒(这是相当高的规格)。贴图不会更新1-2次瓷砖移动

为了解决上述问题,我尝试在一个新线程中(在达到1/5点之前)将文件扫描到一个临时的arraybuffer中。扫描完成后,我会将缓冲区复制到实际数组中,并调用repaint()。随机地,我看到了一些跳过的问题,这没什么大不了的。更糟糕的是,我看到它在地图上随机画了1-2帧。代码示例如下:

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

我真是束手无策。我希望能够克服这个GUI的垃圾,研究真正的游戏机制。任何帮助都将不胜感激。抱歉发了这么长的帖子,但相信我,这里面有很多思考/不眠之夜。我需要专家的建议。非常感谢

另外,我提出了一些潜在的优化想法(但不确定这些想法能否解决一些问题):

  • 将地图文件拆分为多行,这样我就可以调用scanner了。nextLine()1次,而不是扫描仪。next()2200次

  • 给出一个公式,给定地图块的4个角,就可以知道给定的坐标是否在其中。这可以让我给扫描器打电话。nextLine()位于给定行块上的最远点。这将需要上述多行映射文件方法

  • 只扔掉1/5的区块,移动数组,然后加载下1/5的区块

共 (2) 个答案

  1. # 1 楼答案

    尝试使用二进制文件而不是csv文件来加快读取速度。 用^{}^{}来表示。(这也将缩小地图的大小。)

    您还可以使用32x32分块并将其保存到多个文件中。 所以你不必加载已经加载的瓷砖

  2. # 2 楼答案

    在开始新的扫描之前,确保已完成文件扫描

    当前,当你的中心距离前一个扫描中心太远时,你将再次开始扫描(可能在每一帧中)。要解决这个问题,请记住,在你开始之前,你正在扫描,并相应地增强你的远行状态

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

    在上一次重新扫描完成之前防止重新扫描会带来另一个挑战:如果你沿着对角线走,即你达到了x你满足了遥远的条件,开始扫描,在扫描过程中,你将满足y远离的条件。由于您根据当前位置选择下一个扫描中心,因此只要您有足够大的块大小,就不会出现此问题

    直接记住工作人员还有一个好处:如果在扫描过程中需要在某个时间点传送播放器/相机,你会怎么做?现在,您只需终止工作线程并在新位置开始扫描:您必须在MyClient.FillGroundBufferMyClient.FillBuildingBuffer中手动检查终止标志,在Runnable中拒绝(部分计算)结果,并在中止时停止MyClient.worker的重置

    如果需要在游戏中从文件系统中传输更多数据,可以考虑实现流式服务(将工作者的概念扩展到处理任意文件解析作业的工作者)。您还应该检查硬盘驱动器是否能够以比从单个文件读取单个流更快的速度同时读取多个文件

    转向二进制文件格式是一种选择,但在文件大小方面不会节省太多。而且由于Scanner已经使用内部缓冲区来进行解析(从缓冲区解析整数比从文件填充缓冲区更快),所以您应该首先关注让工作程序以最佳方式运行