NS-23: Direct step-down while on ns19_bump up to auth horiz limit
Nav paths often wrap along bump rims when the goal is far; the 8 m cap then disabled step-off and left rim-following. Detect NS-19 bumps via group on footprint rays; when any hit is a bump and vertical drop is in the NS-19 band, steer straight to the goal up to 18 m (server max step). Non-bump podiums keep the 8 m close cap. Single footprint pass returns max_y + bump.pull/23/head
parent
f67705f4a3
commit
003750c7ae
|
|
@ -3,7 +3,7 @@ extends CharacterBody3D
|
||||||
## NS-23: Follow server move target on walkable geometry.
|
## NS-23: Follow server move target on walkable geometry.
|
||||||
##
|
##
|
||||||
## Horizontal velocity toward nav waypoint or goal; `velocity.y` stays 0 except a short **step-off**
|
## Horizontal velocity toward nav waypoint or goal; `velocity.y` stays 0 except a short **step-off**
|
||||||
## nudge (see `_nav_short_step_down_use_goal_direct`) so NS-19 bumps can drop without full gravity.
|
## nudge (see `_use_direct_step_down_steering`) so NS-19 bumps can drop without full gravity.
|
||||||
## Move **legality** is server MoveCommand only — not reimplemented here.
|
## Move **legality** is server MoveCommand only — not reimplemented here.
|
||||||
##
|
##
|
||||||
## Nav for detours; short local step-down (feet vs goal Y, not body origin) may bypass waypoints.
|
## Nav for detours; short local step-down (feet vs goal Y, not body origin) may bypass waypoints.
|
||||||
|
|
@ -17,7 +17,10 @@ const STEP_OFF_FLOOR_MARGIN: float = 0.04
|
||||||
## Only NS-19-sized step downs (not cliffs); allows a wider horiz cap without bee-lining big drops.
|
## Only NS-19-sized step downs (not cliffs); allows a wider horiz cap without bee-lining big drops.
|
||||||
const STEP_OFF_MIN_DROP: float = 0.055
|
const STEP_OFF_MIN_DROP: float = 0.055
|
||||||
const STEP_OFF_MAX_DROP: float = 0.24
|
const STEP_OFF_MAX_DROP: float = 0.24
|
||||||
const STEP_OFF_MAX_HORIZ: float = 8.0
|
## Past this, only direct step-down when footprint still touches `ns19_bump` (nav rim paths).
|
||||||
|
const STEP_OFF_CLOSE_HORIZ: float = 8.0
|
||||||
|
## Match server `MovementValidation.MaxHorizontalStep` default; no bee-line past this.
|
||||||
|
const STEP_OFF_MAX_HORIZ_AUTH: float = 18.0
|
||||||
const FLOOR_PROBE_BODY_Y_OFFSET: float = -0.55
|
const FLOOR_PROBE_BODY_Y_OFFSET: float = -0.55
|
||||||
## Extra xz rays for step-off; max Y keeps bump support when center ray sees floor first.
|
## Extra xz rays for step-off; max Y keeps bump support when center ray sees floor first.
|
||||||
const STEP_FOOTPRINT_HALF: float = 0.2
|
const STEP_FOOTPRINT_HALF: float = 0.2
|
||||||
|
|
@ -55,13 +58,17 @@ func snap_to_server(world_pos: Vector3) -> void:
|
||||||
_nav_agent.set_target_position(world_pos)
|
_nav_agent.set_target_position(world_pos)
|
||||||
|
|
||||||
|
|
||||||
func _ray_floor_y_at(from: Vector3) -> float:
|
func _ray_floor_hit(from: Vector3) -> Dictionary:
|
||||||
var space: PhysicsDirectSpaceState3D = get_world_3d().direct_space_state
|
var space: PhysicsDirectSpaceState3D = get_world_3d().direct_space_state
|
||||||
var to: Vector3 = from + Vector3(0.0, -FLOOR_PROBE_DOWN, 0.0)
|
var to: Vector3 = from + Vector3(0.0, -FLOOR_PROBE_DOWN, 0.0)
|
||||||
var q: PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(from, to)
|
var q: PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(from, to)
|
||||||
q.collision_mask = 1
|
q.collision_mask = 1
|
||||||
q.exclude = [get_rid()]
|
q.exclude = [get_rid()]
|
||||||
var hit: Dictionary = space.intersect_ray(q)
|
return space.intersect_ray(q)
|
||||||
|
|
||||||
|
|
||||||
|
func _ray_floor_y_at(from: Vector3) -> float:
|
||||||
|
var hit: Dictionary = _ray_floor_hit(from)
|
||||||
if hit.is_empty():
|
if hit.is_empty():
|
||||||
return from.y
|
return from.y
|
||||||
var pv: Variant = hit.get("position")
|
var pv: Variant = hit.get("position")
|
||||||
|
|
@ -70,6 +77,15 @@ func _ray_floor_y_at(from: Vector3) -> float:
|
||||||
return from.y
|
return from.y
|
||||||
|
|
||||||
|
|
||||||
|
func _collider_in_ns19_bump(collider: Variant) -> bool:
|
||||||
|
var n: Node = collider as Node
|
||||||
|
while n:
|
||||||
|
if n.is_in_group("ns19_bump"):
|
||||||
|
return true
|
||||||
|
n = n.get_parent()
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
func _floor_y_under_body() -> float:
|
func _floor_y_under_body() -> float:
|
||||||
var y_center: float = _ray_floor_y_at(global_position)
|
var y_center: float = _ray_floor_y_at(global_position)
|
||||||
var lower: Vector3 = global_position + Vector3(0.0, FLOOR_PROBE_BODY_Y_OFFSET, 0.0)
|
var lower: Vector3 = global_position + Vector3(0.0, FLOOR_PROBE_BODY_Y_OFFSET, 0.0)
|
||||||
|
|
@ -77,9 +93,8 @@ func _floor_y_under_body() -> float:
|
||||||
return maxf(y_center, y_lower)
|
return maxf(y_center, y_lower)
|
||||||
|
|
||||||
|
|
||||||
## Max floor Y under capsule + xz offsets (step-off). Fights lip rays that hit ground too early.
|
## One ray pass: max floor Y under footprint + whether any hit is an NS-19 bump (`ns19_bump` group).
|
||||||
func _floor_y_max_under_capsule_footprint() -> float:
|
func _footprint_step_down_state() -> Dictionary:
|
||||||
var best: float = _floor_y_under_body()
|
|
||||||
var p: Vector3 = global_position
|
var p: Vector3 = global_position
|
||||||
var h: float = STEP_FOOTPRINT_HALF
|
var h: float = STEP_FOOTPRINT_HALF
|
||||||
var oxz: Array[Vector3] = [
|
var oxz: Array[Vector3] = [
|
||||||
|
|
@ -92,11 +107,37 @@ func _floor_y_max_under_capsule_footprint() -> float:
|
||||||
Vector3(-h, 0.0, h),
|
Vector3(-h, 0.0, h),
|
||||||
Vector3(-h, 0.0, -h),
|
Vector3(-h, 0.0, -h),
|
||||||
]
|
]
|
||||||
|
var best_y: float = 0.0
|
||||||
|
var got: bool = false
|
||||||
|
var on_bump: bool = false
|
||||||
|
for yoff: float in [0.0, FLOOR_PROBE_BODY_Y_OFFSET]:
|
||||||
|
var from_c: Vector3 = p + Vector3(0.0, yoff, 0.0)
|
||||||
|
var hit_c: Dictionary = _ray_floor_hit(from_c)
|
||||||
|
if not hit_c.is_empty():
|
||||||
|
var yc: float = (hit_c.get("position") as Vector3).y
|
||||||
|
if not got:
|
||||||
|
best_y = yc
|
||||||
|
got = true
|
||||||
|
else:
|
||||||
|
best_y = maxf(best_y, yc)
|
||||||
|
if not on_bump and _collider_in_ns19_bump(hit_c.get("collider")):
|
||||||
|
on_bump = true
|
||||||
for d: Vector3 in oxz:
|
for d: Vector3 in oxz:
|
||||||
best = maxf(best, _ray_floor_y_at(p + d))
|
for yoff2: float in [0.0, FLOOR_PROBE_BODY_Y_OFFSET]:
|
||||||
var lower_from: Vector3 = p + d + Vector3(0.0, FLOOR_PROBE_BODY_Y_OFFSET, 0.0)
|
var from_o: Vector3 = p + d + Vector3(0.0, yoff2, 0.0)
|
||||||
best = maxf(best, _ray_floor_y_at(lower_from))
|
var hit_o: Dictionary = _ray_floor_hit(from_o)
|
||||||
return best
|
if not hit_o.is_empty():
|
||||||
|
var yo: float = (hit_o.get("position") as Vector3).y
|
||||||
|
if not got:
|
||||||
|
best_y = yo
|
||||||
|
got = true
|
||||||
|
else:
|
||||||
|
best_y = maxf(best_y, yo)
|
||||||
|
if not on_bump and _collider_in_ns19_bump(hit_o.get("collider")):
|
||||||
|
on_bump = true
|
||||||
|
if not got:
|
||||||
|
best_y = _floor_y_under_body()
|
||||||
|
return {"max_y": best_y, "touches_ns19_bump": on_bump}
|
||||||
|
|
||||||
|
|
||||||
## Footing under goal xz (surface Y). Server goal.y may be capsule center (e.g. 0.9) or pick surface
|
## Footing under goal xz (surface Y). Server goal.y may be capsule center (e.g. 0.9) or pick surface
|
||||||
|
|
@ -111,8 +152,10 @@ func _destination_footing_y() -> float:
|
||||||
return y
|
return y
|
||||||
|
|
||||||
|
|
||||||
func _nav_short_step_down_use_goal_direct() -> bool:
|
func _use_direct_step_down_steering() -> bool:
|
||||||
var floor_y: float = _floor_y_max_under_capsule_footprint()
|
var st: Dictionary = _footprint_step_down_state()
|
||||||
|
var floor_y: float = float(st["max_y"])
|
||||||
|
var on_bump: bool = bool(st["touches_ns19_bump"])
|
||||||
var dest_floor_y: float = _destination_footing_y()
|
var dest_floor_y: float = _destination_footing_y()
|
||||||
if dest_floor_y >= floor_y - STEP_OFF_FLOOR_MARGIN:
|
if dest_floor_y >= floor_y - STEP_OFF_FLOOR_MARGIN:
|
||||||
return false
|
return false
|
||||||
|
|
@ -122,9 +165,11 @@ func _nav_short_step_down_use_goal_direct() -> bool:
|
||||||
var hd: float = Vector2(
|
var hd: float = Vector2(
|
||||||
_auth_walk_goal.x - global_position.x, _auth_walk_goal.z - global_position.z
|
_auth_walk_goal.x - global_position.x, _auth_walk_goal.z - global_position.z
|
||||||
).length()
|
).length()
|
||||||
if hd > STEP_OFF_MAX_HORIZ:
|
if hd > STEP_OFF_MAX_HORIZ_AUTH:
|
||||||
return false
|
return false
|
||||||
return true
|
if on_bump:
|
||||||
|
return true
|
||||||
|
return hd <= STEP_OFF_CLOSE_HORIZ
|
||||||
|
|
||||||
|
|
||||||
func _set_horizontal_velocity_toward(point: Vector3) -> void:
|
func _set_horizontal_velocity_toward(point: Vector3) -> void:
|
||||||
|
|
@ -161,7 +206,7 @@ func _physics_process(_delta: float) -> void:
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
return
|
return
|
||||||
|
|
||||||
if _nav_short_step_down_use_goal_direct():
|
if _use_direct_step_down_steering():
|
||||||
_set_horizontal_velocity_toward(_auth_walk_goal)
|
_set_horizontal_velocity_toward(_auth_walk_goal)
|
||||||
velocity.y = -STEP_OFF_DESCEND_SPEED
|
velocity.y = -STEP_OFF_DESCEND_SPEED
|
||||||
move_and_slide()
|
move_and_slide()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue