映射分支瓦片路径
Posted
技术标签:
【中文标题】映射分支瓦片路径【英文标题】:Mapping a branching tile path 【发布时间】:2009-07-24 03:22:34 【问题描述】:我正在开发一款游戏(并且已经问了几个问题),现在我还有一个问题要问你们。
此游戏中的关卡格式设置为 Uint16(我正在使用 SDL)的 tilemap,它们是 tilemapData 结构数组的索引。 tilemapData 结构的位之一是 isConductive 位/布尔值。
该位的使用基本上是创建将各种对象连接到单个“powerNet”中的路径。我在下面有一些关于当前方法的代码(它有效,但我会在之后解释为什么我真的讨厌它)
void findSetPoweredObjects(unsigned long x, unsigned long y, powerNetInfo * powerNet)
//Look for poweredObjs on this tile and set their powerNet to the given powernet
for (int i = 0; i < level->numChunks[CHUNKTYPE_POWEREDDEF]; i++)
if (level->poweredObjects[i]->position[0] == x && level->poweredObjects[i]->position[1] == y)
level->poweredObjects[i]->powerNet = powerNet, powerNet->objectsInNet++;
void recursiveCheckTile(bool * isWalked, powerNetInfo * powerNet, unsigned long x, unsigned long y, tilemapData * levelMap)
//If out of bounds, return
if (x < 0 || y < 0 || x >= level->mapDimensions[0] || y >= level->mapDimensions[1]) return;
//If tile already walked, return
if (isWalked[x + (y * level->mapDimensions[0])]) return;
//If tile is nonconductive, return
if (!(level->tiles[levelMap->map[x + (y * level->mapDimensions[0])]]->flags & TILETYPE_CONDUCTIVE)) return;
//Valid tile to check, see if there's a poweredobj on the tile (link it to the net if it is) and check the adjacent tiles.
isWalked[x + (y * level->mapDimensions[0])] = true;
findSetPoweredObjects(x,y,powerNet);
recursiveCheckTile(isWalked, powerNet, x - 1, y, levelMap);
recursiveCheckTile(isWalked, powerNet, x + 1, y, levelMap);
recursiveCheckTile(isWalked, powerNet, x, y - 1, levelMap);
recursiveCheckTile(isWalked, powerNet, x, y + 1, levelMap);
bool buildPowerNets(void)
//Build the powernets used by the powered objects
//TODO: Rewrite buildPowerNets() & recursiveCheckTile() to avoid stack overflows and make it easier to backtrace powernets in-game
bool * isWalked;
isWalked = new bool[(level->mapDimensions[0] * level->mapDimensions[1])];
unsigned long x, y;
tilemapData * levelMap = level->layers[level->activeMap];
for (y = 0; y < level->mapDimensions[1]; y++)
for (x = 0; x < level->mapDimensions[0]; x++)
if (isWalked[x + (y * level->mapDimensions[0])]) continue;
isWalked[x + (y * level->mapDimensions[0])] = true;
if (level->tiles[levelMap->map[x + (y * level->mapDimensions[0])]]->flags & TILETYPE_CONDUCTIVE)
//it's conductive, find out what it's connected to.
//But first, create a new powernet
powerNetInfo * powerNet = new powerNetInfo;
powerNet->objectsInNet = 0;
powerNet->producerId = -1;
powerNet->supplyType = POWER_OFF;
powerNet->prevSupplyType = POWER_OFF;
powerNet->powerFor = 0;
//Find adjacent tiles to this one, add them to it's powernet, and then mark them walked. Then repeat until the net is done.
recursiveCheckTile(isWalked, powerNet, x, y, levelMap);
delete isWalked;
for (int i = 0; i < level->numChunks[CHUNKTYPE_POWEREDDEF]; i++)
if (level->poweredObjects[i]->powerNet == NULL) return false;
return true;
请注意,返回 false 意味着函数失败(在这种情况下,它没有正确链接所有对象)。
我担心的是,在更复杂的地图上,遍历导电瓷砖的功能会因为堆栈溢出而完全失败。对于如何使用这些功能降低这种风险有什么想法?如果需要,我可以提供有关所用结构的更多信息。
我考虑过修改代码,以便 recursiveCheckTile
仅在到达交汇点时进行递归调用,否则只会交互地遵循它所在的导电路径,但这似乎仍然只是部分解决方案,因为我可以'提前知道路径可能是多么曲折或分支。
如果它有所作为,速度在这里完全不重要,因为这个函数只在地图被使用前被处理时运行一次,所以使用一点额外的时间不会有什么坏处。
【问题讨论】:
【参考方案1】:洪水填充
看起来你基本上是在做你的网格的flood fill。您可以通过使用需要检查的队列或一堆正方形来消除递归。有关伪代码,请参阅***文章的 "alternate implementations" section。
自己维护队列/堆栈的好处是,当您访问它们时,您将从列表中删除它们,而在递归解决方案中,即使您访问了它们,它们仍保留在堆栈中。
这是适应您的问题的 Wikipedia 文章中的“简单”替代实现:
1. Set Q to the empty queue.
2. Add node to the end of Q.
3. While Q is not empty:
4. Set n equal to the first element of Q
5. Remove first element from Q
6. If n has already been visited:
7. Go back to step 3.
8. Mark n as visited.
9. Add the node to the west to the end of Q.
10. Add the node to the east to the end of Q.
11. Add the node to the north to the end of Q.
12. Add the node to the south to the end of Q.
13. Return.
请注意,您可以为此使用堆栈或队列,两者都可以。以下是一些很酷且令人着迷的动画,在视觉上展示了差异:
基于队列的洪水填充
基于堆栈的洪水填充
连接组件标记
如果您最终在同一电网上拥有多个电网,您可能还会发现 connected component labeling 页面很有趣。它基本上可以帮助您确定是否有多个断开的电源网,并且当您这样做时,它会告诉您每个方块属于哪一个。
【讨论】:
那些动画太棒了。 连接组件标签很棒,因为电网中可能有数百个电源网(嗯,按功能。如果任何一个关卡设计师真的做了这么多,我会感到震惊) 【参考方案2】:你可以迭代地重写这个函数。
这样想:您隐式使用调用堆栈作为搜索算法的路径堆栈。每次调用recursiveCheckTile
时,您都会将一个节点推入该堆栈。但是,调用堆栈相对较小,因此您很快就会将其炸毁。
您需要明确管理路径堆栈。不要为四个相邻节点调用递归函数,而是将一个节点压入这个显式堆栈。您的算法将如下所示(伪):
add starting node to stack
while( nodes on stack )
pop node
if( node is conductive )
add node to powerSet
push 4 adjoining nodes onto stack
这将产生相同的遍历(深度优先),但您的堆栈将是显式的,因此您可以为其分配大量内存。
【讨论】:
以上是关于映射分支瓦片路径的主要内容,如果未能解决你的问题,请参考以下文章