80 lines
2.4 KiB
GDScript
80 lines
2.4 KiB
GDScript
extends CharacterBody3D
|
||
|
||
## NS-23: Follow server move target on walkable geometry.
|
||
##
|
||
## Plain Godot pattern: horizontal `velocity` toward `NavigationAgent3D`’s next path point (or the
|
||
## goal when finishing / final approach). `velocity.y` stays 0; height comes from `move_and_slide`
|
||
## and floor handling. No custom step-down / bump heuristics — obstacle pathing is best-effort;
|
||
## small steps rely on the engine + scene (snap, collider shapes). Move **legality** is server-only.
|
||
|
||
const MOVE_SPEED: float = 5.0
|
||
const ARRIVE_EPS: float = 0.35
|
||
## Stop when close in xz only; server `y` may be surface or capsule center.
|
||
const DIRECT_APPROACH_RADIUS: float = 0.85
|
||
|
||
var _has_walk_goal: bool = false
|
||
var _auth_walk_goal: Vector3 = Vector3.ZERO
|
||
|
||
@onready var _nav_agent: NavigationAgent3D = $NavigationAgent3D
|
||
|
||
|
||
func _ready() -> void:
|
||
_nav_agent.set_target_position(global_position)
|
||
|
||
|
||
func set_authoritative_nav_goal(world_pos: Vector3) -> void:
|
||
_auth_walk_goal = world_pos
|
||
_has_walk_goal = true
|
||
_nav_agent.set_target_position(world_pos)
|
||
|
||
|
||
func clear_nav_goal() -> void:
|
||
velocity = Vector3.ZERO
|
||
_has_walk_goal = false
|
||
_nav_agent.set_target_position(global_position)
|
||
|
||
|
||
func snap_to_server(world_pos: Vector3) -> void:
|
||
global_position = world_pos
|
||
velocity = Vector3.ZERO
|
||
_has_walk_goal = false
|
||
_nav_agent.set_target_position(world_pos)
|
||
|
||
|
||
func _set_horizontal_velocity_toward(point: Vector3) -> void:
|
||
var pos := global_position
|
||
var dh: Vector3 = Vector3(point.x - pos.x, 0.0, point.z - pos.z)
|
||
if dh.length_squared() < 1e-10:
|
||
dh = Vector3(1.0, 0.0, 0.0)
|
||
velocity = dh.normalized() * MOVE_SPEED
|
||
velocity.y = 0.0
|
||
|
||
|
||
func _physics_process(_delta: float) -> void:
|
||
if not _has_walk_goal:
|
||
velocity = Vector3.ZERO
|
||
move_and_slide()
|
||
return
|
||
|
||
var full_to_goal: Vector3 = _auth_walk_goal - global_position
|
||
var horiz_dist: float = Vector2(full_to_goal.x, full_to_goal.z).length()
|
||
if horiz_dist <= ARRIVE_EPS:
|
||
velocity = Vector3.ZERO
|
||
_has_walk_goal = false
|
||
_nav_agent.set_target_position(global_position)
|
||
move_and_slide()
|
||
return
|
||
|
||
var nav_map: RID = _nav_agent.get_navigation_map()
|
||
if NavigationServer3D.map_get_iteration_id(nav_map) == 0:
|
||
_set_horizontal_velocity_toward(_auth_walk_goal)
|
||
move_and_slide()
|
||
return
|
||
|
||
if _nav_agent.is_navigation_finished() or horiz_dist <= DIRECT_APPROACH_RADIUS:
|
||
_set_horizontal_velocity_toward(_auth_walk_goal)
|
||
else:
|
||
_set_horizontal_velocity_toward(_nav_agent.get_next_path_position())
|
||
|
||
move_and_slide()
|