Godot 4中如何通过射线检测获取鼠标指向的3D坐标?
问题分析与修复方案
你遇到的核心问题是射线检测几乎无法命中3D物体,导致ScreenPointToRay()返回空向量,同时角色移动逻辑也存在用法错误,以下是具体修复点:
1. 射线检测失效的原因及修复
碰撞层/掩码不匹配
射线检测默认只会检测与自身碰撞掩码匹配的物体。如果地面或可点击物体的碰撞层未被射线掩码覆盖,就会检测不到目标。
- 修复:创建射线参数时明确指定要检测的碰撞层(比如检测第1层,对应地面):
var params = PhysicsRayQueryParameters3D.create(rayOrigin, rayEnd) params.collision_mask = 0b1 # 按需调整,对应你场景中物体的碰撞层
射线长度不足
你设置的射线长度为200,若场景中相机到目标物体的距离超过这个值,射线就无法命中。
- 修复:增大射线长度,或针对平面地面用更高效的投射计算:
# 针对平面地面的替代方案(假设地面y坐标为0) var ground_y = 0 var world_pos = camera.project_position(mousePos, camera.global_position.distance_to(Vector3(0, ground_y, 0)))
相机获取方式不可靠
get_tree().root.get_camera_3d()可能返回非当前活跃的相机(多相机场景下),改用视口获取更稳定:
var camera = get_viewport().get_camera_3d()
2. 移动逻辑错误
move_and_collide()需要传入位移向量,而非目标位置。你当前传入目标坐标会导致角色瞬间跳至错误位置。
- 修复:计算当前位置到目标位置的差值作为位移:
var target_pos = Vector3(snapped(motion.x, 2), 0, snapped(motion.z, 2)) var displacement = target_pos - get_child(0).global_position get_child(0).move_and_collide(displacement)
另外,Godot中没有snappedf函数,应使用snapped()方法,建议直接对Vector3整体处理。
修正后的完整代码
extends Node3D func get_mouse_world_position() -> Vector3: var viewport = get_viewport() var camera = viewport.get_camera_3d() if not camera: return Vector3.ZERO var mouse_pos = viewport.get_mouse_position() var space_state = get_world_3d().direct_space_state # 配置射线检测参数 var ray_origin = camera.project_ray_origin(mouse_pos) var ray_end = ray_origin + camera.project_ray_normal(mouse_pos) * 1000 # 增大射线长度 var params = PhysicsRayQueryParameters3D.create(ray_origin, ray_end) params.collision_mask = 0b1 # 按需调整碰撞层 var hit_result = space_state.intersect_ray(params) if hit_result.has("position"): return hit_result["position"] # fallback:未检测到物体时,投射到固定高度地面 return camera.project_position(mouse_pos, camera.global_position.distance_to(Vector3(0, 0, 0))) func _process(delta): if Input.is_action_pressed("L_Click"): var target_world_pos = get_mouse_world_position() # 对齐到2单位网格 var snapped_target = Vector3( snapped(target_world_pos.x, 2), 0, # 固定角色y轴高度 snapped(target_world_pos.z, 2) ) # 计算位移并移动 var player = get_child(0) var displacement = snapped_target - player.global_position player.move_and_collide(displacement)
额外检查项
- 确保地面/可点击物体添加了
CollisionShape3D或CollisionPolygon3D,且碰撞形状正确。 - 检查物体的
collision_layer是否与射线的collision_mask匹配(默认均为0b1,若修改过需对应)。
内容的提问来源于stack exchange,提问作者romaji9
相关产品推荐
相关产品推荐

