Skip to content

Instantly share code, notes, and snippets.

@vmedea
Created May 30, 2023 17:08
Show Gist options
  • Save vmedea/df04115ec452c41d44fb153ff1828c52 to your computer and use it in GitHub Desktop.
Save vmedea/df04115ec452c41d44fb153ff1828c52 to your computer and use it in GitHub Desktop.
AStar2D+physics based navigation for Godot 4
# Physics-collision-based navigation grid, a quick and simple alternative for navigation polygons.
# Part of "Xenomusa" (C) Mara Huldra 2022
# SPDX-License-Identifier: MIT
extends Node2D
@export var tilemap: TileMap
@export var excludes: Array[CollisionObject2D]
@export_flags_2d_physics var nav_physics_layers: int = 1
var update_nav_grid: bool = false
var nav_grid: AStarGrid2D
var nav_tile_size: Vector2
func _ready() -> void:
queue_nav_grid_update()
func _partial_update(used_rect: Rect2i) -> void:
var space_state := get_world_2d().direct_space_state
# Collide with a circle (fastest).
var shape := CircleShape2D.new()
shape.radius = nav_tile_size.x / 2.0
var parameters := PhysicsShapeQueryParameters2D.new()
parameters.collide_with_areas = true
parameters.collide_with_bodies = true
parameters.collision_mask = nav_physics_layers
parameters.shape = shape
var exclude_rids = []
for obj in excludes:
exclude_rids.append(obj.get_rid())
parameters.exclude = exclude_rids
for y in range(used_rect.position.y, used_rect.end.y):
for x in range(used_rect.position.x, used_rect.end.x):
var pos := Vector2i(x, y)
# Position shape in middle, edge, or corner of tile (2x "oversampling").
parameters.transform.origin = Vector2(pos) * nav_tile_size
var collisions := space_state.collide_shape(parameters, 1)
var solid := collisions.size() > 0
nav_grid.set_point_solid(pos, solid)
func _build_navigation_map() -> void:
print("Updating nav grid.")
nav_grid = AStarGrid2D.new()
if tilemap == null:
printerr("FoxNav: No tilemap installed")
return
# Tiles are twice the resolution of the tileset.
var used_rect: Rect2i = tilemap.get_used_rect()
used_rect.position *= 2
used_rect.size *= 2
nav_tile_size = Vector2(tilemap.tile_set.tile_size) / 2.0
nav_grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES
nav_grid.region = used_rect
nav_grid.update()
_partial_update(used_rect)
func queue_nav_grid_update() -> void:
# XXX needs a way to specify only a certain rectangle.
update_nav_grid = true
func navigate(source: Vector2, destination: Vector2) -> Array[Vector2]:
var source_id = Vector2i(round(source / nav_tile_size))
var destination_id = Vector2i(round(destination / nav_tile_size))
var ret_path: Array[Vector2] = []
var first := true # Skip first item it will be the current cell center.
for id in nav_grid.get_id_path(source_id, destination_id):
if not first:
ret_path.append(Vector2(id) * nav_tile_size)
first = false
return ret_path
func _physics_process(_delta) -> void:
if update_nav_grid: # Must be done in physics process.
_build_navigation_map()
update_nav_grid = false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment