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()