В этом руководстве мы детально разберем код и внутреннюю структуру реализации поиска пути на сетке в Godot с использованием AStarGrid2D.Рассмотрим годот создание игры AStar 2
Здесь спользуются следующие узлы: корневой Node2D, содержащий Sprite2D (с текстурой фона), TileMapLayer, Camera2D и Marker2D, внутри которого размещены CPUParticles2D и Sprite2D с изображением корабля. Скрипты подключены к TileMapLayer и Marker2D — их мы сейчас и разберем.
Разбор кода начнем с файла character.gd, который подключен к узлу Marker2D. Рассмотрим следующую его часть:
func _physics_process(_delta: float) -> void:
if _state != State.FOLLOW:
return
var arrived_to_next_point: bool = _move_to(_next_point)
if arrived_to_next_point:
_path.remove_at(0)
if _path.is_empty():
_change_state(State.IDLE)
return
_next_point = _path[0]
Если мы не движемся, метод _physics_process завершает выполнение досрочно. В противном случае циклически вызывается функция _move_to до тех пор, пока она не вернет True. После успешного возврата первый элемент пути удаляется, а переменной _next_point присваивается следующий элемент. Если после перемещения массив _path оказывается пустым, вызывается _change_state. Теперь рассмотрим следующую функцию:
func _move_to(local_position: Vector2) -> bool:
var desired_velocity: Vector2 = (local_position - position).normalized() * speed
var steering: Vector2 = desired_velocity - _velocity
_velocity += steering / MASS
position += _velocity * get_physics_process_delta_time()
rotation = _velocity.angle()
return position.distance_to(local_position) < ARRIVE_DISTANCE
Функция возвращает True, когда корабль успешно проходит одну клетку в направлении следующей точки маршрута. Теперь рассмотрим эту функцию подробнее:
func _change_state(new_state: State) -> void:
if new_state == State.IDLE:
_tile_map.clear_path()
elif new_state == State.FOLLOW:
_path = _tile_map.find_path(position, _click_position)
if _path.size() < 2:
_change_state(State.IDLE)
return
_next_point = _path[1]
_state = new_state
Если состояние — IDLE,то очищаем путь.Если Игрок кликнул по кораблю,то вызываем эту же функцию со значением IDLE.Устанавливаем следующую точку для перемещение и новое состояние.
Рассмотрим функцию управления вводом
func _unhandled_input(input_event: InputEvent) -> void:
_click_position = get_global_mouse_position()
if _tile_map.is_point_walkable(_click_position):
if input_event.is_action_pressed(&"teleport_to", false, true):
_change_state(State.IDLE)
global_position = _tile_map.round_local_position(_click_position)
reset_physics_interpolation()
elif input_event.is_action_pressed(&"move_to"):
_change_state(State.FOLLOW)
Обработка кликов мыши реализована следующим образом: при нажатии правой кнопки мыши корабль перемещается в указанную позицию с округлением координат(телепортируется). Левая кнопка мыши, в свою очередь, изменяет текущее состояние корабля(перемещение). Теперь перейдем к разбору кода в файле pathfind_astar.gd:
func round_local_position(local_position: Vector2i) -> Vector2i:
return map_to_local(local_to_map(local_position))
Этот фрагмент кода отвечает за округление координат: сначала координаты мыши преобразуются в координаты сетки (блока), а затем — обратно в экранные координаты для позиционирования объекта:
func is_point_walkable(local_position: Vector2) -> bool:
var map_position: Vector2i = local_to_map(local_position)
if _astar.is_in_boundsv(map_position):
return not _astar.is_point_solid(map_position)
return false
Код выполняет проверку возможности движения корабля: если клик пришелся на тайл (занятую ячейку), то движение блокируется и корабль остается на месте:
func clear_path() -> void:
if not _path.is_empty():
_path.clear()
erase_cell(_start_point)
erase_cell(_end_point)
queue_redraw()
Код выше выполняет очистку тайлов начальной и конечной позиций, после чего обнуляет массив пути и запускает перерисовку сцены:
func find_path(local_start_point: Vector2i, local_end_point: Vector2i) -> PackedVector2Array:
clear_path()
_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(_start_point, 0, TILE_START_POINT)
set_cell(_end_point, 0, TILE_END_POINT)
queue_redraw()
return _path.duplicate()
Этот код отвечает за расчет пути (вызывается из character.gd). Если путь не пуст, он устанавливает начальный и конечный тайлы перемещения, обновляет отображение и возвращает массив маршрута:
func _draw() -> void:
if _path.is_empty():
return
var last_point: Vector2 = _path[0]
for index in range(1, len(_path)):
var current_point: Vector2 = _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
Данный код отвечает за визуализацию маршрута: если массив пути не пуст, он отрисовывает кружочки в узловых точках и соединяющие их линии:
годот создание игры AStar 2.Конец
Мы детально разобрали код и структуру проекта Grid-based Pathfinding with AStarGrid2D. Теперь вы можете самостоятельно повторить этот проект. Спасибо за внимание!
