|
@@ -1,177 +1,83 @@
|
|
|
extends TileMap
|
|
|
|
|
|
+enum Tile { OBSTACLE, START_POINT, END_POINT }
|
|
|
+
|
|
|
+const CELL_SIZE = Vector2(64, 64)
|
|
|
const BASE_LINE_WIDTH = 3.0
|
|
|
const DRAW_COLOR = Color.WHITE
|
|
|
|
|
|
-# The Tilemap node doesn't have clear bounds so we're defining the map's limits here.
|
|
|
-@export var map_size: Vector2i = Vector2.ONE * 18
|
|
|
-
|
|
|
-# The path start and end variables use setter methods, defined below the initial values.
|
|
|
-var path_start_position = Vector2i():
|
|
|
- set(value):
|
|
|
- if value in obstacles:
|
|
|
- return
|
|
|
- if is_outside_map_bounds(value):
|
|
|
- return
|
|
|
-
|
|
|
- set_cell(0, path_start_position, -1)
|
|
|
- set_cell(0, value, 1, Vector2i())
|
|
|
- path_start_position = value
|
|
|
- if path_end_position and path_end_position != path_start_position:
|
|
|
- _recalculate_path()
|
|
|
-
|
|
|
-var path_end_position = Vector2i():
|
|
|
- set(value):
|
|
|
- if value in obstacles:
|
|
|
- return
|
|
|
- if is_outside_map_bounds(value):
|
|
|
- return
|
|
|
-
|
|
|
- set_cell(0, path_start_position, -1)
|
|
|
- set_cell(0, value, 2, Vector2i())
|
|
|
- path_end_position = value
|
|
|
- if path_start_position != value:
|
|
|
- _recalculate_path()
|
|
|
-
|
|
|
-var _point_path = []
|
|
|
-
|
|
|
-# You can only create an AStar node from code, not from the Scene tab.
|
|
|
-@onready var astar_node = AStar3D.new()
|
|
|
-# get_used_cells_by_id is a method from the TileMap node.
|
|
|
-# Here the id 0 corresponds to the grey tile, the obstacles.
|
|
|
-@onready var obstacles = get_used_cells(0)
|
|
|
+# The object for pathfinding on 2D grids.
|
|
|
+var _astar = AStarGrid2D.new()
|
|
|
+var _map_rect = Rect2i()
|
|
|
+
|
|
|
+var _start_point = Vector2i()
|
|
|
+var _end_point = Vector2i()
|
|
|
+var _path = PackedVector2Array()
|
|
|
|
|
|
func _ready():
|
|
|
- var walkable_cells_list = astar_add_walkable_cells(obstacles)
|
|
|
- astar_connect_walkable_cells(walkable_cells_list)
|
|
|
+ # Let's assume that the entire map is located at non-negative coordinates.
|
|
|
+ var map_size = get_used_rect().end
|
|
|
+ _map_rect = Rect2i(Vector2i(), map_size)
|
|
|
+
|
|
|
+ _astar.size = map_size
|
|
|
+ _astar.cell_size = CELL_SIZE
|
|
|
+ _astar.offset = CELL_SIZE * 0.5
|
|
|
+ _astar.default_compute_heuristic = AStarGrid2D.HEURISTIC_MANHATTAN
|
|
|
+ _astar.default_estimate_heuristic = AStarGrid2D.HEURISTIC_MANHATTAN
|
|
|
+ _astar.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_NEVER
|
|
|
+ _astar.update()
|
|
|
+
|
|
|
+ for i in map_size.x:
|
|
|
+ for j in map_size.y:
|
|
|
+ var pos = Vector2i(i, j)
|
|
|
+ if get_cell_source_id(0, pos) == Tile.OBSTACLE:
|
|
|
+ _astar.set_point_solid(pos)
|
|
|
|
|
|
|
|
|
func _draw():
|
|
|
- if _point_path.is_empty():
|
|
|
+ if _path.is_empty():
|
|
|
return
|
|
|
- var point_start = _point_path[0]
|
|
|
- var point_end = _point_path[len(_point_path) - 1]
|
|
|
|
|
|
- set_cell(0, Vector2i(point_start.x, point_start.y), 1, Vector2i())
|
|
|
- set_cell(0, Vector2i(point_end.x, point_end.y), 2, Vector2i())
|
|
|
-
|
|
|
- var last_point = map_to_local(Vector2i(point_start.x, point_start.y))
|
|
|
- for index in range(1, len(_point_path)):
|
|
|
- var current_point = map_to_local(Vector2i(_point_path[index].x, _point_path[index].y))
|
|
|
+ var last_point = _path[0]
|
|
|
+ for index in range(1, len(_path)):
|
|
|
+ var current_point = _path[index]
|
|
|
draw_line(last_point, current_point, DRAW_COLOR, BASE_LINE_WIDTH, true)
|
|
|
draw_circle(current_point, BASE_LINE_WIDTH * 2.0, DRAW_COLOR)
|
|
|
last_point = current_point
|
|
|
|
|
|
|
|
|
-# Loops through all cells within the map's bounds and
|
|
|
-# adds all points to the astar_node, except the obstacles.
|
|
|
-func astar_add_walkable_cells(obstacle_list = []):
|
|
|
- var points_array = []
|
|
|
- for y in range(map_size.y):
|
|
|
- for x in range(map_size.x):
|
|
|
- var point = Vector2i(x, y)
|
|
|
- if point in obstacle_list:
|
|
|
- continue
|
|
|
-
|
|
|
- points_array.append(point)
|
|
|
- # The AStar class references points with indices.
|
|
|
- # Using a function to calculate the index from a point's coordinates
|
|
|
- # ensures we always get the same index with the same input point.
|
|
|
- var point_index = calculate_point_index(point)
|
|
|
- # AStar works for both 2d and 3d, so we have to convert the point
|
|
|
- # coordinates from and to Vector3s.
|
|
|
- astar_node.add_point(point_index, Vector3(point.x, point.y, 0.0))
|
|
|
- return points_array
|
|
|
-
|
|
|
-
|
|
|
-# Once you added all points to the AStar node, you've got to connect them.
|
|
|
-# The points don't have to be on a grid: you can use this class
|
|
|
-# to create walkable graphs however you'd like.
|
|
|
-# It's a little harder to code at first, but works for 2d, 3d,
|
|
|
-# orthogonal grids, hex grids, tower defense games...
|
|
|
-func astar_connect_walkable_cells(points_array):
|
|
|
- for point in points_array:
|
|
|
- var point_index = calculate_point_index(point)
|
|
|
- # For every cell in the map, we check the one to the top, right.
|
|
|
- # left and bottom of it. If it's in the map and not an obstalce.
|
|
|
- # We connect the current point with it.
|
|
|
- var points_relative = PackedVector2Array([
|
|
|
- point + Vector2i.RIGHT,
|
|
|
- point + Vector2i.LEFT,
|
|
|
- point + Vector2i.DOWN,
|
|
|
- point + Vector2i.UP,
|
|
|
- ])
|
|
|
- for point_relative in points_relative:
|
|
|
- var point_relative_index = calculate_point_index(point_relative)
|
|
|
- if is_outside_map_bounds(point_relative):
|
|
|
- continue
|
|
|
- if not astar_node.has_point(point_relative_index):
|
|
|
- continue
|
|
|
- # Note the 3rd argument. It tells the astar_node that we want the
|
|
|
- # connection to be bilateral: from point A to B and B to A.
|
|
|
- # If you set this value to false, it becomes a one-way path.
|
|
|
- # As we loop through all points we can set it to false.
|
|
|
- astar_node.connect_points(point_index, point_relative_index, false)
|
|
|
-
|
|
|
-
|
|
|
-# This is a variation of the method above.
|
|
|
-# It connects cells horizontally, vertically AND diagonally.
|
|
|
-func astar_connect_walkable_cells_diagonal(points_array):
|
|
|
- for point in points_array:
|
|
|
- var point_index = calculate_point_index(point)
|
|
|
- for local_y in range(3):
|
|
|
- for local_x in range(3):
|
|
|
- var point_relative = Vector2i(point.x + local_x - 1, point.y + local_y - 1)
|
|
|
- var point_relative_index = calculate_point_index(point_relative)
|
|
|
- if point_relative == point or is_outside_map_bounds(point_relative):
|
|
|
- continue
|
|
|
- if not astar_node.has_point(point_relative_index):
|
|
|
- continue
|
|
|
- astar_node.connect_points(point_index, point_relative_index, true)
|
|
|
-
|
|
|
-
|
|
|
-func calculate_point_index(point):
|
|
|
- return point.x + map_size.x * point.y
|
|
|
-
|
|
|
-
|
|
|
-func clear_previous_path_drawing():
|
|
|
- if _point_path.is_empty():
|
|
|
- return
|
|
|
- var point_start = _point_path[0]
|
|
|
- var point_end = _point_path[len(_point_path) - 1]
|
|
|
- set_cell(0, Vector2i(point_start.x, point_start.y), -1)
|
|
|
- set_cell(0, Vector2i(point_end.x, point_end.y), -1)
|
|
|
+func round_local_position(local_position):
|
|
|
+ return map_to_local(local_to_map(local_position))
|
|
|
|
|
|
|
|
|
-func is_outside_map_bounds(point):
|
|
|
- return point.x < 0 or point.y < 0 or point.x >= map_size.x or point.y >= map_size.y
|
|
|
+func is_point_walkable(local_position):
|
|
|
+ var map_position = local_to_map(local_position)
|
|
|
+ if _map_rect.has_point(map_position):
|
|
|
+ return not _astar.is_point_solid(map_position)
|
|
|
+ return false
|
|
|
|
|
|
|
|
|
-func check_start_position(world_start):
|
|
|
- var start_point = local_to_map(world_start)
|
|
|
- if start_point in obstacles:
|
|
|
- return false
|
|
|
+func clear_path():
|
|
|
+ if not _path.is_empty():
|
|
|
+ _path.clear()
|
|
|
+ erase_cell(0, _start_point)
|
|
|
+ erase_cell(0, _end_point)
|
|
|
+ # Queue redraw to clear the lines and circles.
|
|
|
+ queue_redraw()
|
|
|
|
|
|
- return true
|
|
|
|
|
|
+func find_path(local_start_point, local_end_point):
|
|
|
+ clear_path()
|
|
|
|
|
|
-func get_astar_path(world_start, world_end):
|
|
|
- self.path_start_position = local_to_map(world_start)
|
|
|
- self.path_end_position = local_to_map(world_end)
|
|
|
- _recalculate_path()
|
|
|
- var path_world = []
|
|
|
- for point in _point_path:
|
|
|
- var point_world = map_to_local(Vector2i(point.x, point.y))
|
|
|
- path_world.append(point_world)
|
|
|
- return path_world
|
|
|
+ _start_point = local_to_map(local_start_point)
|
|
|
+ _end_point = local_to_map(local_end_point)
|
|
|
+ _path = _astar.get_point_path(_start_point, _end_point)
|
|
|
|
|
|
+ if not _path.is_empty():
|
|
|
+ set_cell(0, _start_point, Tile.START_POINT, Vector2i())
|
|
|
+ set_cell(0, _end_point, Tile.END_POINT, Vector2i())
|
|
|
|
|
|
-func _recalculate_path():
|
|
|
- clear_previous_path_drawing()
|
|
|
- var start_point_index = calculate_point_index(path_start_position)
|
|
|
- var end_point_index = calculate_point_index(path_end_position)
|
|
|
- # This method gives us an array of points. Note you need the start and
|
|
|
- # end points' indices as input.
|
|
|
- _point_path = astar_node.get_point_path(start_point_index, end_point_index)
|
|
|
# Redraw the lines and circles from the start to the end point.
|
|
|
queue_redraw()
|
|
|
+
|
|
|
+ return _path.duplicate()
|