123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- extends TileMap
- 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(Vector2) var map_size = Vector2.ONE * 16
- # The path start and end variables use setter methods.
- # You can find them at the bottom of the script.
- var path_start_position = Vector2() setget _set_path_start_position
- var path_end_position = Vector2() setget _set_path_end_position
- var _point_path = []
- # You can only create an AStar node from code, not from the Scene tab.
- onready var astar_node = AStar.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_by_id(0)
- onready var _half_cell_size = cell_size / 2
- func _ready():
- var walkable_cells_list = astar_add_walkable_cells(obstacles)
- astar_connect_walkable_cells(walkable_cells_list)
- func _draw():
- if not _point_path:
- return
- var point_start = _point_path[0]
- var point_end = _point_path[len(_point_path) - 1]
- set_cell(point_start.x, point_start.y, 1)
- set_cell(point_end.x, point_end.y, 2)
- var last_point = map_to_world(Vector2(point_start.x, point_start.y)) + _half_cell_size
- for index in range(1, len(_point_path)):
- var current_point = map_to_world(Vector2(_point_path[index].x, _point_path[index].y)) + _half_cell_size
- 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 = Vector2(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 = PoolVector2Array([
- point + Vector2.RIGHT,
- point + Vector2.LEFT,
- point + Vector2.DOWN,
- point + Vector2.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 = Vector2(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 not _point_path:
- return
- var point_start = _point_path[0]
- var point_end = _point_path[len(_point_path) - 1]
- set_cell(point_start.x, point_start.y, -1)
- set_cell(point_end.x, point_end.y, -1)
- 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 get_astar_path(world_start, world_end):
- self.path_start_position = world_to_map(world_start)
- self.path_end_position = world_to_map(world_end)
- _recalculate_path()
- var path_world = []
- for point in _point_path:
- var point_world = map_to_world(Vector2(point.x, point.y)) + _half_cell_size
- path_world.append(point_world)
- return path_world
- 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.
- update()
- # Setters for the start and end path values.
- func _set_path_start_position(value):
- if value in obstacles:
- return
- if is_outside_map_bounds(value):
- return
- set_cell(path_start_position.x, path_start_position.y, -1)
- set_cell(value.x, value.y, 1)
- path_start_position = value
- if path_end_position and path_end_position != path_start_position:
- _recalculate_path()
- func _set_path_end_position(value):
- if value in obstacles:
- return
- if is_outside_map_bounds(value):
- return
- set_cell(path_start_position.x, path_start_position.y, -1)
- set_cell(value.x, value.y, 2)
- path_end_position = value
- if path_start_position != value:
- _recalculate_path()
|