# This is Tilemap node 's Script
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()
# This is Player node 's Script

extends Node2D

enum States { IDLE, FOLLOW }

const MASS = 10.0
const ARRIVE_DISTANCE = 10.0

export(float) var speed = 200.0
var _state = States.IDLE

var _path = []
var _target_point_world = Vector2()
var _target_position = Vector2()

var _velocity = Vector2()


func _ready():
	_change_state(States.IDLE)


func _process(_delta):
	if _state != States.FOLLOW:
		return
	var _arrived_to_next_point = _move_to(_target_point_world)
	if _arrived_to_next_point:
		_path.remove(0)
		if len(_path) == 0:
			_change_state(States.IDLE)
			return
		_target_point_world = _path[0]


func _unhandled_input(event):
	if event.is_action_pressed("click"):
		var global_mouse_pos = get_global_mouse_position()
		if Input.is_key_pressed(KEY_SHIFT):
			global_position = global_mouse_pos
		else:
			_target_position = global_mouse_pos
		_change_state(States.FOLLOW)


func _move_to(world_position):
	var desired_velocity = (world_position - position).normalized() * speed
	var steering = desired_velocity - _velocity
	_velocity += steering / MASS
	position += _velocity * get_process_delta_time()
	rotation = _velocity.angle()
	return position.distance_to(world_position) < ARRIVE_DISTANCE


func _change_state(new_state):
	if new_state == States.FOLLOW:
		_path = get_parent().get_node("TileMap").get_astar_path(position, _target_position)
		if not _path or len(_path) == 1:
			_change_state(States.IDLE)
			return
		# The index 0 is the starting cell.
		# We don't want the character to move back to it in this example.
		_target_point_world = _path[1]
	_state = new_state

Above is the script in a tutorial project about Astar2D that I got on Youtube. I found it to work very well and I am planning to follow it. But this example only makes the Player move to avoid only 1 obstacle located at id(0). So now I want to add some other obstacles for the Player to avoid, how do I edit the code?

and here is the screentree of that project.
Please HELP! Thank you so much, everyone!

  • Odin
    You could make a obstacle array:

    Then move the id line to the first line of the ready function:
    so eliminate this line:
    onready var obstacles = get_used_cells_by_id(0)

    func ready():
         var obstacle_array = [0,1,2,3]
         var obstacles = []
         for x in obstacle_array:
                 obstacles += get_used_cells_by_id(obstacle_array[x]) 

    So that would go at the top of the ready function before the rest of the code. I really can't test that code, but hopefully it will work. They should have just wrote something like that in the code because most people would probably need more than one obstacle type.



It looks like this line:
onready var obstacles = get_used_cells_by_id(0)
Will get all the tiles that have this ID. So, if you stamp the same tile all over the place, it will return a list of tiles with that ID and then it gets plugged into the walkable tiles. If you have obstacles with other id's, you would have to append the list with a for loop or something and call it for each ID, or combine the lists that are returned because it only take one obstacle list.

  • Odin replied to this.

    fire7side then I also know that line of code, because I have changed to try another id and know right away. But the problem is now how to add, or modify the code to create more obstacles. That's what I'm scratching my head about. Please help me edit the code.Please HELP me, Mr Fire!

      Odin
      You could make a obstacle array:

      Then move the id line to the first line of the ready function:
      so eliminate this line:
      onready var obstacles = get_used_cells_by_id(0)

      func ready():
           var obstacle_array = [0,1,2,3]
           var obstacles = []
           for x in obstacle_array:
                   obstacles += get_used_cells_by_id(obstacle_array[x]) 

      So that would go at the top of the ready function before the rest of the code. I really can't test that code, but hopefully it will work. They should have just wrote something like that in the code because most people would probably need more than one obstacle type.



      • Odin replied to this.

        Step one would be to find the locations of all the obstacles. (Let's assume for now all obstacles are the size of one tile)
        For each obstacle location get the corresponding location (point) in the AStar. First in AStar you use the location to get the ID for that location.
        With that ID you can disable that point so that navigators can't move through it.

        • Odin replied to this.

          fire7side Awesome it worked, but had to leave the 2 variables out of the ready function and set VAR above. Thank you Master Fire!