You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Godot 4.2 3D手游:如何实现仅触摸拖拽角色时才移动?

解决Godot 4.2 3D移动端仅拖拽角色本体触发移动的问题

优先选射线检测方案(精准适配移动端)

这是实现《Spider Nest》式拖拽最靠谱的方式——只有触摸到角色实体时才触发移动,核心逻辑是用屏幕触摸点发射3D射线,检测是否命中角色,再开启拖拽流程。

实现步骤:

  1. 给角色节点(比如CharacterBody3D)添加CollisionShape3D,确保碰撞体的pickable属性处于开启状态(默认已开启)。
  2. 在角色脚本里监听触摸输入事件,分三步处理:
    • 触摸开始:从触摸点发射射线,命中角色就记录触摸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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 20:23:13