2D 寻路代理
用代码烘焙的导航区域,以及一个绕着四个角走的代理。点击任意位置可以把它派到别处。
上方的编辑器正在运行这个项目。改一行代码,它就会重新加载。
用到的 Godot 类
NavigationRegion2DNavigationAgent2DNavigationPolygon
代码
scripts/main.gd
extends Node
const SPEED: float = 300.0
const BODY_RADIUS: float = 16.0
const MARGIN: float = 60.0
var walls: Array[Rect2] = []
var targets: PackedVector2Array = PackedVector2Array()
var target_index: int = 0
var body: Node2D
var agent: NavigationAgent2D
var path_line: Line2D
var marker: Polygon2D
func _ready() -> void:
_add_background()
var view: Vector2 = get_viewport().get_visible_rect().size
var bounds: Rect2 = Rect2(MARGIN, MARGIN, view.x - MARGIN * 2.0, view.y - MARGIN * 2.0)
walls = [
Rect2(view.x * 0.26, MARGIN, 60, view.y * 0.62),
Rect2(view.x * 0.49, view.y * 0.34, 60, view.y - view.y * 0.34 - MARGIN),
Rect2(view.x * 0.72, MARGIN, 60, view.y * 0.62),
]
targets = PackedVector2Array(
[
Vector2(view.x - 110, view.y - 100),
Vector2(view.x - 110, 120),
Vector2(110, view.y - 100),
Vector2(110, 120),
]
)
var region: NavigationRegion2D = NavigationRegion2D.new()
region.navigation_polygon = _bake(bounds)
add_child(region)
_add_region_visual(region.navigation_polygon)
for wall in walls:
var block: Polygon2D = _make_rect(wall.size, Color('#2a2f3d'))
block.position = wall.position + wall.size * 0.5
add_child(block)
path_line = Line2D.new()
path_line.width = 5.0
path_line.default_color = Color('#8eef97')
add_child(path_line)
marker = _make_circle(12.0, Color('#f3d48d'))
marker.position = targets[0]
add_child(marker)
body = Node2D.new()
body.position = Vector2(110, 120)
body.add_child(_make_circle(BODY_RADIUS, Color('#8da5f3')))
add_child(body)
agent = NavigationAgent2D.new()
agent.radius = BODY_RADIUS
agent.path_desired_distance = 12.0
agent.target_desired_distance = 12.0
body.add_child(agent)
print('the agent walks a loop of four corners, click anywhere to send it somewhere else')
func _physics_process(delta: float) -> void:
var next: Vector2 = agent.get_next_path_position()
var path: PackedVector2Array = agent.get_current_navigation_path()
# the navigation map only syncs at the end of a physics frame, so the first queries come back empty
if path.is_empty():
_send_to(targets[target_index])
return
path_line.points = path
if agent.is_navigation_finished():
target_index = (target_index + 1) % targets.size()
_send_to(targets[target_index])
return
body.position = body.position.move_toward(next, SPEED * delta)
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
_send_to(event.position)
func _send_to(where: Vector2) -> void:
agent.target_position = where
marker.position = where
func _bake(bounds: Rect2) -> NavigationPolygon:
var poly: NavigationPolygon = NavigationPolygon.new()
poly.agent_radius = BODY_RADIUS + 8.0
var source: NavigationMeshSourceGeometryData2D = NavigationMeshSourceGeometryData2D.new()
source.add_traversable_outline(_rect_points(bounds))
for wall in walls:
source.add_obstruction_outline(_rect_points(wall))
NavigationServer2D.bake_from_source_geometry_data(poly, source)
return poly
func _add_region_visual(poly: NavigationPolygon) -> void:
var vertices: PackedVector2Array = poly.get_vertices()
for i in poly.get_polygon_count():
var points: PackedVector2Array = PackedVector2Array()
for index in poly.get_polygon(i):
points.append(vertices[index])
var face: Polygon2D = Polygon2D.new()
face.polygon = points
face.color = Color(0.55, 0.65, 0.95, 0.12)
add_child(face)
var edge: Line2D = Line2D.new()
edge.points = points
edge.closed = true
edge.width = 2.0
edge.default_color = Color(0.55, 0.65, 0.95, 0.4)
add_child(edge)
func _rect_points(rect: Rect2) -> PackedVector2Array:
return PackedVector2Array([rect.position, Vector2(rect.end.x, rect.position.y), rect.end, Vector2(rect.position.x, rect.end.y)])
func _make_rect(size: Vector2, color: Color) -> Polygon2D:
var rect: Polygon2D = Polygon2D.new()
rect.polygon = PackedVector2Array([-size * 0.5, Vector2(size.x, -size.y) * 0.5, size * 0.5, Vector2(-size.x, size.y) * 0.5])
rect.color = color
return rect
func _make_circle(radius: float, color: Color) -> Polygon2D:
var points: PackedVector2Array = PackedVector2Array()
for i in 20:
points.append(Vector2(cos(TAU * i / 20.0), sin(TAU * i / 20.0)) * radius)
var circle: Polygon2D = Polygon2D.new()
circle.polygon = points
circle.color = color
return circle
func _add_background() -> void:
var background: ColorRect = ColorRect.new()
background.color = Color('#12141a')
add_child(background)
# ColorRect defaults to MOUSE_FILTER_STOP and would eat the click before _unhandled_input
background.mouse_filter = Control.MOUSE_FILTER_IGNORE
background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)