mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2025-12-31 09:49:06 +03:00
Update 2D navigation demos
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 )
|
||||
|
||||
|
||||
Reference in New Issue
Block a user