
Godot Raycasting Queries
- 107 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Helps with ai & agent building tasks during AI-assisted development.
About
godot-raycasting-queries is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- godot-raycasting-queries
- AI & Agent Building
- AI-coding skill
Godot Raycasting Queries by the numbers
- 107 all-time installs (skills.sh)
- +18 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #4,145 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-raycasting-queriesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 107 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Raycasting and Physics Queries
Physics queries allow for instantaneous detection of objects using lines (rays), volumes (shapes), or points.
Available Scripts
direct_space_state_raycast.gd
Expert usage of PhysicsDirectSpaceState2D/3D for bypassing node-based overhead in high-frequency queries.
shapecast_ground_detection.gd
Reliable ground/footing detection using volume-based ShapeCast instead of thin rays.
multiple_hit_piercing_ray.gd
Implementing piercing projectiles that detect and return multiple hits in a single line.
field_of_view_scanner.gd
AI sensor logic using a fan of raycasts to detect targets within a FOV cone.
raycast_reflection_logic.gd
Calculating bounces for lasers or bullets using collision normal reflection.
point_in_shape_query.gd
Checking for overlapping physics bodies at a single point (Explosion epicenters).
rest_info_3d_stuck_fix.gd
Using get_rest_info to detect stuck objects and resolve overlaps immediately.
mouse_pick_3d_query.gd
Converting 2D screen coordinates to 3D world rays for point-and-click interaction.
water_buoyancy_surface_calc.gd
Finding water surface height for buoyancy systems using high-to-low raycasting.
query_exclusion_optimization.gd
Optimizing performance by excluding specific RIDs (Resource IDs) from intersection checks.
NEVER Do in Physics Queries
- NEVER access `direct_space_state` outside of `_physics_process()` — The physics space can be locked or running on a separate thread; querying it in
_process()is unsafe [1, 2]. - NEVER use ShapeCast when a thin RayCast is sufficient — Volume queries are significantly more expensive. Default to rays unless you need volumetric detection [3, 4].
- NEVER assume results return `CollisionObject` nodes — CSG shapes,
GridMap, andTileMapLayerreturn themselves, not a generic physics body [5, 6]. - NEVER assume `RayCast` nodes update instantly — They update once per physics frame. If you move a node and query it immediately, you MUST call
force_raycast_update()[3, 9]. - NEVER use complex visual meshes for physics queries — GPU-only data requires expensive thread locking to parse. Use simplified primitive collision shapes [10, 11].
- NEVER iterate results to find the first valid hit — Use
collision_maskandcollision_layerto filter queries at the server level for maximum performance. - NEVER forget to exclude the caster — A ray starting from the center of a character will hit the character itself. Use
query.exclude = [self.get_rid()][20]. - NEVER use rays for small, fast detection areas — Rays can "tunnel" through thin walls if the frame rate drops. Use
cast_motionor high-frequency stepping for bullets. - NEVER query 1000+ rays individually in GDScript — Batch your queries or use the
PhysicsServerdirectly in C++ if you reach extreme query counts. - NEVER ignore the `result.rid` — RIDs are the fastest way to identify and exclude objects in subsequent queries, bypassing node-path lookups [20].
---
3D Mouse Picking Example
func screen_point_to_ray():
var space_state = get_world_3d().direct_space_state
var mouse_pos = get_viewport().get_mouse_position()
var origin = project_ray_origin(mouse_pos)
var end = origin + project_ray_normal(mouse_pos) * 2000
var query = PhysicsRayQueryParameters3D.create(origin, end)
var result = space_state.intersect_ray(query)
if result:
return result.collider
return null---
Expert Raycasting & Query Architectures
1. NavMesh-Ray-Constrain (Line-of-Sight via NavMesh)
Standard physics raycasts check against collision shapes, but if using NavigationObstacle3D with carve_navigation_mesh enabled, the NavMesh dynamically adapts. To check LOS strictly against the NavMesh (avoiding carved holes), query an optimized path between points. If the result contains exactly 2 points, it's a direct, unobstructed line.
class_name NavMeshRayValidator extends Node
## Validates line-of-sight using NavigationServer3D path optimization.
@export var agent: NavigationAgent3D
## Returns true if there is a direct, unobstructed line-of-sight on the NavMesh.
func has_navmesh_line_of_sight(target_position: Vector3) -> bool:
if not is_instance_valid(agent): return false
var map: RID = agent.get_navigation_map()
var start_position: Vector3 = agent.global_position
# Query an optimized path using the funnel algorithm.
var path: PackedVector3Array = NavigationServer3D.map_get_path(
map, start_position, target_position, true, agent.navigation_layers
)
# A direct line of sight will yield exactly two points: start and end.
return path.size() == 22. Collision-Object-Metadata (Decoupled Surface Types)
Instead of checking groups or names on intersect_ray() results, utilize Godot's built-in Object metadata. Attach arbitrary Variant data (e.g., "Stone", "Metal") to StaticBody3D nodes. This decouples the physics query from specific class implementations and allows for highly extensible surface interaction logic.
class_name SurfaceRaycaster extends Node3D
## Extracts surface metadata from raycast colliders for decoupled logic.
func perform_surface_raycast() -> void:
var space_state := get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(global_position, target_pos)
var result: Dictionary = space_state.intersect_ray(query)
if not result.is_empty():
var collider: Object = result.collider
var surface: StringName = &"default"
# Check for explicitly assigned surface metadata.
if collider.has_meta(&"surface_type"):
surface = collider.get_meta(&"surface_type")
print_rich("[color=cyan]Hit surface: %s[/color]" % surface)3. Compute-Shader-Raycast (Massively Parallel Hits)
When performing tens of thousands of rays (Radar, GI, or Volumetrics), CPU-bound queries bottleneck. Use the RenderingDevice API to execute GLSL compute shaders. Note: Since the physics BVH is CPU-bound, world geometry must be serialized into a GPU storage buffer to perform ray-triangle intersections in GLSL.
class_name ComputeRaycaster extends Node
## Orchestrates massively parallel raycasts using the RenderingDevice API.
var _rd: RenderingDevice
var _shader: RID
var _pipeline: RID
func _ready() -> void:
_rd = RenderingServer.get_rendering_device()
var shader_file: RDShaderFile = load("res://compute_raycast.glsl")
var spirv: RDShaderSPIRV = shader_file.get_spirv()
_shader = _rd.shader_create_from_spirv(spirv)
_pipeline = _rd.compute_pipeline_create(_shader)
func dispatch_rays(data_bytes: PackedByteArray) -> PackedByteArray:
var buffer_rid: RID = _rd.storage_buffer_create(data_bytes.size(), data_bytes)
var uniform := RDUniform.new()
uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
uniform.binding = 0
uniform.add_id(buffer_rid)
var uniform_set: RID = _rd.uniform_set_create([uniform], _shader, 0)
var compute_list: int = _rd.compute_list_begin()
_rd.compute_list_bind_compute_pipeline(compute_list, _pipeline)
_rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
_rd.compute_list_dispatch(compute_list, 1, 1, 1)
_rd.compute_list_end()
_rd.submit()
_rd.sync()
var output: PackedByteArray = _rd.buffer_get_data(buffer_rid)
_rd.free_rid(buffer_rid)
return outputReference
Related
godot-2d-physics,godot-physics-3d- Master Skill: godot-master
# direct_space_state_raycast.gd
# Querying physics state directly via PhysicsServer for max speed
extends Node3D
func fire_laser(origin: Vector3, end: Vector3) -> void:
var space_state = get_world_3d().direct_space_state
# create() is a fast helper to initialize the query parameters
var query = PhysicsRayQueryParameters3D.create(origin, end)
query.exclude = [self.get_rid()] # Expert: Use RID for faster exclusion
var result = space_state.intersect_ray(query)
if result:
print("Hit at point: ", result.position)
print("Collider: ", result.collider)
# field_of_view_scanner.gd
# AI visibility scanner using fan-out intersection queries
extends Node3D
@export var view_distance := 50.0
@export var view_angle := 60.0
@export var ray_count := 12
func scan_fov():
var space_state = get_world_3d().direct_space_state
var forward = -global_transform.basis.z
var start_angle = -view_angle / 2.0
var step = view_angle / (ray_count - 1)
var targets = []
for i in range(ray_count):
var angle = deg_to_rad(start_angle + i * step)
# Rotate forward vector around UP axis
var dir = forward.rotated(Vector3.UP, angle)
var query = PhysicsRayQueryParameters3D.create(global_position, global_position + dir * view_distance)
query.exclude = [self.get_rid()]
var hit = space_state.intersect_ray(query)
if hit: targets.append(hit)
return targets
# mouse_pick_3d_query.gd
# Picking 3D objects accurately via screen-to-world rays
extends Camera3D
const RAY_LENGTH = 1000.0
func _physics_process(_delta: float) -> void:
if Input.is_action_just_pressed("mouse_left"):
var result = _perform_picking()
if result:
print("Selected: ", result.collider.name)
func _perform_picking() -> Dictionary:
var space_state = get_world_3d().direct_space_state
var mouse_pos = get_viewport().get_mouse_position()
# Project ray origin and normal from screen to world
var origin = project_ray_origin(mouse_pos)
var end = origin + project_ray_normal(mouse_pos) * RAY_LENGTH
var query = PhysicsRayQueryParameters3D.create(origin, end)
return space_state.intersect_ray(query)
# multiple_hit_piercing_ray.gd
# Implementing projectiles that pierce through multiple enemies
extends Node3D
func raycast_pierce(from: Vector3, to: Vector3, max_hits: int = 5):
var hits = []
var space_state = get_world_3d().direct_space_state
var exclude = [self.get_rid()]
for i in range(max_hits):
var query = PhysicsRayQueryParameters3D.create(from, to)
query.exclude = exclude
var result = space_state.intersect_ray(query)
if result:
hits.append(result)
# Exclude the RID of the hit object to pierce it in the next iteration
exclude.append(result.rid)
else:
break
return hits
# point_in_shape_query.gd
# Checking for overlapping physics bodies at a pinpoint epicenter
extends Node3D
func check_point_overlap(epicenter: Vector3, mask: int = 1) -> Array[Dictionary]:
var space_state = get_world_3d().direct_space_state
var query = PhysicsPointQueryParameters3D.new()
query.position = epicenter
query.collision_mask = mask
# Checks whether the point is inside any solid shape (Concave/Convex/Primitive)
return space_state.intersect_point(query)
# query_exclusion_optimization.gd
# Optimizing queries by specifically excluding RIDs
extends Node
var _ignored_rids: Array[RID] = []
func add_ignored_body(body: CollisionObject3D):
_ignored_rids.append(body.get_rid())
func efficient_query(from: Vector3, to: Vector3):
var query = PhysicsRayQueryParameters3D.create(from, to)
# EXPERT: Passing RIDs directly is faster than NodePath arrays
query.exclude = _ignored_rids
return get_world_3d().direct_space_state.intersect_ray(query)
# raycast_reflection_logic.gd
# Calculating laser/bullet bounces using collision normals
extends Node3D
func calculate_bounce_path(start: Vector3, dir: Vector3, max_bounces: int = 4):
var space_state = get_world_3d().direct_space_state
var path = [start]
var current_pos = start
var current_dir = dir.normalized()
for i in max_bounces:
var query = PhysicsRayQueryParameters3D.create(current_pos, current_pos + current_dir * 500)
query.exclude = [self.get_rid()]
var result = space_state.intersect_ray(query)
if result:
current_pos = result.position
path.append(current_pos)
# Reflect the direction based on the hit normal
current_dir = current_dir.bounce(result.normal)
# Offset from surface to prevent hit_from_inside on next step
current_pos += current_dir * 0.05
else:
path.append(current_pos + current_dir * 500)
break
return path
# rest_info_3d_stuck_fix.gd
# Using get_rest_info for instant overlap/stuck detection
extends CollisionObject3D
# EXPERT NOTE: Area3D has a frame delay. get_rest_info is instant.
func detect_stuck_state():
var space_state = get_world_3d().direct_space_state
var query = PhysicsShapeQueryParameters3D.new()
# Query using the object's own first shape
query.shape_rid = get_shape_rid(0)
query.transform = global_transform
# get_rest_info returns exact contact position and normal if overlapping
var res = space_state.get_rest_info(query)
if res.size() > 0:
# Object is stuck. 'normal' points away from collision.
return res.normal
return null
# shapecast_ground_detection.gd
# Using ShapeCast3D for robust footing detection
extends ShapeCast3D
# EXPERT NOTE: Raycasts are thin and can miss corners.
# Shapecasts use a volume (Circle/Box) to detect footing reliably.
func is_on_solid_ground() -> bool:
# Ensure the shapecast is updated even if physics frame hasn't finished
force_shapecast_update()
return is_colliding()
func get_ground_normal() -> Vector3:
if is_colliding():
# Get the normal of the first contact point
return get_collision_normal(0)
return Vector3.UP
# water_buoyancy_surface_calc.gd
# Vertical raycasting to find water surface levels
extends Node3D
func get_water_height_at(pos_2d: Vector2) -> float:
var space_state = get_world_3d().direct_space_state
# Cast from high altitude down to find the surface
var start = Vector3(pos_2d.x, 100, pos_2d.y)
var end = Vector3(pos_2d.x, -50, pos_2d.y)
var query = PhysicsRayQueryParameters3D.create(start, end)
query.collision_mask = 16 # Water layer mask
query.hit_from_inside = true # Detect if we're already underwater
var res = space_state.intersect_ray(query)
if res:
return res.position.y
return -1.0 # No water found