Publish:

ํƒœ๊ทธ:

์นดํ…Œ๊ณ ๋ฆฌ:

๐Ÿ“Œ A* Algorithm

๊ธธ ์ฐพ๊ธฐ๋ฅผ ๋ณธ๊ฒฉ์ ์œผ๋กœ ํ•ด๋ณด์ž.

๐Ÿ“ Player Grid

ํ”Œ๋ ˆ์ด์–ด์˜ ์œ„์น˜๋ฅผ Grid ์œ„์— ํ‘œ์‹œํ•ด๋ณด์ž.

๐Ÿ“‹ Code

MyGrid.cs ์— ์ถ”๊ฐ€ํ•ด ์ค„ ๋‚ด์šฉ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public Transform _player;

public Node NodeFromWorldPoint(Vector3 worldPosition)
{
  float percentX = (worldPosition.x + _gridSize.x * 0.5f) / _gridSize.x;
  float percentY = (worldPosition.z + _gridSize.y * 0.5f) / _gridSize.y;
		
  int x = Mathf.FloorToInt(_gridSizeX * percentX);
  int y = Mathf.FloorToInt(_gridSizeY * percentY);
		
  return _grid[x, y];
}

private void OnDrawGizmos()
{
  Gizmos.DrawWireCube(transform.position, new Vector3(_gridSize.x, 0.5f, _gridSize.y));
  if(_grid != null && _displayGizmos)
  {
    Node playerNode = NodeFromWorldPoint(_player.position);
    foreach(var node in _grid)
    {
      Gizmos.color = (node._walkable) ? Color.white : Color.red;     
      
      // ์ถ”๊ฐ€๋œ ๋ถ€๋ถ„
      if(playerNode == node) // ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์„œ์žˆ๋Š” ๋…ธ๋“œ๊ฐ€ ํ˜„์žฌ ๊ทธ๋ ค์ง€๊ณ  ์žˆ๋Š” ๋…ธ๋“œ์™€ ๊ฐ™๋‹ค๋ฉด
      {
        Gizmos.color = Color.cyan; // ์ƒ‰์„ ๋ฐ”๊ฟ”๋ผ
      }
      // ์ถ”๊ฐ€ ๋

      Gizmos.DrawCube(node._worldPos, Vector3.one * (_nodeDiameter - 0.1f));
    }
  }
}

ํ”Œ๋ ˆ์ด์–ด์˜ ์œ„์น˜๊ฐ€ ์ „์ฒด Grid ์˜ ๋ช‡ ํผ์„ผํŠธ ์ฏค์— ์„œ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ํ•ด๋‹น Node ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.

์ด Node ์™€ OnDrawGizmos() ์—์„œ ๊ทธ๋ ค์ฃผ๋Š” Node ๋ฅผ ๋น„๊ตํ•˜์—ฌ ์ƒ‰์„ ๋ฐ”๊ฟ”์ค€๋‹ค.

๐Ÿ’ป Execute

1

๐Ÿ“ Path Finding

ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์„œ์žˆ๋Š” ์œ„์น˜์—์„œ ๋ชฉํ‘œ ์ง€์ ๊นŒ์ง€ ๊ธธ์„ ์ฐพ๋Š” ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด๋ณด์ž.

๐Ÿ“‹ Code

Node.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Node
{
  public Vector3 _worldPos;
  public int _gridX;
  public int _gridY;
  public bool _walkable;
	
  public int _gCost;
  public int _hCost;
  
  public int _fCost
  {
    get
    {
      return _gCost + _hCost;
    }
  }
	
  public Node _parent;
	
  public Node(Vector3 worldPos, int gridX, int gridY, bool walkable)
  {
    _worldPos = worldPos;
    _gridX = gridX;
    _gridY = gridY;
    _walkable = walkable;
  }
}

MyGrid.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class MyGrid : MonoBehaviour
{
  public Transform _player;
  [SerializeField] bool _displayGizmos;
  [SerializeField] Vector2 _gridSize;
  [SerializeField] float _nodeRadius;
  [SerializeField] LayerMask _unwalkableMask;
  Node[,] _grid;
  float _nodeDiameter;
  int _gridSizeX , _gridSizeY;
	
  public List<Node> _path;
	
  public List<Node> GetNeighbours(Node node)
  {
    List<Node> neighbours = new List<Node>();
		
    for(int x = -1; x <= 1; x++)
    {
	    for(int y = -1; y <= 1; y++)
	    {
	      // ์ œ์ž๋ฆฌ์ด๋ฉด ์•ž์œผ๋กœ
	      if(x == 0 && y == 0) continue;
				
	      int checkX = node._gridX + x;
	      int checkY = node._gridY + y;
				
	      if(checkX >= 0 && checkX < _gridSizeX && checkY >= 0 && checkY < _gridSizeY)
	      {
	        neighbours.Add(_grid[checkX, checkY]);
	      }
	    }
	  }
		
	  return neighbours;
	}
	
  void Awake()
  {
    _nodeDiameter = _nodeRadius * 2;
    _gridSizeX = Mathf.RoundToInt(_gridSize.x / _nodeDiameter);
    _gridSizeY = Mathf.RoundToInt(_gridSize.y / _nodeDiameter);
    CreateGrid();
  }
	
  void CreateGrid()
  {
    _grid = new Node[_gridSizeX, _gridSizeY];
    Vector3 worldBottomLeft = 
      transform.position - Vector3.right * (_gridSize.x / 2) - Vector3.forward * (_gridSize.y / 2);
    for(int x = 0; x < _gridSizeX; x++)
    {
      for(int y = 0; y < _gridSizeY; y++)
      {
        Vector3 worldPos = 
          worldBottomLeft + Vector3.right * (x * _nodeDiameter + _nodeRadius)
          + Vector3.forward * (y * _nodeDiameter + _nodeRadius);
        bool walkable = !(Physics.CheckSphere(worldPos, _nodeRadius - 0.2f, _unwalkableMask));
        _grid[x, y] = new Node(worldPos, x, y, walkable);
      }
    }
  }
	
  public Node NodeFromWorldPoint(Vector3 worldPosition)
  {
    float percentX = (worldPosition.x + _gridSize.x * 0.5f) / _gridSize.x;
    float percentY = (worldPosition.z + _gridSize.y * 0.5f) / _gridSize.y;
		
    int x = Mathf.FloorToInt(_gridSizeX * percentX);
    int y = Mathf.FloorToInt(_gridSizeY * percentY);
		
    return _grid[x, y];
  }
	
  private void OnDrawGizmos()
  {
    Gizmos.DrawWireCube(transform.position, new Vector3(_gridSize.x, 0.5f, _gridSize.y));
    if(_grid != null && _displayGizmos)
    {
      Node playerNode = NodeFromWorldPoint(_player.position);
      foreach(var node in _grid)
      {
        Gizmos.color = (node._walkable) ? Color.white : Color.red;
        if(playerNode == node)
        {
          Gizmos.color = Color.cyan;
        }
        if(_path != null)
        {
          if(_path.Contains(node))
          {
            Gizmos.color = Color.black;
          }
        }
        Gizmos.DrawCube(node._worldPos, Vector3.one * (_nodeDiameter - 0.1f));
      }
    }
  }
}

PathFinding.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
public class PathFinding : MonoBehaviour
{
  public Transform _player , _target;
	
  MyGrid _grid;
	
  private void Awake()
  {
    _grid = GetComponent<MyGrid>();
  }
	
  private void Update()
  {
    FindPath(_player.position, _target.position);
  }
	
  private void FindPath(Vector3 startPos, Vector3 targetPos)
  {
    Node startNode = _grid.NodeFromWorldPoint(startPos);
    Node targetNode = _grid.NodeFromWorldPoint(targetPos);
		
    List<Node> openSet = new List<Node>();
    HashSet<Node> closedSet = new HashSet<Node>();
		
    openSet.Add(startNode);
		
    while(openSet.Count > 0)
    {
      Node currentNode = openSet[0];
      for(int i = 1; i < openSet.Count; i++)
      {
        if(openSet[i]._fCost <= currentNode._fCost && openSet[i]._hCost < currentNode._hCost)
        {
          currentNode = openSet[i];
        }
      }
			
      openSet.Remove(currentNode);
      closedSet.Add(currentNode);
			
      if(currentNode == targetNode)
      {
        RetracePath(startNode, targetNode);
        return;
      }
			
      foreach(Node neighbour in _grid.GetNeighbours(currentNode))
      {
        if(!neighbour._walkable || closedSet.Contains(neighbour)) continue;
				
        int newMovementCostToNeighbour = currentNode._gCost + GetDistance(currentNode, neighbour);
        if(newMovementCostToNeighbour < neighbour._gCost || !openSet.Contains(neighbour))
        {
	        neighbour._gCost = newMovementCostToNeighbour;
	        neighbour._hCost = GetDistance(neighbour, targetNode);
	        neighbour._parent = currentNode;
					
	        if(!openSet.Contains(neighbour)) openSet.Add(neighbour);
	      }
      }
    }
	}
	
  private void RetracePath(Node startNode, Node endNode)
  {
    List<Node> path = new List<Node>();
    Node currentNode = endNode;
		
    while(currentNode != startNode)
    {
      path.Add(currentNode);
      currentNode = currentNode._parent;
    }
		
    path.Reverse();
		
    _grid._path = path;
  }
	
  private int GetDistance(Node nodeA, Node nodeB)
  {
    int dstX = Mathf.Abs(nodeA._gridX - nodeB._gridX);
    int dstY = Mathf.Abs(nodeA._gridY - nodeB._gridY);
		
    if(dstX > dstY) return 14 * dstY + 10 * (dstX - dstY);
    return 14 * dstX + 10 * (dstY - dstX);
  }
}

๐Ÿ’ป Execute

2

๋ฐฉ๋ฌธํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!๐Ÿ˜Š

์—…๋ฐ์ดํŠธ:

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ