映射分支切片路径

时间:2009-07-24 03:22:34

标签: c++ algorithm pathing tile-engine

我正在制作游戏(并且已经就此问了几个问题),现在我还有一个问题要问你们。

此游戏中的关卡格式设置为Uint16的平铺图(我正在使用SDL),它是tilemapData结构数组的索引。 tilemapData结构的一个位是isConductive bit / boolean。

这个位的使用基本上是创建将各种对象连接成一个“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只在到达交汇点时才进行递归调用,并且只是按照它的导电路径进行交互式调用,但这似乎只是部分解决方案我不能提前知道路径的扭曲或分支。

如果它有所不同,速度在这里完全不重要,因为此功能仅在使用之前处理地图时运行一次,因此使用一点额外时间不会受到伤害。

2 个答案:

答案 0 :(得分:5)

洪水填充

看起来你基本上正在做你网格的flood fill。您可以通过使用需要检查的队列或一堆方块来消除递归。有关伪代码,请参阅维基百科文章的"alternate implementations" section

自己维护队列/堆栈的好处是,当您访问它们时,您将从列表中删除方块,而在递归解决方案中,即使您访问它们之后,方块仍保留在堆栈中。

以下是适用于您的问题的维基百科文章中的“简单”替代实现:

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.

请注意,您可以使用堆栈或队列来执行此操作。这里有一些很酷且令人着迷的动画,可以直观地显示出差异:

基于队列的洪水填充

Flood fill with queue

基于堆栈的洪水填充

Flood fill with stack

连接组件标签

如果您最终在同一网格上拥有多个电源网,您可能还会发现connected component labeling页面很有趣。它基本上可以帮助你弄清楚你是否有多个断开连接的电源网,当你这样做时会告诉你每个方块属于哪一个。

Connected-component labeling example

答案 1 :(得分:1)

您可以迭代重写此功能。

以这种方式思考:您隐式使用调用堆栈作为搜索算法的路径堆栈。每次调用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
    }
}

这将产生相同的遍历(深度优先),但你的堆栈将是显式的,因此你可以为它分配内存的gobsmacks。