Question: // USING UNITY TRYING TO CREATE A CLICK TO PATH, THAT YOU CLICK ON AND THE AGENT/AVATAR FOLLOWS THE BEST PATH TO GET THERE //
// USING UNITY TRYING TO CREATE A CLICK TO PATH, THAT YOU CLICK ON AND THE AGENT/AVATAR FOLLOWS THE BEST PATH TO GET THERE
// AT THE SAME TIME THERE IS A GUARD CHASING THE AGENT USING AWARENESS AND PAHTFINDING.
//THIS IS THE MapManager.cs the Agent.cs I could upload it
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
// Custom struct to hold the per-tile information needed for the A* pathing search
public struct grid_cell
{
public bool visited;
public bool isBlocked;
public Vector2Int parent;
public float g, h, f;
}
public class MapManager : MonoBehaviour
{
// fixed map size for simplicity - map file must match
public const int WIDTH = 12;
public const int HEIGHT = 12;
// need to know tile size (color for debugging)
private Vector2 TILE_SIZE;
private Color TILE_COLOR;
// prefab tiles (and debugging labels)
public GameObject[] _tilePrefabs;
public GameObject _labelPrefab;
public GameObject _uiCanvas;
// A* pathfinding array and queue
private grid_cell[,] _map = new grid_cell[WIDTH, HEIGHT];
private List
// references to tile spriterenderers and tile labels for debugging
private SpriteRenderer[,] _tiles = new SpriteRenderer[WIDTH, HEIGHT];
private Text[,] _labels = new Text[WIDTH, HEIGHT];
// steps is a convenient way to generate the 8 children of a grid square
private Vector2Int[] _steps = new Vector2Int[8];
void Start()
{
TILE_SIZE = _tilePrefabs[0].transform.GetComponent
.bounds.extents * 2;
// load map and create tiles
readMapFile();
// for debugging, store the color of an unblocked tile (for changing them back)
TILE_COLOR = _tiles[0,0].color;
// store "step" vectors for the four cardinal directions
_steps[0].x = -1; _steps[0].y = 0;
_steps[1].x = 0; _steps[1].y = -1;
_steps[2].x = 0; _steps[2].y = 1;
_steps[3].x = 1; _steps[3].y = 0;
// and the four diagonal directions
_steps[4].x = 1; _steps[4].y = 1;
_steps[5].x = 1; _steps[5].y = -1;
_steps[6].x = -1; _steps[6].y = 1;
_steps[7].x = -1; _steps[7].y = -1;
}
/*********************************************************************************************
Collision with blocked tiles (walls)
- called by agents
- returns a response vector indicating the amount to "push" the agent out of the walls
- (0,0) indicates no collisions happening
*/
public Vector2 checkBlockedCollision(Vector2 pos, Vector2 extents)
{
// convert the world position that we're checking to the coordinates of a grid cell
Vector2Int posGC = vectorToGC(pos);
// loop over the diagonal steps to check the diagonally-adjacent cells
// check those first, because a diagonal collision implies two cardinal direction collisions
for (int si=4;si<_steps.Length;si++) {
// for each diagonal neighbor
Vector2Int step = _steps[si];
Vector2Int neighbor = posGC + step;
// if it's not on the map or not blocked, then no collision possible
if (!onMap(neighbor)) continue;
if (!_map[neighbor.x, neighbor.y].isBlocked) continue;
// otherwise, convert the neighbor to world position and calculate the distance
Vector2 neighborPos = gcToVector(neighbor);
Vector2 penetration = new Vector2(step.x * (pos.x + (extents.x * step.x) - (neighborPos.x - (TILE_SIZE.x/2 * step.x))),
step.y * (pos.y + (extents.y * step.y) - (neighborPos.y - (TILE_SIZE.y/2 * step.y))));
// in the diagonals, if penetration x AND y are positive, we have collison
if (penetration.x > 0 && penetration.y > 0) {
// if you find any one collision, return, you're done
return new Vector2(-step.x * penetration.x, -step.y * penetration.y);
}
}
// no diagonal collision, check cardinal neighbors (same algorithm)
for (int si=0;si<4;si++) {
Vector2Int step = _steps[si];
Vector2Int neighbor = posGC + step;
// only need to check if blocked
if (!onMap(neighbor)) continue;
if (!_map[neighbor.x, neighbor.y].isBlocked) continue;
Vector2 neighborPos = gcToVector(neighbor);
Vector2 penetration = new Vector2(step.x * (pos.x + (extents.x * step.x) - (neighborPos.x - (TILE_SIZE.x/2 * step.x))),
step.y * (pos.y + (extents.y * step.y) - (neighborPos.y - (TILE_SIZE.y/2 * step.y))));
// in the cardinal directions, if penetration x OR y is positive, we have collision on that axis
if (penetration.x > 0 || penetration.y > 0) {
// if you find any one collision, return, you're done
return new Vector2(-step.x * penetration.x, -step.y * penetration.y);
}
}
// no collison
return Vector2.zero;
}
/*********************************************************************************************
Loading tile map from text file
*/
private void readMapFile()
{
// file reading made easy
string[] lines = System.IO.File.ReadAllLines("Assets" + Path.DirectorySeparatorChar + "map.txt");
// use counters to keep track of grid x, y (first line in the file is at the top, so start at biggest y value and count down)
int grid_x=0, grid_y=HEIGHT-1;
// for each line in the map file
foreach (string line in lines)
{
// x starts at zero
grid_x = 0;
foreach (string sval in line.Split())
{
// get the next value (0 for open, 1 for a wall)
int val = int.Parse(sval);
// instantiate the tile prefab at the current grid x, y
GameObject tile = Instantiate(_tilePrefabs[val],
new Vector2(grid_x, grid_y),
Quaternion.identity); // *** this is wrong ***
// and parent it to this manager entity
tile.transform.parent = transform;
// set the search array blocked value based on the map value at the current grid x, y
_map[grid_x,grid_y].isBlocked = (val == 1); // *** this is wrong ***
// set the debugging array values at the current grid x, y
// the tile spriterenderer for changing color
_tiles[grid_x,grid_y] = tile.GetComponent
// and the text label to show the path search values
GameObject label = Instantiate(_labelPrefab, Camera.main.WorldToScreenPoint(tile.transform.position),
Quaternion.identity);
_labels[grid_x,grid_y] = label.GetComponent
_labels[grid_x,grid_y].text = "";
label.transform.SetParent(_uiCanvas.transform, true);
grid_x++;
}
grid_y--;
}
}
/*********************************************************************************************
Pathfinding (A*)
- getPath is the public API, takes start/end in world space vectors
*/
public List
{
// call the A* search to do the real work
List
// no path found
if (path == null) return null;
// convert the sequence of grid cells returned by A* into a sequence of world positions
List
foreach (Vector2Int gc in path)
{
vpath.Add(gcToVector(gc));
}
return vpath;
}
/***************************
Pathfinding helper functions
- grid coordinates are stored as Vector2Int
*/
// convert from a world position to the coordinates of the grid cell it's in
private Vector2Int vectorToGC(Vector2 v)
{
return new Vector2Int((int)(v.x+(TILE_SIZE.x/2))/(int)TILE_SIZE.x, (int)(v.y+(TILE_SIZE.y/2))/(int)TILE_SIZE.y);
}
// convert from the coordinates of a grid cell to a world position (the center of the cell)
private Vector2 gcToVector(Vector2Int gc)
{
return new Vector2((gc.x+0.0f)*TILE_SIZE.x, (gc.y+0.0f)*TILE_SIZE.y);
}
// check it the coordinates specify a grid cell that is within the map boundaries
private bool onMap(Vector2Int gc)
{
return gc.x >= 0 && gc.x < WIDTH && gc.y >= 0 && gc.y < HEIGHT;
}
/***************************
The A* algorithm
*/
private List
{
// degenerate cases first
// end not on map, no path to get there
if (!onMap(end))
{
return null;
}
// start is end, you're done
if (start == end) {
return null;
}
// end cell itself is blocked, no path to get there
if (_map[end.x, end.y].isBlocked) {
return null;
}
// reset the search array for a new search
for (int i=0;i for (int j=0;j // clear visited and A* g values _map[i,j].visited = false; _map[i,j].g = 0; // if debugging, reset the tile colors and labels if (debug && !_map[i,j].isBlocked) { _tiles[i,j].color = TILE_COLOR; _labels[i,j].text = ""; } } } // start the search by starting the open list with just the start position _openList.Clear(); _openList.Add(new KeyValuePair while (_openList.Count != 0) { // get the next best cell by sorting the open list by f value _openList.Sort(new ByKey()); KeyValuePair _openList.Remove(first); Vector2Int next = first.Value; // add to "closed list" by marking it visited in the search array _map[next.x, next.y].visited = true; if (next == end) { // done! generate the path list by traversing the parent links return extractPath(start, end, debug); } // add children (loop over steps to generate) for (int si=0;si<_steps.Length;si++) { Vector2Int child = next + _steps[si]; // skip this child if it is not on the map if (!onMap(child)) continue; // skip this child if it is blocked (can't walk on it anyway), or has already been visited // *** you add here *** if (_map[child.x, child.y].isBlocked) continue; if (_map[child.x, child.y].visited) continue; // calculate the A* values for this child float g = _map[next.x, next.y].g + (si>3 ? 1.414f : 1.0f) ; float h = (end - child).magnitude; float f = g + h; // is this child already in the open list? KeyValuePair if (find.Key != 0) { // if so, see if the new path to it is shorter than the one already in there if (g < _map[find.Value.x, find.Value.y].g) { // new one is better, get rid of old _openList.Remove(find); } else { // old is better, no need to add this child continue; } } // add this child to the open list _openList.Add(new KeyValuePair // and store its search values in the search array for later use _map[child.x, child.y].g = g; _map[child.x, child.y].h = h; _map[child.x, child.y].f = f; // store its parent too so we can backtrace the path when we get to the end _map[child.x, child.y].parent = next; if (debug) { // color the child tile being considered and set label to its g value _tiles[child.x, child.y].color = new Color(0.5f, 0.5f, 0.5f, 1.0f); _labels[child.x, child.y].text = g.ToString("f2"); } } } return null; } // extract the finished path by backtracing through the parent links private List { List // add the ending cell first path.Add(end); if (debug) { _tiles[end.x, end.y].color = new Color(.5f, .5f, 1.0f, 1.0f); } // then go to the parent cell Vector2Int next = _map[end.x, end.y].parent; // and keep going until we back to the start cell while (next != start) { path.Add(next); if (debug) { _tiles[next.x, next.y].color = new Color(.5f, .5f, 1.0f, 1.0f); } next = _map[next.x, next.y].parent; } // reverse the path order so start is first and end is last path.Reverse(); return path; } } // used to sort the open list by f public class ByKey : IComparer { public int Compare(KeyValuePair return a.Key.CompareTo(b.Key); } }
Step by Step Solution
There are 3 Steps involved in it
Get step-by-step solutions from verified subject matter experts
