继续之前关于无限游戏世界和坐标数据结构的问题,我决定使用 Hashmap。然而,由于我在 Android 上尝试这个,我很快就遇到了问题。
以下是要求的回顾:
- 伪无限的 2D 瓷砖世界。世界将在所有 4 个方向 (2D) 的探索中生成,因此我们需要一些用于负索引的东西。
- 相邻的瓷砖等需要随机访问。
- 需要能够从特定坐标(如 get(x,y))检索图块
所以,这是我用来创建 600 * 600 瓦片的哈希图的代码片段。
每个地图图块的类:
public class MapTile {
public int type;
public MapTile(int type) {
this.type = type;
}
public boolean is(int type) {
return (type==this.type);
}
}
生成大地图的测试代码:
long startTime;
long lastTime;
map = new HashMap<Point, MapTile>();
int x;
int y;
int tile;
startTime = System.currentTimeMillis();
lastTime = startTime;
for (x=-300;x<=300;x++) {
for (y=-300;y<=300;y++) {
tile = (x+y)%2; //Just so I differentiate the tiles
this.map.put(new Point(x,y), new MapTile(tile));
}
// Logging each row
Log.d(this.getClass().toString(), "Generated x:" + x + " Time in ms:"+(System.currentTimeMillis() - lastTime));
lastTime = System.currentTimeMillis();
}
所以,当我在 Java 中运行类似的代码时。它成功完成(尽管会占用大量内存)。没问题。
但是,当我尝试在高端设备上的 Android 中执行此操作时,它实际上会在达到 x = 190 标记之前停止(通常)。垃圾收集器启动,然后出现无限循环:
** snip **
05-26 15:52:19.565 10758-10758/com.ayk.test04 D/class com.ayk.test04.WorldMap2D: Generated x:187 Time in ms:2
05-26 15:52:19.570 10758-10758/com.ayk.test04 D/class com.ayk.test04.WorldMap2D: Generated x:188 Time in ms:6
05-26 15:52:19.575 10758-10758/com.ayk.test04 D/class com.ayk.test04.WorldMap2D: Generated x:189 Time in ms:2
05-26 15:52:19.995 10758-10760/com.ayk.test04 I/dalvikvm-heap: Clamp target GC heap from 66.002MB to 64.000MB
05-26 15:52:19.995 10758-10760/com.ayk.test04 D/dalvikvm: GC_CONCURRENT freed 3K, 2% free 64797K/65543K, paused 4ms+30ms, total 429ms
05-26 15:52:19.995 10758-10758/com.ayk.test04 D/dalvikvm: WAIT_FOR_CONCURRENT_GC blocked 421ms
05-26 15:52:20.335 10758-10758/com.ayk.test04 I/dalvikvm-heap: Clamp target GC heap from 66.002MB to 64.000MB
05-26 15:52:20.335 10758-10758/com.ayk.test04 D/dalvikvm: GC_FOR_ALLOC freed 0K, 2% free 64797K/65543K, paused 341ms, total 341ms
05-26 15:52:20.335 10758-10758/com.ayk.test04 I/dalvikvm-heap: Clamp target GC heap from 64.002MB to 64.000MB
05-26 15:52:20.335 10758-10758/com.ayk.test04 I/dalvikvm-heap: Grow heap (frag case) to 64.000MB for 16-byte allocation
05-26 15:52:20.695 10758-10760/com.ayk.test04 I/dalvikvm-heap: Clamp target GC heap from 66.002MB to 64.000MB
05-26 15:52:20.695 10758-10760/com.ayk.test04 D/dalvikvm: GC_CONCURRENT freed 0K, 2% free 64797K/65543K, paused 12ms+2ms, total 358ms
05-26 15:52:20.695 10758-10758/com.ayk.test04 D/dalvikvm: WAIT_FOR_CONCURRENT_GC blocked 332ms
** thousands of these... snip **
所以,几乎,我被困在这里。
我的瓷砖现在只包含一个 int 变量。这是准系统。所以我明白了,从技术上讲,创建一个 50.000 个左右对象的 Hashmap 是不可能的?这对我来说似乎有点小。
我想尝试的事情是:
- 仅将地图的活动区域保留在内存中,而将其余部分卸载到存储中。虽然这可能会解决内存问题,但恐怕在访问超出活动区域的图块时可能会导致性能问题。
- 有人建议使用块(ala Minecraft)。所以我保留了哈希图的哈希图?它会起作用吗?如果是这样,我该怎么做?
- 我是否完全偏离目标并以错误的方式看待这个问题?
感谢您提供的任何帮助。
更新
再一次问好。在您的帮助下,我决定使用数据库。它的工作效率更高,到目前为止,我还没有遇到任何问题。对于后代,这是它的工作原理:
数据库
在 Android 中工作时,我设置了一个 SQLite 数据库和表来存储每个图块。起初,我决定在每次生成图块时运行插入查询。这很快就变成了另一个性能瓶颈,因为每个 INSERT 需要花费 2ms 多一点的时间才能完成,即使是最小的 300x300 切片生成过程也会受到影响。我不得不使用多个 INSERT 查询,该查询需要许多图块并使用单个查询存储它们。因此,我创建了一个“平铺缓冲区”,其中每个平铺都与其坐标一起存储:
//A linked list of integers is good enough.
//The first 2 are X and Y coordinates, the third one is the tile type.
//I can add more fields if needed.
this.tileDBbuffer = new LinkedList<Integer[]>();
以下是缓冲区的填充方式:
void addTileBuffer(int x,int y, MapTile map_tile) {
int maxsize = 10000;
Integer[] tile_data;
tile_data = new Integer[3];
tile_data[0] = x;
tile_data[1] = y;
tile_data[2] = map_tile.type;
if (this.tileDBbuffer.size()>= maxsize) {
Log.d(this.getClass().toString(), "Buffer filled up... Saving");
savemapbuffer();
}
this.tileDBbuffer.add(tile_data);
}
一旦缓冲区达到一定大小(或瓦片生成结束),“savemapbuffer()”函数就会运行,它基本上首先调用这个函数,然后清除缓冲区:
public void addTileBulk(LinkedList<Integer[]> map_tiles) {
Integer[] tile_data = new Integer[3];
if (db == null) db = this.getWritableDatabase();
String sql = "INSERT INTO "+TABLE_NAME_MAP+" ("+COLUMN_NAME_MAP_X+", "+COLUMN_NAME_MAP_Y+", "+COLUMN_NAME_MAP_TILE_TYPE+") VALUES (?,?,?)";
db.beginTransaction();
SQLiteStatement stmt = db.compileStatement(sql);
try {
while (!map_tiles.isEmpty()) {
tile_data = map_tiles.pop();
stmt.bindLong(1,tile_data[0]);
stmt.bindLong(2,tile_data[1]);
stmt.bindLong(3,tile_data[2]);
stmt.execute();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
map_tiles.clear();
}
使用中的哈希图
此后不久,绘制地图时出现了问题。绘图是在另一个线程中完成的,因此在生成新图块的同时迭代它会引发很多奇怪的错误。在使用“地图是否繁忙”布尔值进行了一些测试后,我最终使用了ConcurrentHashmap而不是常规的Hashmap。有效。
块
在那之后,我最终还是需要使用“块”。管理地图的离散部分比根据玩家距离估计要加载的图块更容易。所以现在,每个 HashMap 元素都是一个固定的二维整数数组,包含瓦片。我称它们为细胞
map = new ConcurrentHashMap<Point, MapCell>();
一个细胞是这样的:
public class MapCell {
public int x,y;
private static int cell_size = 32;
public MapTile[][] tiles = new MapTile[cell_size][cell_size];
}
现在。我要去柏林噪音和地图生成冒险:) 下次见。