pathfind_astar.gd 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. extends TileMap
  2. # You can only create an AStar node from code, not from the Scene tab
  3. onready var astar_node = AStar.new()
  4. # The Tilemap node doesn't have clear bounds so we're defining the map's limits here
  5. export(Vector2) var map_size = Vector2(16, 16)
  6. # The path start and end variables use setter methods
  7. # You can find them at the bottom of the script
  8. var path_start_position = Vector2() setget _set_path_start_position
  9. var path_end_position = Vector2() setget _set_path_end_position
  10. var _point_path = []
  11. const BASE_LINE_WIDTH = 3.0
  12. const DRAW_COLOR = Color('#fff')
  13. # get_used_cells_by_id is a method from the TileMap node
  14. # here the id 0 corresponds to the grey tile, the obstacles
  15. onready var obstacles = get_used_cells_by_id(0)
  16. onready var _half_cell_size = cell_size / 2
  17. func _ready():
  18. var walkable_cells_list = astar_add_walkable_cells(obstacles)
  19. astar_connect_walkable_cells(walkable_cells_list)
  20. # Click and Shift force the start and end position of the path to update
  21. # and the node to redraw everything
  22. #func _input(event):
  23. # if event.is_action_pressed('click') and Input.is_key_pressed(KEY_SHIFT):
  24. # # To call the setter method from this script we have to use the explicit self.
  25. # self.path_start_position = world_to_map(get_global_mouse_position())
  26. # elif event.is_action_pressed('click'):
  27. # self.path_end_position = world_to_map(get_global_mouse_position())
  28. # Loops through all cells within the map's bounds and
  29. # adds all points to the astar_node, except the obstacles
  30. func astar_add_walkable_cells(obstacles = []):
  31. var points_array = []
  32. for y in range(map_size.y):
  33. for x in range(map_size.x):
  34. var point = Vector2(x, y)
  35. if point in obstacles:
  36. continue
  37. points_array.append(point)
  38. # The AStar class references points with indices
  39. # Using a function to calculate the index from a point's coordinates
  40. # ensures we always get the same index with the same input point
  41. var point_index = calculate_point_index(point)
  42. # AStar works for both 2d and 3d, so we have to convert the point
  43. # coordinates from and to Vector3s
  44. astar_node.add_point(point_index, Vector3(point.x, point.y, 0.0))
  45. return points_array
  46. # Once you added all points to the AStar node, you've got to connect them
  47. # The points don't have to be on a grid: you can use this class
  48. # to create walkable graphs however you'd like
  49. # It's a little harder to code at first, but works for 2d, 3d,
  50. # orthogonal grids, hex grids, tower defense games...
  51. func astar_connect_walkable_cells(points_array):
  52. for point in points_array:
  53. var point_index = calculate_point_index(point)
  54. # For every cell in the map, we check the one to the top, right.
  55. # left and bottom of it. If it's in the map and not an obstalce,
  56. # We connect the current point with it
  57. var points_relative = PoolVector2Array([
  58. Vector2(point.x + 1, point.y),
  59. Vector2(point.x - 1, point.y),
  60. Vector2(point.x, point.y + 1),
  61. Vector2(point.x, point.y - 1)])
  62. for point_relative in points_relative:
  63. var point_relative_index = calculate_point_index(point_relative)
  64. if is_outside_map_bounds(point_relative):
  65. continue
  66. if not astar_node.has_point(point_relative_index):
  67. continue
  68. # Note the 3rd argument. It tells the astar_node that we want the
  69. # connection to be bilateral: from point A to B and B to A
  70. # If you set this value to false, it becomes a one-way path
  71. # As we loop through all points we can set it to false
  72. astar_node.connect_points(point_index, point_relative_index, false)
  73. # This is a variation of the method above
  74. # It connects cells horizontally, vertically AND diagonally
  75. func astar_connect_walkable_cells_diagonal(points_array):
  76. for point in points_array:
  77. var point_index = calculate_point_index(point)
  78. for local_y in range(3):
  79. for local_x in range(3):
  80. var point_relative = Vector2(point.x + local_x - 1, point.y + local_y - 1)
  81. var point_relative_index = calculate_point_index(point_relative)
  82. if point_relative == point or is_outside_map_bounds(point_relative):
  83. continue
  84. if not astar_node.has_point(point_relative_index):
  85. continue
  86. astar_node.connect_points(point_index, point_relative_index, true)
  87. func is_outside_map_bounds(point):
  88. return point.x < 0 or point.y < 0 or point.x >= map_size.x or point.y >= map_size.y
  89. func calculate_point_index(point):
  90. return point.x + map_size.x * point.y
  91. func get_path(world_start, world_end):
  92. self.path_start_position = world_to_map(world_start)
  93. self.path_end_position = world_to_map(world_end)
  94. _recalculate_path()
  95. var path_world = []
  96. for point in _point_path:
  97. var point_world = map_to_world(Vector2(point.x, point.y)) + _half_cell_size
  98. path_world.append(point_world)
  99. return path_world
  100. func _recalculate_path():
  101. clear_previous_path_drawing()
  102. var start_point_index = calculate_point_index(path_start_position)
  103. var end_point_index = calculate_point_index(path_end_position)
  104. # This method gives us an array of points. Note you need the start and end
  105. # points' indices as input
  106. _point_path = astar_node.get_point_path(start_point_index, end_point_index)
  107. # Redraw the lines and circles from the start to the end point
  108. update()
  109. func clear_previous_path_drawing():
  110. if not _point_path:
  111. return
  112. var point_start = _point_path[0]
  113. var point_end = _point_path[len(_point_path) - 1]
  114. set_cell(point_start.x, point_start.y, -1)
  115. set_cell(point_end.x, point_end.y, -1)
  116. func _draw():
  117. if not _point_path:
  118. return
  119. var point_start = _point_path[0]
  120. var point_end = _point_path[len(_point_path) - 1]
  121. set_cell(point_start.x, point_start.y, 1)
  122. set_cell(point_end.x, point_end.y, 2)
  123. var last_point = map_to_world(Vector2(point_start.x, point_start.y)) + _half_cell_size
  124. for index in range(1, len(_point_path)):
  125. var current_point = map_to_world(Vector2(_point_path[index].x, _point_path[index].y)) + _half_cell_size
  126. draw_line(last_point, current_point, DRAW_COLOR, BASE_LINE_WIDTH, true)
  127. draw_circle(current_point, BASE_LINE_WIDTH * 2.0, DRAW_COLOR)
  128. last_point = current_point
  129. # Setters for the start and end path values.
  130. func _set_path_start_position(value):
  131. if value in obstacles:
  132. return
  133. if is_outside_map_bounds(value):
  134. return
  135. set_cell(path_start_position.x, path_start_position.y, -1)
  136. set_cell(value.x, value.y, 1)
  137. path_start_position = value
  138. if path_end_position and path_end_position != path_start_position:
  139. _recalculate_path()
  140. func _set_path_end_position(value):
  141. if value in obstacles:
  142. return
  143. if is_outside_map_bounds(value):
  144. return
  145. set_cell(path_start_position.x, path_start_position.y, -1)
  146. set_cell(value.x, value.y, 2)
  147. path_end_position = value
  148. if path_start_position != value:
  149. _recalculate_path()