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

ARKit技术问询:如何像GIPHY World应用一样在空中移动物体

Great question! I’ve built similar AR interaction systems before, so let’s walk through exactly how to implement that free-floating object movement and mode switching like GIPHY World does.

Core Logic: How the Two Modes Differ

First, let’s clarify the core difference between your existing plane-based drag and the free-floating mode:

  • Plane Detection Mode: Raycasts against real-world surfaces detected by ARKit/ARCore to anchor object movement.
  • Free-Floating Mode: Ignores real planes entirely, instead using a virtual "target" (either a fixed virtual plane or gesture-based relative tracking) to calculate where the object should move in 3D space.

1. Build the Mode Switching Foundation

Start by creating a state system to manage which mode the app is in, and tie it to a UI control (like a button) for users to toggle:

// Define your mode enum (iOS example; adapt for Android/Unity as needed)
enum ARPlacementMode {
    case planeDetection
    case freeFloating
}

var currentMode: ARPlacementMode = .planeDetection

// Toggle mode with a button
@IBAction func toggleModeTapped(_ sender: UIButton) {
    currentMode = currentMode == .planeDetection ? .freeFloating : .planeDetection
    sender.setTitle(currentMode == .planeDetection ? "Switch to Free Mode" : "Switch to Plane Mode", for: .normal)
}

2. Implement Free-Floating Object Movement

There are two reliable approaches for this—pick the one that fits your use case:

Option 1: Virtual Plane Raycasting (Most Like GIPHY World)

This method creates an invisible virtual plane fixed in front of the camera (e.g., 1.5 meters away) and raycasts against it, just like you do with real planes. It’s intuitive for users because it mimics placing objects on a "virtual table" in mid-air.

func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
    guard let sceneView = gesture.view as? ARSCNView else { return }
    
    switch currentMode {
    case .planeDetection:
        // Your existing plane-based drag logic here
        let touchLoc = gesture.location(in: sceneView)
        if let hitResult = sceneView.hitTest(touchLoc, types: .existingPlaneUsingExtent).first {
            let newPos = hitResult.worldTransform.columns.3
            yourObjectNode.position = SCNVector3(newPos.x, newPos.y, newPos.z)
        }
        
    case .freeFloating:
        let touchLoc = gesture.location(in: sceneView)
        guard let camera = sceneView.pointOfView else { return }
        
        // Define the invisible virtual plane: 1.5m in front of camera, facing the user
        let planeDistance: Float = 1.5
        let planeOrigin = camera.position + camera.worldFront * planeDistance
        let planeNormal = camera.worldFront
        
        // Create a ray from the user's touch point
        guard let ray = sceneView.ray(fromScreenPoint: touchLoc) else { return }
        
        // Calculate where the ray intersects the virtual plane
        let denominator = ray.direction.dot(planeNormal)
        guard abs(denominator) > 0.0001 else { return } // Avoid parallel rays
        
        let t = (planeOrigin - ray.origin).dot(planeNormal) / denominator
        guard t > 0 else { return } // Only care about intersections in front of camera
        
        let intersectionPoint = ray.origin + ray.direction * t
        
        // Smoothly update the object's position
        SCNTransaction.begin()
        SCNTransaction.animationDuration = 0.1
        yourObjectNode.position = intersectionPoint
        SCNTransaction.commit()
    }
}

Option 2: Gesture-Based Relative Tracking

This method tracks the user’s pan gesture on the screen and converts that 2D movement into 3D space relative to the camera’s current position. It’s great for letting users "drag" objects freely without a fixed plane.

private var initialObjectPos: SCNVector3?
private var initialCameraTransform: SCNMatrix4?

func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
    guard let sceneView = gesture.view as? ARSCNView else { return }
    
    switch gesture.state {
    case .began:
        if currentMode == .freeFloating {
            initialObjectPos = yourObjectNode.position
            initialCameraTransform = sceneView.pointOfView?.transform
        }
        
    case .changed:
        guard currentMode == .freeFloating,
              let initialObjPos = initialObjectPos,
              let initialCamTransform = initialCameraTransform else { return }
        
        let currentCamTransform = sceneView.pointOfView?.transform ?? initialCamTransform
        // Calculate how the camera has moved since the gesture started
        let camRotationDelta = currentCamTransform.inverse * initialCamTransform
        
        // Convert screen pan to 3D movement (adjust sensitivity as needed)
        let translation = gesture.translation(in: sceneView)
        let sensitivity: Float = 0.002
        let localDelta = SCNVector3(Float(translation.x) * sensitivity, Float(-translation.y) * sensitivity, 0)
        
        // Convert local camera-space movement to world space
        let worldDelta = localDelta.transformed(by: camRotationDelta)
        
        // Update object position
        yourObjectNode.position = initialObjPos + worldDelta
        
    case .ended, .cancelled:
        initialObjectPos = nil
        initialCameraTransform = nil
        
    default: break
    }
}

3. Seamless Mode Switching Details

To make the transition feel natural:

  • Preserve Object Position: When switching from plane mode to free mode, keep the object in its current 3D position—don’t reset it to the virtual plane.
  • Reset Raycast Targets: Ensure that when in plane mode, your hit tests only check real AR planes; when in free mode, ignore real planes entirely.
  • Visual Feedback: Add a small UI indicator (like an icon) to show which mode is active, so users never feel confused.

4. Polish the Experience

  • Add Distance Adjustment: Let users pinch to zoom the object closer/farther in free mode, or swipe up/down on the screen to adjust the Z-axis position.
  • Collision Detection: If you have multiple objects, add basic collision checks in free mode to prevent objects from overlapping.
  • Smooth Animations: Always use short, subtle animations when updating the object’s position—this makes the movement feel fluid instead of jerky.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:57:11