背景
在体验HelloWorld时,很好奇每个功能是怎么实现的,但是这个demo复用了很多功能、数据模型,刚开始理解起来就比较困难。所以我就先从功能点来看,将复用的功能、数据模型都剔除掉,保证单一功能能解藕单独运行。
环境
Xcode:15.1 beta
VisionOS:1.0
梳理功能
卫星围绕地球转动
这里虽然只展示了卫星运动,其实月球和它一样,只是去掉了轨迹。
swift
import SwiftUI
import RealityKit
struct SatelliteAroundEarth: View {
@State private var earthEntity: EarthEntity?
var body: some View {
RealityView { content in
let earth = await EarthEntity(name: "")
earth.setSunlight(intensity: 14)
content.add(earth)
self.earthEntity = earth
earth.update()
}
}
init() {
TraceComponent.registerComponent()
TraceSystem.registerSystem()
}
}
#Preview {
SatelliteAroundEarth()
}
这个是HelloWorld里面最有意思的一个场景,地球在自转,一颗卫星围绕地球运行,后面拖着长长的白色轨迹。
1. 资源
和地球自转一样,一个普通的资源就可以了,不用在Reality Composer Pro
里面添加任何组件。
2. 转动
2.1 原理
如果要理解卫星怎么围绕地球转动,首先要理解Entity
。
Entity
我理解,就是一个3D的容器,它既可以加载一个本地的资源,也可以addChild
添加其他Entity
,类似UIView
。
旋转很好理解,在地球自转里面,我们已经自定义了一个RotationComponent
绕Y轴旋转的组件,Entity.components
添加组件就可以轻松实现旋转。
举个例子,如下图,Entity1
添加了一个Y轴旋转的组件,那么Entity1
就会围绕Y轴开始旋转。
Entity1
添加一个Entity2
,如果不改变Entity2
的位置,默认两个原点都是[0,0,0],也就是Entity1
和Entity2
同时往相同方向旋转,视觉上看起来是重叠的。
如果想要让Entity2
围绕Entity1
旋转呢?只需要给Entity2
设置一个Z轴上的偏移量(默认单位m),[0,0.1,0]。还是把Entity1
、Entity2
看成一个整体,这个整体是围绕Entity1
的原点做Y轴旋转,那么视觉上看起来就是Entity2
围绕Entity1
旋转。
2.2 实现一个简单的围绕旋转
Swift
import SwiftUI
import RealityKit
import RealityKitContent
struct EarthAround: View {
var body: some View {
RealityView { content in
guard let earth = await RealityKitContent.entity(named: "Earth") else {
return
}
let orbit = Entity()
// 添加一个Y轴旋转
orbit.components.set(RotationComponent(speed: 0.5))
content.add(orbit)
// 为了让地球更立体,添加光线
earth.setSunlight(intensity: 14)
// 地球模型太大,适当的缩放,避免超出窗口
earth.scale = SIMD3(repeating: 0.1)
// 设置Z轴偏移
earth.position = [0,0,0.1]
orbit.addChild(earth)
}
}
init() {
RotationComponent.registerComponent()
RotationSystem.registerSystem()
}
}
#Preview {
EarthAround()
}
用刚才的图理解一下,就是这样的。
2.3 自定义卫星组件
swift
import SwiftUI
import RealityKit
import RealityKitContent
class SatelliteEntity: Entity {
private var satellite = Entity()
private let box = Entity()
private let orbit = Entity()
init(_ configuration: Configuration) async {
super.init()
guard let satellite = await RealityKitContent.entity(named: configuration.name) else { return }
self.satellite = satellite
orbit.components.set(RotationComponent(speed: 0))
// 在Y轴上的旋转角度
orbit.orientation = .init(angle: Float(configuration.initialRotation.radians), axis: [0,1,0])
self.addChild(orbit)
orbit.addChild(box)
box.addChild(satellite)
}
@MainActor required init() {
super.init()
}
func update(anchor: Entity, configuration: Configuration, speed: Float){
// 在Z轴上的旋转角度,可以调整卫星与赤道的夹角
let newOrientation = simd_quatf(angle: Float(configuration.inclination.radians), axis: [0, 0, 1])
orientation = newOrientation
// Y轴自旋转
if var rotation: RotationComponent = orbit.components[RotationComponent.self]{
rotation.speed = configuration.speedRatio * speed
orbit.components[RotationComponent.self] = rotation
}
// 卫星所在box,缩放、位置
box.scale = SIMD3(repeating: configuration.scale)
box.position = [0,0,configuration.altitude]
// 卫星更新轨迹
satellite.updateTrace(anchor: anchor,
width: configuration.traceWidth,
isVisible: configuration.isTraceVisible,
isPaused: false)
}
}
那这里就稍微复杂一点了,里面涉及到的Entity
、Component
比较多。
白色轨迹组件TraceComponent
就没有深究了,里面涉及到MeshResource
、TextureResource
,目前还不是很懂。
2.4 卫星围绕地球转动
swift
import SwiftUI
import RealityKit
import RealityKitContent
class EarthEntity: Entity {
/// The model that draws the Earth's surface features.
private var earth: Entity = Entity()
/// A container for artificial satellites.
private var satellites = Entity()
init(name: String) async {
super.init()
guard let earth = await RealityKitContent.entity(named: name) else {return}
self.earth = earth
await satellites = SatelliteEntity(.orbitSatelliteDefault)
self.addChild(earth)
self.addChild(satellites)
update()
}
@MainActor required init() {
super.init()
}
func update(){
// 添加地球自转
if var rotation: RotationComponent = earth.components[RotationComponent.self] {
rotation.speed = 0.1
earth.components[RotationComponent.self] = rotation
} else {
earth.components.set(RotationComponent(speed: 0.1))
}
// 更新卫星的数据
(satellites as? SatelliteEntity)?.update(anchor: earth, configuration: .orbitSatelliteDefault, speed: 0.1)
// 目前只用到了缩放
move(
to: Transform(
scale: SIMD3(repeating: 0.3),
rotation: orientation,
translation: .zero),
relativeTo: parent)
}
}
目前是把所有逻辑都封装在EarthEntity
,在外面使用就比较简单。
代码
都可以单独运行
2.2 实现一个简单的围绕旋转运行EarthAround.swift
2.4 卫星围绕地球转动 运行SatelliteAroundEarth.swift
,里面也包含了月球。
注意 :只要理解了卫星围绕地球的原理,其实就可以复用到月球,所以卫星、月球的代码放在一起展示。当然也一个单独的月球围绕的Demo,逻辑是一样的。