Godot 4.2 3D手游:如何实现仅触摸拖拽角色时才移动?
解决Godot 4.2 3D移动端仅拖拽角色本体触发移动的问题
优先选射线检测方案(精准适配移动端)
这是实现《Spider Nest》式拖拽最靠谱的方式——只有触摸到角色实体时才触发移动,核心逻辑是用屏幕触摸点发射3D射线,检测是否命中角色,再开启拖拽流程。
实现步骤:
- 给角色节点(比如
CharacterBody3D)添加CollisionShape3D,确保碰撞体的pickable属性处于开启状态(默认已开启)。 - 在角色脚本里监听触摸输入事件,分三步处理:
- 触摸开始:从触摸点发射射线,命中角色就记录触摸ID和触摸点相对于角色的位置偏移
- 拖拽过程:仅响应有效触摸ID的拖拽事件来驱动角色移动
- 触摸结束:清空记录的状态,避免后续误触发
代码示例(角色脚本):
extends CharacterBody3D # 记录当前有效触摸的ID,-1表示无有效触摸 var active_touch_id: int = -1 # 触摸点相对于角色全局位置的偏移 var touch_offset: Vector3 = Vector3.ZERO func _input(event): # 处理触摸按下事件 if event is InputEventScreenTouch and event.pressed: var camera = get_viewport().get_camera_3d() # 生成从屏幕触摸点到3D世界的射线起点和终点 var ray_start = camera.project_ray_origin(event.position) var ray_end = ray_start + camera.project_ray_normal(event.position) * 1000.0 # 检测射线是否命中当前角色 var space_state = get_world_3d().direct_space_state var hit_result = space_state.intersect_ray(RayQueryParameters3D.create(ray_start, ray_end, [self])) if hit_result: active_touch_id = event.index # 计算触摸点映射到角色Y轴高度的世界位置,再算出偏移量 var touch_world_pos = camera.project_position(event.position, global_position.y) touch_offset = global_position - touch_world_pos # 处理拖拽事件,仅响应有效触摸ID的拖拽 elif event is InputEventScreenDrag and event.index == active_touch_id: var camera = get_viewport().get_camera_3d() # 根据当前拖拽位置计算目标世界位置,保持角色Y轴高度不变 var target_world_pos = camera.project_position(event.position, global_position.y) + touch_offset global_position = target_world_pos # 处理触摸松开事件,清空状态 elif event is InputEventScreenTouch and not event.pressed and event.index == active_touch_id: active_touch_id = -1 touch_offset = Vector3.ZERO
Area3D方案(适合简单静态场景)
用Area3D也能实现,但精度不如射线检测——需要把角色的3D碰撞区域映射到屏幕空间,判断触摸点是否在这个屏幕区域内。缺点是相机移动、角色旋转时,屏幕区域的计算容易出现偏差,适合角色不怎么动的简单场景。
代码示例片段:
extends CharacterBody3D var active_touch_id: int = -1 var touch_start_world_pos: Vector3 = Vector3.ZERO func _input(event): if event is InputEventScreenTouch and event.pressed: var camera = get_viewport().get_camera_3d() # 获取角色碰撞体的AABB并转换为屏幕上的两个对角点 var aabb = $CollisionShape3D.shape.get_aabb().translated(global_position) var screen_p1 = camera.unproject_position(aabb.position) var screen_p2 = camera.unproject_position(aabb.position + aabb.size) # 计算屏幕区域的边界 var min_x = min(screen_p1.x, screen_p2.x) var max_x = max(screen_p1.x, screen_p2.x) var min_y = min(screen_p1.y, screen_p2.y) var max_y = max(screen_p1.y, screen_p2.y) # 判断触摸点是否在屏幕区域内 if event.position.x.in_range(min_x, max_x) and event.position.y.in_range(min_y, max_y): active_touch_id = event.index touch_start_world_pos = global_position # 处理拖拽逻辑 elif event is InputEventScreenDrag and event.index == active_touch_id: var camera = get_viewport().get_camera_3d() var drag_world_delta = camera.project_position(event.position, global_position.y) - camera.project_position(event.relative, global_position.y) global_position = touch_start_world_pos + drag_world_delta # 触摸结束清空状态 elif event is InputEventScreenTouch and not event.pressed and event.index == active_touch_id: active_touch_id = -1
额外提示
- 角色的碰撞体大小要适配视觉大小,不要过大导致误触,也不要过小让玩家难摸到
- 如果角色有动画带动碰撞体位移,建议把检测逻辑绑定到角色的根节点,或者让碰撞体跟随动画节点同步位置
- 移动端测试时,可以给检测区域加个小缓冲(比如射线检测时忽略0.1单位内的偏移,或者屏幕区域扩大5个像素),提升触摸容错率
内容的提问来源于stack exchange,提问作者Ash Babayan
相关产品推荐
相关产品推荐

