Kis*_*ine 6 java memory-management
我有一个可怕的时间提出一个很好的问题标题...对不起/请编辑,如果你的大脑比我的大脑少.
我在处理我的游戏地图客户端方面遇到了一些问题.我的游戏是基于平铺的,使用32x32像素图块.我的第一张游戏地图是1750 x 1750瓷砖.我有一堆层客户端,但设法将其减少到2(地面和建筑物).我之前将整个地图的图层加载到内存(短数组)中.当我跳到2200 x 2200瓷砖时,我注意到一台旧电脑存在内存不足(1GB +)的问题.我希望在byte和short之间有一个数据类型(我的目标是~1000个不同的tile).我的游戏支持多种分辨率,因此玩家可见空间可以显示23,17个区块,800x600分辨率,最高可达45,29个区块,1440x1024(加)分辨率.我使用Tiled绘制我的地图并使用类似于以下的格式将2层输出到单独的文本文件中(0,0,2,0,3,6,0,74,2 ......
在许多SO问题和一些研究的帮助下,我想出了一个地图分块策略.使用玩家的当前坐标作为中心点,我将足够的图块加载到视觉图的大小的5倍(最大的是45*5,29*5 = 225,145个图块).玩家总是被吸引到中心,地面在他/她下方移动(当你向东走时,地面向西移动).绘制小地图,在所有方向上显示一个屏幕,是可见地图大小的三倍.请参阅下面(非常缩小的)视觉表示来解释比我可能解释的更好.

我的问题是:当玩家从原始中心点(chunkX/Y)坐标移动"1/5块大小的瓷砖"时,我呼吁游戏重新扫描文件.新扫描将使用播放器的当前坐标作为其中心点.目前我遇到的这个问题是我的电脑上的重新设置需要.5s(这是相当高的规格).地图不会更新为1-2平铺移动.
为了解决上面的问题,我尝试在一个新线程中运行文件扫描(在点击1/5点之前)到一个临时数组缓冲区.然后,一旦完成扫描,我会将缓冲区复制到真实数组中,并调用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;
}
Run Code Online (Sandbox Code Playgroud)
我真的很满意这个.我希望能够继续使用这个GUI废话并开发真正的游戏机制.任何帮助将非常感激.很抱歉这篇长篇文章,但相信我很多想法/不眠之夜已经进入了这个.我需要SO专家的想法.非常感谢!!
ps我想出了一些潜在的优化想法(但不确定这些会解决一些问题):
将地图文件拆分成多行,这样我就可以调用scanner.nextLine()1次,而不是scan.next()2200次
得出一个公式,给定地图的四个角,块将知道给定的坐标是否位于其中.这将允许我在给定行的块的最远点处调用scanner.nextLine().这将需要上面的多线地图文件方法.
在开始新扫描之前,请确保扫描文件已完成。
目前,当您的中心距离上一个扫描中心太远时,您将再次开始扫描(可能在每一帧中)。要解决此问题,请记住您在开始之前就进行了扫描,并相应地增强了远程条件。
// 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();
}
Run Code Online (Sandbox Code Playgroud)
在上一次重新扫描完成之前防止重新扫描提出了另一个挑战:如果您沿对角线行走,即您达到了x满足远处条件的情况,则开始扫描,并且在该扫描期间您将满足以下条件该y怎么办:离得远一点。由于您根据当前位置选择下一个扫描中心,因此只要您有足够大的块大小,就不会出现此问题。
直接记住工作人员会带来一个好处:如果您在扫描时需要在某个时刻传送播放器/摄像机,您该怎么办?现在,您可以简单地终止工作线程并在新位置开始扫描:您必须在MyClient.FillGroundBuffer和中手动检查终止标志MyClient.FillBuildingBuffer,拒绝 中的(部分计算的)结果,并在中止的情况下Runnable停止重置。MyClient.worker
如果您需要从游戏中的文件系统传输更多数据,请考虑实现流服务(将工作线程的概念扩展到处理任意文件解析作业的服务)。您还应该检查您的硬盘驱动器是否能够同时读取多个文件,而不是比从单个文件读取单个流更快。
转向二进制文件格式是一种选择,但不会在文件大小方面节省太多。由于Scanner已经使用内部缓冲区进行解析(从缓冲区解析整数比从文件填充缓冲区更快),因此您应该首先专注于让工作程序以最佳方式运行。