neon-sprawl/client/scripts/ground_pick.gd

62 lines
2.0 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

extends Node3D
## NS-16: walkable ground ray pick. Emits world target for MoveCommand; camera wired from main.
## NS-19: reject vertical faces (pedestal). Bump slopes ~3945° (dot UP ~0.710.78).
## 0.82 rejected slope-first picks when leaving plateau; use 0.64. Walls stay ~0.
## (No `class_name` so headless/CI can load the project before `.godot` import exists.)
signal target_chosen(world: Vector3)
## Minimum `hit_normal.dot(Vector3.UP)` to accept a pick (0 = vertical wall, 1 = flat floor).
const MIN_WALKABLE_NORMAL_DOT_UP: float = 0.64
## Fallback when `Viewport.get_camera_3d()` is null (e.g. some editor setups). Set from `main.gd`
## in `_ready`.
var fallback_camera: Camera3D
func _input(event: InputEvent) -> void:
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
if mb.button_index == MOUSE_BUTTON_LEFT and mb.pressed:
_try_pick(get_viewport().get_mouse_position())
func _try_pick(screen_pos: Vector2) -> void:
var cam: Camera3D = get_viewport().get_camera_3d()
if cam == null:
cam = fallback_camera
if cam == null:
return
var origin: Vector3 = cam.project_ray_origin(screen_pos)
var ray_dir: Vector3 = cam.project_ray_normal(screen_pos)
var to: Vector3 = origin + ray_dir * 2000.0
var query := PhysicsRayQueryParameters3D.create(origin, to)
query.collision_mask = 1
var space: PhysicsDirectSpaceState3D = get_world_3d().direct_space_state
var hit: Dictionary = space.intersect_ray(query)
if hit.is_empty():
return
var collider: Variant = hit.get("collider")
if not _collider_is_walkable(collider):
return
var hit_normal: Variant = hit.get("normal", Vector3.ZERO)
if hit_normal is not Vector3:
return
var nrm: Vector3 = (hit_normal as Vector3).normalized()
if nrm.dot(Vector3.UP) < MIN_WALKABLE_NORMAL_DOT_UP:
return
var hit_pos: Variant = hit.get("position")
if hit_pos is Vector3:
target_chosen.emit(hit_pos as Vector3)
func _collider_is_walkable(collider: Variant) -> bool:
var n: Node = collider as Node
while n:
if n.is_in_group("walkable"):
return true
n = n.get_parent()
return false