diff --git a/2d/navigation/navigation.gd b/2d/navigation/navigation.gd index 27523c10..336e7130 100644 --- a/2d/navigation/navigation.gd +++ b/2d/navigation/navigation.gd @@ -1,46 +1,44 @@ extends Navigation2D -export(float) var CHARACTER_SPEED = 400.0 +export(float) var character_speed = 400.0 var path = [] +func _process(delta): + var walk_distance = character_speed * delta + move_along_path(walk_distance) + + # The 'click' event is a custom input action defined in -# Project > Project Settings > Input Map tab +# Project > Project Settings > Input Map tab. func _input(event): - if not event.is_action_pressed('click'): + if not event.is_action_pressed("click"): return _update_navigation_path($Character.position, get_local_mouse_position()) -func _update_navigation_path(start_position, end_position): - # get_simple_path is part of the Navigation2D class - # it returns a PoolVector2Array of points that lead you from the - # start_position to the end_position - path = get_simple_path(start_position, end_position, true) - # The first point is always the start_position - # We don't need it in this example as it corresponds to the character's position - path.remove(0) - set_process(true) - - -func _process(delta): - var walk_distance = CHARACTER_SPEED * delta - move_along_path(walk_distance) - - func move_along_path(distance): var last_point = $Character.position while path.size(): var distance_between_points = last_point.distance_to(path[0]) - - # the position to move to falls between two points + # The position to move to falls between two points. if distance <= distance_between_points: $Character.position = last_point.linear_interpolate(path[0], distance / distance_between_points) return - - # the position is past the end of the segment + # The position is past the end of the segment. distance -= distance_between_points last_point = path[0] path.remove(0) - # the character reached the end of the path + # The character reached the end of the path. $Character.position = last_point set_process(false) + + +func _update_navigation_path(start_position, end_position): + # get_simple_path is part of the Navigation2D class. + # It returns a PoolVector2Array of points that lead you + # from the start_position to the end_position. + path = get_simple_path(start_position, end_position, true) + # The first point is always the start_position. + # We don't need it in this example as it corresponds to the character's position. + path.remove(0) + set_process(true) diff --git a/2d/navigation_astar/Game.tscn b/2d/navigation_astar/Game.tscn index b2a44a43..2bd5269c 100644 --- a/2d/navigation_astar/Game.tscn +++ b/2d/navigation_astar/Game.tscn @@ -5,7 +5,7 @@ [ext_resource path="res://character.gd" type="Script" id=3] [ext_resource path="res://sprites/character.png" type="Texture" id=4] -[node name="Game" type="Node"] +[node name="Game" type="Node2D"] [node name="TileMap" type="TileMap" parent="."] tile_set = ExtResource( 1 ) diff --git a/2d/navigation_astar/character.gd b/2d/navigation_astar/character.gd index e1d4e951..b29122ab 100644 --- a/2d/navigation_astar/character.gd +++ b/2d/navigation_astar/character.gd @@ -1,8 +1,8 @@ extends Position2D -export(float) var SPEED = 200.0 +enum States { IDLE, FOLLOW } -enum STATES { IDLE, FOLLOW } +export(float) var speed = 200.0 var _state = null var path = [] @@ -12,38 +12,35 @@ var target_position = Vector2() var velocity = Vector2() func _ready(): - _change_state(STATES.IDLE) - - -func _change_state(new_state): - if new_state == STATES.FOLLOW: - path = get_parent().get_node('TileMap')._get_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 + _change_state(States.IDLE) func _process(_delta): - if not _state == STATES.FOLLOW: + if not _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) + _change_state(States.IDLE) return target_point_world = path[0] +func _input(event): + if event.is_action_pressed("click"): + if Input.is_key_pressed(KEY_SHIFT): + global_position = get_global_mouse_position() + else: + target_position = get_global_mouse_position() + _change_state(States.FOLLOW) + + func move_to(world_position): var MASS = 10.0 var ARRIVE_DISTANCE = 10.0 - var desired_velocity = (world_position - position).normalized() * SPEED + var desired_velocity = (world_position - position).normalized() * speed var steering = desired_velocity - velocity velocity += steering / MASS position += velocity * get_process_delta_time() @@ -51,10 +48,13 @@ func move_to(world_position): return position.distance_to(world_position) < ARRIVE_DISTANCE -func _input(event): - if event.is_action_pressed('click'): - if Input.is_key_pressed(KEY_SHIFT): - global_position = get_global_mouse_position() - else: - target_position = get_global_mouse_position() - _change_state(STATES.FOLLOW) +func _change_state(new_state): + if new_state == States.FOLLOW: + path = get_parent().get_node("TileMap")._get_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 diff --git a/2d/navigation_astar/pathfind_astar.gd b/2d/navigation_astar/pathfind_astar.gd index 21fddfd2..30e6afc0 100644 --- a/2d/navigation_astar/pathfind_astar.gd +++ b/2d/navigation_astar/pathfind_astar.gd @@ -1,22 +1,22 @@ extends TileMap -# You can only create an AStar node from code, not from the Scene tab -onready var astar_node = AStar.new() -# The Tilemap node doesn't have clear bounds so we're defining the map's limits here +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(16, 16) -# The path start and end variables use setter methods -# You can find them at the bottom of the script +# 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 = [] -const BASE_LINE_WIDTH = 3.0 -const DRAW_COLOR = Color('#fff') - -# get_used_cells_by_id is a method from the TileMap node -# here the id 0 corresponds to the grey tile, the obstacles +# 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 @@ -25,123 +25,6 @@ func _ready(): astar_connect_walkable_cells(walkable_cells_list) -# Click and Shift force the start and end position of the path to update -# and the node to redraw everything -#func _input(event): -# if event.is_action_pressed('click') and Input.is_key_pressed(KEY_SHIFT): -# # To call the setter method from this script we have to use the explicit self. -# self.path_start_position = world_to_map(get_global_mouse_position()) -# elif event.is_action_pressed('click'): -# self.path_end_position = world_to_map(get_global_mouse_position()) - - -# 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(obstacles = []): - 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 obstacles: - 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([ - Vector2(point.x + 1, point.y), - Vector2(point.x - 1, point.y), - Vector2(point.x, point.y + 1), - Vector2(point.x, point.y - 1)]) - 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 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 calculate_point_index(point): - return point.x + map_size.x * point.y - - -func _get_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() - - -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 _draw(): if not _point_path: return @@ -159,6 +42,121 @@ func _draw(): last_point = current_point +# Click and Shift force the start and end position of the path to update, +# and the node to redraw everything. +#func _input(event): +# if event.is_action_pressed('click') and Input.is_key_pressed(KEY_SHIFT): +# # To call the setter method from this script we have to use the explicit self. +# self.path_start_position = world_to_map(get_global_mouse_position()) +# elif event.is_action_pressed('click'): +# self.path_end_position = world_to_map(get_global_mouse_position()) + + +# 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([ + Vector2(point.x + 1, point.y), + Vector2(point.x - 1, point.y), + Vector2(point.x, point.y + 1), + Vector2(point.x, point.y - 1)]) + 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_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: diff --git a/2d/navigation_astar/tileset/tileset_source.tscn b/2d/navigation_astar/tileset/tileset_source.tscn index 1a9ee15b..a4a4307b 100644 --- a/2d/navigation_astar/tileset/tileset_source.tscn +++ b/2d/navigation_astar/tileset/tileset_source.tscn @@ -4,7 +4,7 @@ [ext_resource path="res://sprites/path_start.png" type="Texture" id=2] [ext_resource path="res://sprites/path_end.png" type="Texture" id=3] -[node name="Node2D" type="Node2D"] +[node name="Tileset" type="Node2D"] [node name="Obstacle" type="Sprite" parent="."] position = Vector2( 32, 32 ) @@ -17,4 +17,3 @@ texture = ExtResource( 2 ) [node name="PathEnd" type="Sprite" parent="."] position = Vector2( 192, 32 ) texture = ExtResource( 3 ) -