Give street props colliders, zoned placements and variants
Props were decoration: the guard and guests walked straight through benches, bins, bollards and planters. They now block movement and steer guests around. Collision - _add_prop_model() fits a StaticBody3D from the model's mesh bounds in holder-local space: a cylinder for bollard/bin/hydrant, a box otherwise, so placement rotation and variant scale carry over. - Fallback primitives go through the same fit, so a missing .glb cannot silently remove collision. - Each prop also publishes a NavigationObstacle3D; NpcAvatar enables agent avoidance and moves with the safe velocity from velocity_computed. The navmesh stays a flat walkable plane and avoidance does the steering. Placement - PROP_PLACEMENTS groups furniture by entrance, mid-block and curb zones, with a static placement_problems() validator for bounds, overlap/min-spacing, duplicates, and the door/steps/guest-lane exclusions. - variant_for(index) gives deterministic yaw jitter, subtle scale, and optional clutter (bag by the bin, crate by the newspaper boxes, cone by the hydrant). Verification - New windowed smoke_props.gd: guard walks into a bench and stops; a guest headed beyond the bench routes around it and never enters the collider. - Headless 182 -> 187: placement validation and its failure modes, variant determinism/range, collider + obstacle on every placed prop, fallback collider. - Other smokes (PLAN verbs, crowd, emergency, timeline) still pass; Python tooling 53/53; screenshots recaptured; compare 9 good / 0 bad. - Version 0.4.0. TODO: collision/placement section moved to DONE; prop pipeline checks remain.
This commit is contained in:
1 parent
a3e90accdd
commit
bb40a98537
18 files changed
+518
-66
No files matched your search
+33
-3
@@ -73,6 +73,15 @@ func setup(profile: NpcProfile, intent: String, seed_value: int, p_spawn: Vector
|
||||
agent.target_desired_distance = 4.0
|
||||
agent.radius = 0.35
|
||||
agent.height = 1.2
|
||||
# Steer around street furniture instead of walking into it (props publish a
|
||||
# NavigationObstacle3D; the navmesh itself stays a flat walkable plane).
|
||||
agent.avoidance_enabled = true
|
||||
agent.neighbor_distance = 4.0
|
||||
agent.max_neighbors = 8
|
||||
agent.time_horizon_agents = 0.6
|
||||
agent.time_horizon_obstacles = 1.2
|
||||
agent.max_speed = FLEE_SPEED
|
||||
agent.velocity_computed.connect(_on_safe_velocity)
|
||||
add_child(agent)
|
||||
|
||||
# Normalize intent targets: approach the entrance, stop near the door.
|
||||
@@ -98,6 +107,7 @@ var _sign_prop: MeshInstance3D = null
|
||||
var _legs: Array[MeshInstance3D] = []
|
||||
var _arms: Array[MeshInstance3D] = []
|
||||
var _theater_t := 0.0
|
||||
var _avoidance_active := false
|
||||
var _photo_prop_base_y := 0.0
|
||||
var _sign_prop_base_y := 0.0
|
||||
const SKIN_TONE := Color(0.88, 0.72, 0.60)
|
||||
@@ -527,6 +537,7 @@ func _advance_fsm(delta: float) -> void:
|
||||
var leave_requested_by_lifetime := false
|
||||
|
||||
func _move_toward_target(delta: float) -> void:
|
||||
_avoidance_active = false
|
||||
if agent == null:
|
||||
_move_direct(delta)
|
||||
return
|
||||
@@ -575,14 +586,33 @@ func _move_toward_target(delta: float) -> void:
|
||||
return
|
||||
dir = dir.normalized()
|
||||
var speed := FLEE_SPEED if reaction_mode == "flee" else 1.3
|
||||
velocity.x = dir.x * speed
|
||||
velocity.z = dir.z * speed
|
||||
velocity.y -= 9.8 * delta
|
||||
look_at(global_position + Vector3(dir.x, 0, dir.z), Vector3.UP)
|
||||
if agent.avoidance_enabled:
|
||||
# The navigation server returns the obstacle-aware velocity through
|
||||
# velocity_computed; never dash straight at the point in the meantime.
|
||||
_avoidance_active = true
|
||||
agent.velocity = Vector3(dir.x * speed, 0.0, dir.z * speed)
|
||||
else:
|
||||
velocity.x = dir.x * speed
|
||||
velocity.z = dir.z * speed
|
||||
velocity.y -= 9.8 * delta
|
||||
move_and_slide()
|
||||
|
||||
## Safe velocity from NavigationServer avoidance: move with the steered vector.
|
||||
func _on_safe_velocity(safe: Vector3) -> void:
|
||||
if not _avoidance_active:
|
||||
return
|
||||
velocity.x = safe.x
|
||||
velocity.z = safe.z
|
||||
if is_on_floor():
|
||||
velocity.y = 0.0
|
||||
else:
|
||||
velocity.y -= 9.8 * get_physics_process_delta_time()
|
||||
move_and_slide()
|
||||
|
||||
## Fallback used when no navigation mesh is available (e.g. minimal scenes).
|
||||
func _move_direct(delta: float) -> void:
|
||||
_avoidance_active = false
|
||||
var to_target := target_position - global_position
|
||||
to_target.y = 0.0
|
||||
var dist := to_target.length()
|
||||
|
||||
Reference in new issue
Block a user