[VisionOS] 拆分HelloWorld的功能点 - 地球跟随鼠标拖动

1,006 阅读2分钟

背景

在体验HelloWorld时,很好奇每个功能是怎么实现的,但是这个demo复用了很多功能、数据模型,刚开始理解起来就比较困难。所以我就先从功能点来看,将复用的功能、数据模型都剔除掉,保证单一功能能解藕单独运行。

环境

Xcode:15.1 beta

VisionOS:1.0

梳理功能

graph LR;
    功能点-->A(设置光照);
    style A fill:#bbf,color:#fff
    click A "https://juejin.cn/post/7298690615046651943"
    
    功能点-->B(手势转动地球)
    style B fill:#bbf,color:#fff
    click B "https://juejin.cn/post/7298765809290706983"
    
    功能点-->C(地球自转)
    style C fill:#bbf,color:#fff
    click C "https://juejin.cn/post/7298775642261569575"
    
    功能点-->D(地球跟随鼠标拖动)
    style D fill:#bbf,color:#fff
    click D "https://juejin.cn/post/7299037876637351975"
    
    功能点-->E(卫星围绕地球转动)
    style E fill:#bbf,color:#fff
    click E "https://juejin.cn/post/7300431522255241250"
    
    功能点-->G(沉浸式与窗口之间的切换)
    style G fill:#bbf,color:#fff
    click G "https://juejin.cn/spost/7300816733525901352"

地球跟随鼠标拖动

Nov-08-2023 15-37-18.gif

在这里可以明显看到窗口是有边界的,触碰到边界时,整个模型都很会切掉。如果我们在加载模型后,看不到效果,要考虑下是不是超出窗口了,适当的调整模型的位置,窗口的大小。

import SwiftUI
import RealityKit
import RealityKitContent

struct EarthPlacement: View {
    var body: some View {
        RealityView { content in
            guard let earth = await RealityKitContent.entity(named: "Globe") else {
                return
            }
            content.add(earth)
            earth.setSunlight(intensity: 14)

            earth.scale = SIMD3(repeating: 0.3)
        }
        .placementGestures(initialPosition: Point3D([450,300.0,100.0]))
    }
}

#Preview {
    EarthPlacement()
}

1. 加载3D资源

手势转动地球一样,资源必须要添加一个InputComponent,这样才可以添加手势。

image.png

2. 添加手势

import SwiftUI
import RealityKit

extension View {
    /// Listens for gestures and places an item based on those inputs.
    func placementGestures(
        initialPosition: Point3D = .zero,
        axZoomIn: Bool = false,
        axZoomOut: Bool = false
    ) -> some View {
        self.modifier(
            PlacementGesturesModifier(
                initialPosition: initialPosition,
                axZoomIn: axZoomIn,
                axZoomOut: axZoomOut
            )
        )
    }
}

/// A modifier that adds gestures and positioning to a view.
private struct PlacementGesturesModifier: ViewModifier {
    var initialPosition: Point3D
    var axZoomIn: Bool
    var axZoomOut: Bool

    @State private var scale: Double = 1
    @State private var startScale: Double? = nil
    @State private var position: Point3D = .zero
    @State private var startPosition: Point3D? = nil

    func body(content: Content) -> some View {
        content
            .onAppear {
                position = initialPosition
            }
            .scaleEffect(scale)
            .position(x: position.x, y: position.y)
            .offset(z: position.z)

            // Enable people to move the model anywhere in their space.
            .simultaneousGesture(DragGesture(minimumDistance: 0.0, coordinateSpace: .global)
                .handActivationBehavior(.pinch)
                .onChanged { value in
                    if let startPosition {
                        let delta = value.location3D - value.startLocation3D
                        position = startPosition + delta
                    } else {
                        startPosition = position
                    }
                }
                .onEnded { _ in
                    startPosition = nil
                }
            )

            // Enable people to scale the model within certain bounds.
            .simultaneousGesture(MagnifyGesture()
                .onChanged { value in
                    if let startScale {
                        scale = max(0.1, min(3, value.magnification * startScale))
                    } else {
                        startScale = scale
                    }
                }
                .onEnded { value in
                    startScale = scale
                }
            )
            .onChange(of: axZoomIn) {
                scale = max(0.1, min(3, scale + 0.2))
                startScale = scale
            }
            .onChange(of: axZoomOut) {
                scale = max(0.1, min(3, scale - 0.2))
                startScale = scale
            }
    }
}

手势转动地球类似,添加了DragGesture手势,在onChangedonEnded中改变positionMagnifyGesture放大手势,我暂时是没有用,模拟器也不知道怎么模拟。

3.代码

EarthPlacement.swift