我正在尝试在 2D RPG 游戏(基于网格的环境)中创建角色 AI 运动。使用 A* 寻路算法,我已经成功地为角色创建了不同的路径(代表他的运动)但是当我运行/调试我的游戏时我遇到了一个反复出现的问题,我似乎无法解决:
由于我的“openList”中的元素数量随机变为 0(变量:oCount),因此出现索引超出范围错误。我已经调试了很多代码,试图找到 openList 丢失其元素的确切位置,但没有任何运气。我已将其范围缩小到 while 循环的末尾。为了测试这一点,我使用了以下 if 语句并在其中放置了一个断点:
if (oCount == 0 || openList.Count == 0)
oCount = 0; //breakpoint added here
如果您在我的代码中看到“//**”,这就是我将上述“if 语句”用于测试的地方。
我对编程很陌生,所以请多多包涵。
这是我制作的 AI 类的代码:
class AI
{
List<Tile> openList;
List<Tile> closedList;
List<int> path; //represents the shortest path to travel in
int cCount = 0; // no. of elements in closedList
int oCount = 0; // no. of elements in openList
public AI()
{
openList = new List<Tile>();
closedList = new List<Tile>();
path = new List<int>();
}
public List<int> FindPath(Tile[,] tile, Vector2 xBoundary, Vector2 yBoundary, Vector2 startTile, Vector2 endTile)
{
// [tile = elements of this array tell me the grid-position of the tile]
// [xBoundary + yBoundary = the rectangular boundary for where the character can move]
// [startTile + endTile = the grid-location of the target tile and the tile the player is currently on]
//Reset variables
openList.Clear();
closedList.Clear();
path.Clear();
tile[(int)startTile.X, (int)startTile.Y].PDir = 0; //[PDir = direction to parent tile]
//Add starting tile to openList
openList.Add(tile[(int)startTile.X, (int)startTile.Y]);
//Set scores for starting tile
openList[0].G = 0;
openList[0].H = (int)Math.Abs(endTile.X - startTile.X) + (int)Math.Abs(endTile.Y - startTile.Y);
openList[0].F = openList[0].G + openList[0].H;
//** Error occurred without debugger entering the 'if statement' here
while (path.Count == 0)
{
oCount = openList.Count;
//**
//Sort openList by F score (i.e last element will have the lowest F score)
openList = Sort(openList);
//**
//Move tile with lowest F score from openList to closedList
closedList.Add(openList[oCount - 1]); //ERROR OCCURS ON THIS LINE!!
openList.RemoveAt(oCount - 1);
cCount = closedList.Count;
//Add valid surrounding tiles to openList AND Alter valid existing tiles (I will condense this into a method later, when everything is working)
//----------------------------------------------------------------------------------------------
// Each following for-loop checks the two nearest x- or y- grid positions
// a = difference in grid x-position
for (int a = -1; a < 2; a += 2)
{
//If tile is walkable[1], not in closed list[2], and within set boundaries[3-4]...
if (tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].Collision == false
&& !closedList.Contains(tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y])
&& closedList[cCount - 1].X + a >= xBoundary.X && closedList[cCount - 1].X + a <= xBoundary.Y
&& closedList[cCount - 1].Y >= yBoundary.X && closedList[cCount - 1].Y <= yBoundary.Y)
{
//If tile not in open list...
if (!openList.Contains(tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y]))
{
//Add tile to openList
openList.Add(tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y]);
oCount = openList.Count;
//Set direction to parent tile
if (a == -1)
tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].PDir = 1;
else if (a == 1)
tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].PDir = 3;
//Calculate F, G and H
openList[oCount - 1].G = closedList[cCount - 1].G + 1;
openList[oCount - 1].H = (int)Math.Abs(endTile.X - openList[oCount - 1].X) + (int)Math.Abs(endTile.Y - openList[oCount - 1].Y);
openList[oCount - 1].F = openList[oCount - 1].G + openList[oCount - 1].H;
}
//otherwise, check if current path is better than the previous path that tile had...
else if (closedList[cCount - 1].G + 1 < tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].G)
{
//Set new direction to parent tile
if (a == -1)
tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].PDir = 1;
else if (a == 1)
tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].PDir = 3;
//Re-calculate G and H values
int g = closedList[cCount - 1].G + 1;
int h = (int)Math.Abs(endTile.X - tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].X) + (int)Math.Abs(endTile.Y - tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].Y);
//Set values to tile
tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].G = g;
tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].H = h;
tile[closedList[cCount - 1].X + a, closedList[cCount - 1].Y].F = g + h; //including f-value...
//**
}
}
}
// b = difference in grid y-position
for (int b = -1; b < 2; b += 2)
{
//If tile is walkable, not in closed list, and within set boundaries...
if (tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].Collision == false
&& !closedList.Contains(tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b])
&& closedList[cCount - 1].X >= xBoundary.X && closedList[cCount - 1].X <= xBoundary.Y
&& closedList[cCount - 1].Y + b >= yBoundary.X && closedList[cCount - 1].Y + b <= yBoundary.Y)
{
//If tile not in open list...
if (!openList.Contains(tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b]))
{
//Add tile to openList
openList.Add(tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b]);
oCount = openList.Count;
//Set new parent tile direction
if (b == -1)
tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].PDir = 2;
else if (b == 1)
tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].PDir = 4;
//Calculate F, G and H
openList[oCount - 1].G = closedList[cCount - 1].G + 1;
openList[oCount - 1].H = (int)Math.Abs(endTile.X - openList[oCount - 1].X) + (int)Math.Abs(endTile.Y - openList[oCount - 1].Y);
openList[oCount - 1].F = openList[oCount - 1].G + openList[oCount - 1].H;
}
//otherwise, check if current path is better than the previous path that tile had...
else if (closedList[cCount - 1].G + 1 < tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].G)
{
//Set new parent tile direction
if (b == -1)
tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].PDir = 2;
else if (b == 1)
tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].PDir = 4;
//Re-calculate G and H values
int g = closedList[cCount - 1].G + 1;
int h = (int)Math.Abs(endTile.X - tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].X) + (int)Math.Abs(endTile.Y - tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].Y);
//Set values to tile
tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].G = g;
tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].H = h;
tile[closedList[cCount - 1].X, closedList[cCount - 1].Y + b].F = g + h; //including f-value...
//**
}
}
}
//----------------------------------------------------------------------------------------------
//If end tile is reached (i.e at end of path)...
if (closedList[cCount - 1].Y == endTile.Y && closedList[cCount - 1].X == endTile.X)
{
//tile[(int)endTile.X, (int)endTile.Y].PDir = 0;
path = PlotPath(tile, tile[(int)startTile.X, (int)startTile.Y], tile[(int)endTile.X, (int)endTile.Y]); //plot the shortest route/path
}
}
//** Breakpoint here tells me oCount reaches 0
return path;
}
private List<Tile> Sort(List<Tile> list)
{
Tile temp;
bool swapMade = true; //tells whether a swap was made during an iteration
short a = 0;
while (swapMade)
{
//reset
swapMade = false;
a++;
for (int i = list.Count - 1; i >= 0 + a; i--)
{
if (list[i].F > list[i - 1].F)
{
//swap
temp = list[i];
list[i] = list[i - 1];
list[i - 1] = temp;
swapMade = true;
}
}
}
return list;
}
private List<int> PlotPath(Tile[,] tileArray, Tile startingTile, Tile endingTile)
{
//**
Tile currentTile = endingTile;
List<int> pathList = new List<int>();
//**
while (currentTile.PDir != 0) // PDir of 0 indicates that we've reached the starting Tile (i.e the path has been fully plotted). When the character moves, it follows this path in reverse order
{
if (currentTile.PDir == 1)
{
pathList.Add(3);
currentTile = tileArray[currentTile.X + 1, currentTile.Y];
}
else if (currentTile.PDir == 2)
{
pathList.Add(4);
currentTile = tileArray[currentTile.X, currentTile.Y + 1];
}
else if (currentTile.PDir == 3)
{
pathList.Add(1);
currentTile = tileArray[currentTile.X - 1, currentTile.Y];
}
else if (currentTile.PDir == 4)
{
pathList.Add(2);
currentTile = tileArray[currentTile.X, currentTile.Y - 1];
}
}
//**
return pathList;
}
}
编辑:感谢 Peter Duniho 向我解释我的问题中缺少的地方。这样,我添加了所有使用 oCount/openList 的代码。
希望我的代码清晰易读。我相信它有很多可以改进的地方。