I have implemented A* in my game. However, I am not happy with the performance and need some help deciding what to do next.
I have already done the following optimizations:
- Not having a closed list (a
Node
just stores abool onClosedList()
) - Not iterating through the open list to check if a
Node
is on it (I have abool onOpenList()
too) - Using a
std::priority_queue
to store the what is on the open list - Using a heuristic that just adds up the difference in x and y coordinates
- If the zombie is targeting the player, it only updates if the player moves tiles
- If the zombie is targeting an object other than the player, it only finds the path once as objects(i.e turrets) don't move.
Some questions about my pathfinding:
- Zombies will eventually position themselves directly horizontally or vertically to to the player, and then follow the same path. Can I make it more random?
- I've heard about pre calculating every possibly path, but I feel like that will be extremely memory intensive as my map size is 257 x 257 tiles.
How my pathfinding works:
- Create a multi-dimensional vector (matrix?) of Nodes
- Put the starting node into the
openList
- Pop the top of the openList and set that node's
onOpenList
value to true. - Get its neighbors (
!onOpenList && !onClosedList && walkable
) into theopenList
after calculating their values. If the neighbor is the target, put it on the stack and stop. - Set its
onClosedList
value to true - Repeat 3-5 until you find the target
- Stack the parent of the top
Node
on the stack until it is the root node. - The zombie will keep moving to the top
Node
in the stack and then pop it.
My code:
void Zombie::findPath(std::vector< std::vector<Tile> >* pVTiles)
{
//Creates a matrix of nodes
std::vector< std::vector<Node> > mNodes_(257, std::vector<Node>(257, Node(sf::Vector2i(0, 0))));
//Clears the stack
while (!sPNodes_.empty())
sPNodes_.pop();
if (targetPosition_ != sf::Vector2i(0.0f, 0.0f))
{
readyToRepath_ = false;
//Initiates lists
std::priority_queue<Node*, std::vector<Node*>, compNode> openList;
Node* currentNode;
bool pathFound = false;
//Initiates the great journey
Node* pStartNode = &mNodes_.at((int)(positionGlobal_.x / 32)).at((int)(positionGlobal_.y / 32));
pStartNode->setPosition(sf::Vector2i(positionGlobal_.x - fmod(positionGlobal_.x, 32.0f) + 16, positionGlobal_.y - fmod(positionGlobal_.y, 32.0f) + 16));
pStartNode->setIsStartNode(true);
pStartNode->setIsOnOpen(true);
openList.push(pStartNode);
while (!pathFound)
{
//Gets the a pointer to the top item in the openList, then moves it to the closed list
currentNode = openList.top();
currentNode->setIsOnClosed(true);
currentNode->setIsOnOpen(true);
openList.pop();
//For the eight neighboring tiles/nodes
for (int i = 0; i < 8; ++i)
{
int xPos;
int yPos;
//xPos
if (i == 0 || i == 4)
xPos = 0;
else if (i > 0 && i < 4)
xPos = 1;
else
xPos = -1;
//yPos
if (i == 2 || i == 6)
yPos = 0;
else if (i < 2 || i > 6)
yPos = 1;
else
yPos = -1;
sf::Vector2i nodePosition = currentNode->getPosition() + sf::Vector2i(xPos * 32, yPos * 32);
//Creates a node for the tile
Node node(currentNode, sf::Vector2i(xPos, yPos));
//Checks to see if it is the target adds node to stack and breaks if so
if (node.getPosition() == targetPosition_)
{
pathFound = true;
sPNodes_.push(node);
break;
}
//Stop working if the node/tile is a wall or contains a tree
if (pVTiles->at(nodePosition.x / 32).at(nodePosition.y / 32).getType() == "unwalkable")
continue;
//If it's not the target
if (!pathFound)
{
float parentDistanceValue = node.getParentNodePtr()->getDistanceValue();
//Distance is 1.4f x 32 if diagonal, 1 x 32 otherwise
if (xPos == yPos)
node.setDistanceValue(parentDistanceValue + 44.8f);
else
node.setDistanceValue(parentDistanceValue + 32.0f);
//Gets the distance to the target(Heuristic) and then gets the total(Distance + Heuristic)
node.setHeuristicValue(abs(targetPosition_.x - nodePosition.x) + abs(targetPosition_.y - nodePosition.y));
node.setTotalValue();
//If the node is not already on the open/closed list
Node listCheckNode = mNodes_.at((int)(node.getPosition().x / 32)).at((int)(node.getPosition().y / 32));
if (!listCheckNode.isOnClosed() && !listCheckNode.isOnOpen())
{
mNodes_.at((int)(node.getPosition().x / 32)).at((int)(node.getPosition().y / 32)) = node;
openList.push(&mNodes_.at((int)(node.getPosition().x / 32)).at((int)(node.getPosition().y / 32)));
}
}
}
}
//Keeps stacking parent nodes until the start is reached
while (!sPNodes_.top().isStartNode())
{
Node parent = *sPNodes_.top().getParentNodePtr();
sPNodes_.push(parent);
}
//Pops the top node as the zombie is already on it
sPNodes_.pop();
}
}
Thanks!
If you need more information or have any questions, just ask!