Files
zombie-fps/scripts/room_navigation.gd
T

74 lines
2.5 KiB
GDScript

extends Node
## Shared planar A* map for static office collisions; no runtime navmesh bake.
const CELL := 0.5
const CLEARANCE := 0.60 # capsule radius .35 + conservative cell/edge margin
var grid := AStarGrid2D.new()
var obstacles: Array[Rect2] = []
func _ready() -> void:
name = "RoomNavigation"
grid.region = Rect2i(-17, -17, 35, 35)
grid.cell_size = Vector2(CELL, CELL)
grid.diagonal_mode = AStarGrid2D.DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES
grid.default_compute_heuristic = AStarGrid2D.HEURISTIC_OCTILE
grid.default_estimate_heuristic = AStarGrid2D.HEURISTIC_OCTILE
grid.update() # Must precede solid flags; update clears them.
for prop in get_tree().get_nodes_in_group("office_props"):
for collider in prop.get_children():
if not collider is CollisionShape3D or not collider.shape is BoxShape3D:
continue
var half: Vector3 = collider.shape.size * 0.5
var minimum := Vector2(INF, INF)
var maximum := Vector2(-INF, -INF)
for x in [-half.x, half.x]:
for z in [-half.z, half.z]:
var p: Vector3 = collider.global_transform * Vector3(x, 0, z)
minimum = minimum.min(Vector2(p.x, p.z))
maximum = maximum.max(Vector2(p.x, p.z))
obstacles.append(Rect2(minimum, maximum - minimum).grow(CLEARANCE))
for x in range(-17, 18):
for y in range(-17, 18):
var id := Vector2i(x, y)
var p := Vector2(id) * CELL
for rect in obstacles:
if rect.has_point(p):
grid.set_point_solid(id)
break
func _free_id(point: Vector3) -> Vector2i:
var id := Vector2i(roundi(point.x / CELL), roundi(point.z / CELL))
id.x = clampi(id.x, -17, 17)
id.y = clampi(id.y, -17, 17)
if not grid.is_point_solid(id):
return id
var best := id
var distance := INF
for x in range(-17, 18):
for y in range(-17, 18):
var candidate := Vector2i(x, y)
if not grid.is_point_solid(candidate):
var d := Vector2(candidate).distance_squared_to(Vector2(point.x, point.z) / CELL)
if d < distance:
best = candidate
distance = d
return best
func route(from: Vector3, to: Vector3) -> PackedVector3Array:
var result := PackedVector3Array()
for p in grid.get_point_path(_free_id(from), _free_id(to)):
result.append(Vector3(p.x, 0, p.y))
return result
func direct_clear(from: Vector3, to: Vector3) -> bool:
var a := Vector2(from.x, from.z)
var b := Vector2(to.x, to.z)
var count := maxi(1, ceili(a.distance_to(b) / 0.15))
for i in range(count + 1):
var p := a.lerp(b, float(i) / count)
if absf(p.x) > 8.6 or absf(p.y) > 8.6:
return false
for obstacle in obstacles:
if obstacle.has_point(p):
return false
return true