A* optimization and path help!

Hi, so I asked this question on Answers but I think that it is a bit to much for Answers. Anyways, this is a repost of what I did there. Please help, thanks! :smile:

:face_with_spiral_eyes::face_with_spiral_eyes::face_with_spiral_eyes:

Ok, so I made a A* script. My problem, however, is that I don’t know how to actually get the list of nodes that leads the object to its target after all of the g, h, and f values have been checked. How do I do that? Also, with my script, the game will freeze. Partly because of the while loop never ending (since I don’t know how to get the actual path). But even when I did not have it, the game still freezes up for a for a about half a sec(multiplied by the amount of zombies) every time it updates the path (every second). I need help optimizing my script. I know it is because all of the loops, but I don’t know how to get around them. Also, did I do my A* script correctly? Thanks!

P.S. This is what I am basing this off of.

GridCell Class:

class GridCell
{
var cellWalkable: boolean;
var cellHeight: float;
var cellX: float;
var cellZ: float;
var transformPosition: Vector3;
var hueristicValue: float;
var movementCost: float;
var totalMoveCost: float;
var onOpenList: boolean = false;
var onClosedList: boolean = false;
var onNeighborList: boolean = false;
 
 
 
}

That class is located in a script called NavCube. The NavCube is taken from a tutorial, and what it does is create the cells. It is essentially a cube that is invisible and has no mesh collider, but engulfs the whole level.(The generation of a grid does not lag at all)

NavCube Script:

//GridCell Class creation 
 
 
 
//Other variables and the code
 
var mapWidth: int;
var mapHeight: int;
var cellSize: float = 1;
var bounds: Bounds;
var topLeftCorner: Vector3;
var walkableLayer;
 
//Map of the world
var gridCellList: List.<GridCell> = new List.<GridCell>();
 
var square: GameObject;
 
function Start () 
{
 
       bounds = renderer.bounds;
       walkableLayer = LayerMask.NameToLayer("walkable");
 
}
 
function Update () 
{
//Scans the bounds of the model, then creates a grid
    //Finds the top left corner
    topLeftCorner = bounds.center - bounds.extents + Vector3(0, bounds.size.y, 0);
 
    //creates the grid map
 
    //Calculates the dimensions of the grid map.
    mapWidth = Mathf.RoundToInt(bounds.size.x / cellSize);
    mapHeight = Mathf.RoundToInt(bounds.size.z / cellSize);
    //Scans for walkable terrain in each cell
    for(var x = 0; x < mapWidth; x++)
    {
       for(var y = 0; y < mapHeight; y++)
       {
         //Gets the position for a ray
         var currentPosition = topLeftCorner + Vector3(x * cellSize, 0, y * cellSize);
         var hit: RaycastHit;
         //Creates a cell for the grid
         var cell = new GridCell();
 
         //Cast the ray
         if(Physics.Raycast(currentPosition, Vector3(0, -1, 0),hit, bounds.size.y))
         {
 
          if(hit.transform.gameObject.layer == walkableLayer)
          {
              cell.cellHeight = hit.point.y;
              cell.cellX = hit.point.x;
              cell.cellZ = hit.point.z;
              cell.transformPosition = Vector3(cell.cellX, cell.cellHeight, cell.cellZ);
              gridCellList.Add(cell);    
 
          }
         }
       }
 
    }
 
}

After that, I have a ZombieScript. It contains the actual A* algorithm. I am not including the extra zombie part of the script in here. Again, please tell me if I am doing the actual A* correct.

ZombieScript Script:

//Zombie code...
 
 
//A*
 
function UpdateGrid() //The actual A* algorithim
{
 
    var pathFound: boolean = false;
 
    //Nearest cells and their distances from the player or zombie
    var nearestCellDistance: float = Mathf.Infinity;
    var nearestTargetCellDistance: float = Mathf.Infinity;
    var nearestCell: GridCell;
    var nearestTargetCell: GridCell;
 
    //The open list, closed list, and neighbor list
    var openList: List.<GridCell> = new List.<GridCell>();
    var closedList: List.<GridCell> = new List.<GridCell>();
    var neighborList: List.<GridCell> = new List.<GridCell>();
 
    //The node with the smallest f value, and its value
    var smallestTotalMoveCostNode: GridCell;
    var smallestTotalMoveCost: float = Mathf.Infinity;
 
 
    //Finds the nearest node to the zombie and to the player(The one they are standing on)
 
    for(var i = 0; i < navCubeScript.gridCellList.Count; i++)
    {
       if(Vector3.Distance(transform.position, navCubeScript.gridCellList[i].transformPosition) < nearestCellDistance)
       {
         nearestCell = navCubeScript.gridCellList[i];
         nearestCellDistance = Vector3.Distance(transform.position, navCubeScript.gridCellList[i].transformPosition);
       }
 
       if(Vector3.Distance(playerTransform.position, navCubeScript.gridCellList[i].transformPosition) < nearestTargetCellDistance)
       {
         nearestTargetCell = navCubeScript.gridCellList[i];
         nearestTargetCellDistance = Vector3.Distance(playerTransform.position, navCubeScript.gridCellList[i].transformPosition);
       }
    }
 
    var currentNode: GridCell = nearestCell;
    currentNode.onOpenList = true;
    openList.Add(currentNode);
    currentNode.hueristicValue = Vector3.Distance(currentNode.transformPosition, nearestTargetCell.transformPosition);
    currentNode.movementCost = 0;
    currentNode.totalMoveCost = currentNode.hueristicValue + currentNode.movementCost;
 
    while(pathFound == false)
    {
 
       //Finds the node with the lowest f score
       for(var ii = 0; ii < openList.Count; ii++)
       {
         if(openList[ii].totalMoveCost < smallestTotalMoveCost)
         {
          smallestTotalMoveCostNode = openList[ii];
          smallestTotalMoveCost = openList[ii].totalMoveCost;
         }
       }
 
       //Adds the node to the closed list
       smallestTotalMoveCostNode.onClosedList = true;
       closedList.Add(smallestTotalMoveCostNode);
 
       //Finds the neighbors of the cell
       for(var iii = 0; iii < navCubeScript.gridCellList.Count; iii++)
       { 
         if((Vector3.Distance(currentNode.transformPosition, navCubeScript.gridCellList[iii].transformPosition) < 0.8))
         {
          if(navCubeScript.gridCellList[iii].onClosedList == false)
          {
              //Finds the temp cost of moving
              var tempMovementCost = Vector3.Distance(currentNode.transformPosition, navCubeScript.gridCellList[iii].transformPosition);
 
              if(navCubeScript.gridCellList[iii].onOpenList == false)
              {
                 navCubeScript.gridCellList[iii].onOpenList = true;
                 openList.Add(navCubeScript.gridCellList[iii]);
              }
 
 
              //Checks if the open set node gscore > temp cost
              for(var iv = 0; iv < openList.Count; iv++)
              {
                 if(Vector3.Distance(currentNode.transformPosition, openList[iv].transformPosition) >  tempMovementCost)
                 {
                   openList[iv].movementCost = tempMovementCost;
                   openList[iv].totalMoveCost = openList[iv].movementCost + Vector3.Distance(openList[iv].transformPosition, nearestTargetCell.transformPosition);
                 }
              }
          }
 
         }
       }
       }
 
 
 
}

Thanks for taking the time to read my long post! Please keep in mind though that this is my first time using an algorithm and doing something this complicated, just in case I did something stupid. THANKS!

Just a note on on OO design, the zombie script really should not be doing the path finding, it should ask the pathfinder script for the path information, and use that. This way you don’t clog up the zombie script with pathfinding code that could be kept away.

I did not read the scripts. But from your description then you lack something crucial. You need to save the parent node in each node. So when you find the end node, you just back track parent nodes in a loop untill you reach null (your start nod got no parent). All back tracked node positions are then saved in a list or array and returned.

When you want to optimize A*, you really need to look at the data stucture you use.

Well, how would I go around to doing that? If I have a separate script for path finding, do I just loop it for every zombie?

Thanks, I will try doing that and will then get back.

By the way, can anyone tell me what it wants me to do in this part? I am asking because I am not sure if a interpreted it correctly.

1225029--50996--$2DLg7.jpg

It more or less checks if you are moving diagonal. If you are then the G score is higher. But overall i might be better to move diagonal (lower score), hence you recalculate the f score.

It is a really silly way of explaining the algorithm, the graph you got. This is a good site for A* beginners: http://www.policyalmanac.org/games/aStarTutorial.htm

Thanks, I’ll check that out and get back.

Ok, I have some code that should work. But I need help optimizing it extremely because the game freezes when it runs. Also when you mentioned that I should have the zombies ask the pathfinding script for directions, did you mean that the script calculates the path for every zombie and then sends it to them? Thanks.

Class/NavCube Script (In a box that is as big as the map)

//Grid Cell

import System.Collections.Generic;


class GridCell
{
var cellWalkable: boolean;
var cellHeight: float;
var cellX: float;
var cellZ: float;
var transformPosition: Vector3;
var hueristicValue: float;
var movementCost: float;
var totalMoveCost: float;
var onOpenList: boolean = false;
var onClosedList: boolean = false;
var onNeighborList: boolean = false;
var parentList: List.<GridCell> = List.<GridCell>();
var parentCell: GridCell;



}

var mapWidth: int;
var mapHeight: int;
var cellSize: float = 1;
var bounds: Bounds;
var topLeftCorner: Vector3;
var walkableLayer;

//Map of the world
var gridCellList: List.<GridCell> = new List.<GridCell>();

var square: GameObject;

function Start () 
{

		bounds = renderer.bounds;
		walkableLayer = LayerMask.NameToLayer("walkable");

}

function Update () 
{
//Scans the bounds of the model, then creates a grid
	//Finds the top left corner
	topLeftCorner = bounds.center - bounds.extents + Vector3(0, bounds.size.y, 0);
	
	//creates the grid map
	
	//Calculates the dimensions of the grid map.
	mapWidth = Mathf.RoundToInt(bounds.size.x / cellSize);
	mapHeight = Mathf.RoundToInt(bounds.size.z / cellSize);
	//Scans for walkable terrain in each cell
	for(var x = 0; x < mapWidth; x++)
	{
		for(var y = 0; y < mapHeight; y++)
		{
			//Gets the position for a ray
			var currentPosition = topLeftCorner + Vector3(x * cellSize, 0, y * cellSize);
			var hit: RaycastHit;
			//Creates a cell for the grid
			var cell = new GridCell();
			
			//Cast the ray
			if(Physics.Raycast(currentPosition, Vector3(0, -1, 0),hit, bounds.size.y))
			{
				
				if(hit.transform.gameObject.layer == walkableLayer)
				{
					cell.cellHeight = hit.point.y;
					cell.cellX = hit.point.x;
					cell.cellZ = hit.point.z;
					cell.transformPosition = Vector3(cell.cellX, cell.cellHeight, cell.cellZ);
					gridCellList.Add(cell);	
					
				}
			}
		}
		
	}

}

PathfindingScript (in a gameObject that is not the zombie)

#pragma strict
import System.Collections.Generic;


//Variables
var playerTransform: Transform;
var navCubeScript: NavCube;
var timer: float = 12;


function Start () 
{
	
	playerTransform = GameObject.FindWithTag("Player").transform;	
	navCubeScript = GameObject.FindWithTag("NavCube").GetComponent(NavCube); 
}

function Update () 
{
	
	timer -= Time.deltaTime;
	
	if(timer <= 0)
	{
		UpdateGrid();
		timer = 1;
	}

	
}

function UpdateGrid() //The actual A* algorithim
{
	
	var pathFound: boolean = false;
	
	var parentCell: GridCell;
	
	var zombie: GameObject;
	
	//Nearest cells and their distances from the player or zombie
	var nearestCellDistance: float = Mathf.Infinity;
	var nearestTargetCellDistance: float = Mathf.Infinity;
	var nearestCell: GridCell;
	var nearestTargetCell: GridCell;
	
	//The open list, closed list, and neighbor list
	var openList: List.<GridCell> = new List.<GridCell>();
	var closedList: List.<GridCell> = new List.<GridCell>();
	var neighborList: List.<GridCell> = new List.<GridCell>();
	
	//The node with the smallest f value, and its value
	var smallestTotalMoveCostNode: GridCell;
	var smallestTotalMoveCost: float = Mathf.Infinity;

	zombie = GameObject.FindGameObjectWithTag("Zombie");
	//Finds the nearest node to the zombie and to the player(The one they are standing on)
	
	for(var i = 0; i < navCubeScript.gridCellList.Count; i++)
	{
		if(Vector3.Distance(zombie.transform.position, navCubeScript.gridCellList[i].transformPosition) < nearestCellDistance)
		{
			nearestCell = navCubeScript.gridCellList[i];
			nearestCellDistance = Vector3.Distance(transform.position, navCubeScript.gridCellList[i].transformPosition);
			Debug.Log("A node closer to the zombie has been found!");
		}
		
		if(Vector3.Distance(playerTransform.position, navCubeScript.gridCellList[i].transformPosition) < nearestTargetCellDistance)
		{
			nearestTargetCell = navCubeScript.gridCellList[i];
			nearestTargetCellDistance = Vector3.Distance(playerTransform.position, navCubeScript.gridCellList[i].transformPosition);
			Debug.Log("A node closer to the player has been found");
		}
	}
	
	var currentNode: GridCell = nearestCell;
	currentNode.onOpenList = true;
	openList.Add(currentNode);
	currentNode.hueristicValue = Vector3.Distance(currentNode.transformPosition, nearestTargetCell.transformPosition);
	currentNode.movementCost = 0;
	currentNode.totalMoveCost = currentNode.hueristicValue + currentNode.movementCost;


		for(var index = 0; index < openList.Count; index++)
		{
			Debug.Log("The open list has been checked for nodes!");
			openList[index].hueristicValue = Vector3.Distance(openList[index].transformPosition, nearestTargetCell.transformPosition);
			openList[index].movementCost = Vector3.Distance(nearestCell.transformPosition, openList[index].transformPosition);
			openList[index].totalMoveCost = openList[index].hueristicValue + openList[index].movementCost;
			
			//Finds the node with the lowest f score
			for(var ii = 0; ii < openList.Count; ii++)
			{	
				if(openList[ii].totalMoveCost < smallestTotalMoveCost)
				{
					smallestTotalMoveCostNode = openList[ii];
					smallestTotalMoveCost = openList[ii].totalMoveCost;
				}
			}
			
			//Is the smallest f value node the end node?
			if(smallestTotalMoveCostNode == nearestTargetCell)
			{
				//Code
				if(parentCell)
				{
					Debug.Log("The last cell has a parent");
				}
				else
				{
					Debug.Log("The last cell has no parent :(");
				}
				
			}
			
			//If not, do this
			else
			{
			
				
				//Adds the node to the closed list; removes it from the open list
				smallestTotalMoveCostNode.onClosedList = true;
				closedList.Add(smallestTotalMoveCostNode);
				smallestTotalMoveCostNode.onOpenList = false;
				openList.Remove(smallestTotalMoveCostNode);
				parentCell = smallestTotalMoveCostNode;
				Debug.Log("A node has been added to the closed list");
			
				//Finds the neighbors of the cell
				for(var iii = 0; iii < navCubeScript.gridCellList.Count; iii++)
				{	
					if((Vector3.Distance(currentNode.transformPosition, navCubeScript.gridCellList[iii].transformPosition) < 0.8))
					{
						if(navCubeScript.gridCellList[iii].onClosedList == false)
						{
							//Adds the previous cell as its perent
							navCubeScript.gridCellList[iii].parentCell = parentCell;
							
							//Finds the temp cost of moving
							var tempMovementCost = Vector3.Distance(currentNode.transformPosition, navCubeScript.gridCellList[iii].transformPosition);
							
							if(navCubeScript.gridCellList[iii].onOpenList == false)
							{
								navCubeScript.gridCellList[iii].onOpenList = true;
								openList.Add(navCubeScript.gridCellList[iii]);
								Debug.Log("Congratulations! A node has been added to the open list!");
							}
						
	
							//Checks if the open set node gscore > temp cost
							for(var iv = 0; iv < openList.Count; iv++)
							{
								if(Vector3.Distance(currentNode.transformPosition, openList[iv].transformPosition) >  tempMovementCost)
								{
									Debug.Log("A node with a lower g score or something has been found");
									//openList[iv].movementCost = tempMovementCost;
									//openList[iv].totalMoveCost = openList[iv].movementCost + Vector3.Distance(openList[iv].transformPosition, nearestTargetCell.transformPosition);
								}
							}
						}
						
					}
				}
			}
		}
		Debug.Log(Vector3.Distance(smallestTotalMoveCostNode.transformPosition, nearestTargetCell.transformPosition));			
}

I am guessing it is from the absurd amount of for loops but how can I remove those without loosing the function? Thanks again!

Yes you could have a singleton class that all zombies call with start and end position, which will then return the paths.

Looping through open and closed lists will make your search exponentially slower as the lists increases. You should look into how you can manage your data, and something like priority queues, plus some imagination will get you a long way :wink:

I would recommend you to go through an AI book and some literature on data structures. Often the best way to get an overall understanding of the area.

Thanks, I will look into the queues. I am currently making it so that each cell finds its neighbors at runtime so I don’t have to check all of the cells in the list every time I want to find neighbors.