extends Node3D var boxes: Array[CSGBox3D] var points: Array[Vector3] const MAX_CACHED_POINTS := 100 var doomed_point_idx := 0 func _get_point() -> Vector3: assert(not boxes.is_empty()) var box: CSGBox3D = boxes.pick_random() var x_stride := box.size.x / 2 var y_stride := box.size.y / 2 var ret := box.global_position ret.x += randf_range(-x_stride, x_stride) ret.y += randf_range(-y_stride, y_stride) return ret func _ready() -> void: for child in get_children(): if child is CSGBox3D: boxes.push_back(child) func _process(_delta: float) -> void: var v := _get_point() v.y = 50. var col_mask := 16 # floor collision layer var params := PhysicsRayQueryParameters3D.create( v, v + Vector3.DOWN * 100., col_mask ) var raycast := get_world_3d().direct_space_state.intersect_ray(params) if not raycast.is_empty(): var hitpos: Vector3 = raycast.position if points.size() < MAX_CACHED_POINTS: points.push_back(hitpos) else: points[doomed_point_idx] = hitpos doomed_point_idx += 1 doomed_point_idx = doomed_point_idx % MAX_CACHED_POINTS func _on_timer_timeout() -> void: if points.is_empty(): return var pos: Vector3 = points.pick_random() pos += Vector3.UP * 0.02 var scene := preload("res://enemies/police_car/police_car.tscn") var car: Node3D = scene.instantiate() car.global_position = pos get_tree().current_scene.add_child(car)