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

iOS 18 RealityView:如何通过触摸拖拽手势精准移动Entity

RealityView实现2D拖拽3D实体精准跟随手指

在iOS 18的RealityView中,直接通过手势的translation累加固定比例移动实体,会因忽略相机投影关系导致无法精准对应手指位置。正确的做法是通过射线与固定平面相交,将2D触摸点映射到3D空间坐标,让实体跟随交点移动。

完整实现代码

import SwiftUI
import RealityKit

struct ContentView: View {
    @State private var box = Entity()
    // 定义实体拖拽时依附的平面Z坐标(可根据场景调整,默认相机在z=0,此处设为-1让实体处于相机前方)
    private let targetPlaneZ: Float = -1.0

    var body: some View {
        RealityView { content in
            let boxMesh = ModelEntity(
                mesh: .generateBox(size: 0.25),
                materials: [SimpleMaterial(color: .blue, isMetallic: true)]
            )
            box.addChild(boxMesh)
            box.position.z = targetPlaneZ // 让实体初始就在目标平面上
            content.add(box)
        }
        .gesture(dragGesture)
    }

    private var dragGesture: some Gesture {
        DragGesture()
            .onChanged { value in
                guard let realityView = value.view as? RealityView else { return }
                // 获取场景中的相机实体
                let camera = realityView.camera ?? realityView.scene.cameras.first!
                
                // 转换触摸点为RealityKit归一化坐标(原点在左下角)
                let viewSize = realityView.bounds.size
                let normalizedTouchPoint = CGPoint(
                    x: value.location.x / viewSize.width,
                    y: 1 - (value.location.y / viewSize.height) // 翻转y轴适配坐标空间差异
                )
                
                // 生成从相机穿过触摸点的射线
                let ray = camera.generateRay(fromScreenPosition: normalizedTouchPoint, viewportSize: viewSize)
                
                // 计算射线与目标平面的交点
                if let intersection = ray.intersection(withPlaneAt: [0, 0, targetPlaneZ], normal: [0, 0, 1]) {
                    box.position = intersection.position
                }
            }
            .onEnded { _ in
                box.position = [0, 0, targetPlaneZ]
            }
    }
}

// 扩展Ray,实现射线与平面的交点计算
extension Ray {
    func intersection(withPlaneAt planePosition: SIMD3<Float>, normal: SIMD3<Float>) -> (position: SIMD3<Float>, distance: Float)? {
        let denominator = dot(normal, direction)
        guard abs(denominator) > 0.0001 else { return nil } // 射线与平面平行,无交点
        
        let t = dot(planePosition - origin, normal) / denominator
        guard t >= 0 else { return nil } // 忽略相机后方的交点
        
        let intersectionPos = origin + direction * t
        return (intersectionPos, t)
    }
}

关键逻辑说明

  1. 坐标空间适配:SwiftUI触摸坐标原点在顶部,RealityKit屏幕坐标原点在左下角,因此需要翻转y轴的归一化值,确保射线方向正确。
  2. 射线生成:利用相机的generateRay方法,生成从相机位置出发、穿过触摸点的3D射线,这是映射2D到3D的核心。
  3. 平面交点计算:通过数学计算射线与固定Z平面的交点,实体移动到该交点位置,保证手指位置与实体3D位置精准对应。
  4. 初始位置对齐:将实体初始Z坐标设置为目标平面的Z值,避免拖拽时出现位置跳变。

内容的提问来源于stack exchange,提问作者Ostoura

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 21:13:11