Code Repo: github.com/xuchi16/vis…
Project Path: github.com/xuchi16/vis…
根本概念
实体组件体系(Entity Component System, ECS)形式是游戏开发中常见的一种形式。RealityKit 也遵从了这一形式。这种形式将使用程序目标分为三种类型:实体(Entities) 、组件(Components) 和体系(Systems) 。
- 实体(Entities)
-
实体是 RealityKit 中的核心人物,任何能够放入场景中的目标,无论是可见的仍是不可见的,都是实体。
-
实体能够是 3D 模型、形状基元、灯光,甚至是像声音发射器或触发体积这样的不可见目标。
-
实体经过增加组件来存储与特定功能相关的附加状况。实体自身包括的特点相对较少,简直一切实体状况都存储在实体的组件上。
- 组件(Components)
-
组件是增加到实体上的模块化构建块,它们标识体系将对哪些实体采取举动,并保护体系依靠的每个实体的状况。
-
组件能够包括逻辑,但约束组件逻辑在验证其特点值或设置初始状况上。
-
组件负责存储实体的特定状况,而体系则运用这些组件来查询和操作实体。
- 体系(Systems)
-
体系包括代码,RealityKit 在每一帧调用这些代码来完成特定类型的实体行为或更新特定类型的实体状况。
-
体系运用组件来存储其实体特定的状况,并查询具有特定组件或组件组合的实体来进行操作。
-
体系一般与一个或多个组件一起作业,例如,游戏中的损伤体系或许会运用健康组件来跟踪每个实体受到的损伤以及它们在被摧毁之前能承受多少损伤。
遵从 ECS 形式的好处在于,它答应在不同的实体中重用组件中的功能性,即便这些实体在承继链上没有共同的先人。这种规划避免了传统面向目标规划形式中或许出现的作业重复,因为每个实体的逻辑只需求计算一次,而不是对每个或许受影响的实体重复计算。
在 RealityKit 中,开发者能够经过创建和装备实体、组件和体系来构建杂乱的 3D 场景和交互。这种模块化的办法使得开发者能够愈加灵敏地构建和保护他们的使用程序。
根本完成
预订程序操控物体移动
依据 ECS 形式,假如想要移动某些物体,步骤如下:
-
需求先界说 Component,其中包括每个移动物体的初始及状况数据
-
界说对应的 System,筛选出需求效果的目标,编写对应的 update 逻辑
-
注册上述界说的 Component 和 System
-
将 Component 增加到需求移动的物体上
在这里我们期望构建一个红色球体,而且能够绕某一点进行圆周运动。一起有一个 Toggle 用来操控运动开端/暂停。
- 按照上述步骤,首先我们先界说 Component,用于记录进行圆周运动的目标所需的初始及状况数据。
import RealityKit
public struct MoveComponent: Component {
public var movementEnabled: () -> Bool
public var speed: Float = 2.0 // Movement peed
public var radius: Float = 0.2 // Radius of the routine
public var angle: Float = 0.0 // Current angle
public var center: SIMD3<Float>
public init(position: SIMD3<Float>) {
movementEnabled = {false}
center = [position.x - radius, position.y, position.z]
}
}
- 界说对应的 System,用于识别效果目标,而且完成圆周运动的逻辑。注意,特定目标的运动状况数据不应该写在 System 逻辑里。
import RealityKit
public struct MoveSystem: System {
static let moveQuery = EntityQuery(where: .has(MoveComponent.self))
public init(scene: Scene) {
}
public func update(context: SceneUpdateContext) {
let entities = context.scene.performQuery(Self.moveQuery)
for entity in entities {
guard var moveComponent = entity.components[MoveComponent.self] else {
continue
}
if moveComponent.movementEnabled() {
// Update angle
moveComponent.angle += moveComponent.speed * Float(context.deltaTime)
// Calculate position
let center = moveComponent.center
let x = cos(moveComponent.angle) * moveComponent.radius + center.x
let y = sin(moveComponent.angle) * moveComponent.radius + center.y
entity.position = SIMD3<Float>(x, y, entity.position.z)
entity.components[MoveComponent.self] = moveComponent
}
}
}
}
- 界说一个
ViewModel
,用于存储需求绑定的变量,如操控物体运动/暂停。
import Foundation
@Observable
class ViewModel {
var enableMovement = false
}
- 注册上述界说的 Component 和 System,一般能够在使用翻开时一致进行注册。这里一起还能够初始化
ViewModel
,而且注入环境。
@main struct ObjectMovementApp: App { @State var model = ViewModel() init() { MoveComponent.registerComponent() MoveSystem.registerSystem() } var body: some Scene { WindowGroup { ContentView() .environment(model) } .defaultSize(CGSize(width: 300, height: 400)) ImmersiveSpace(id: "MovementSpace") { MovementView() .environment(model) } } }
- 将 Component 增加到需求移动的物体上
struct MovementView: View { private let radius: Float = 0.2 @State var programmaticEntity = Entity() @Environment(ViewModel.self) var model; var body: some View { RealityView { content in // Programmatic movement let initPosition = SIMD3<Float>(x: -0.6, y: 1.5, z: -2) var moveComponent = MoveComponent(position: initPosition) moveComponent.movementEnabled = {model.enableMovement} programmaticEntity = ModelEntity( mesh: .generateSphere(radius: radius), materials: [SimpleMaterial(color: .red, isMetallic: false)] ) programmaticEntity.position = initPosition programmaticEntity.components[MoveComponent.self] = moveComponent content.add(programmaticEntity) } } }
手势操控物体移动
假如想要经过手势操控物体移动,相同也需求经过 ECS 形式,增加体系预设的 Component。
相较于刚刚经过程序直接操控物体方位,假如想要经过手势操控物体移动,需求给对应的实体
- 界说实体,在界说 ModelEntity 时,指定磕碰体
collisionShape
及质量mass
,赋予其物理性质。需求注意的是此处物体的质量mass
需求设置为0,否则会由于重力导致物体下落。
// Drag movement
dragEntity = ModelEntity(
mesh: .generateSphere(radius: radius),
materials: [SimpleMaterial(color: .blue, isMetallic: false)],
collisionShape: .generateBox(size: SIMD3<Float>(repeating: radius)),
mass: 0.0
)
- 为实体增加官方库界说的
InputTargetComponent
组件,用于相使用户输入
dragEntity.components.set(InputTargetComponent(allowedInputTypes: .indirect))
- 界说拖拽手势,并经过
.gestrue(_:)
modifier 让对应的物体能够被拖动
var dragGesture: some Gesture {
DragGesture()
.targetedToAnyEntity()
.onChanged{ value in
dragEntity.position =
value.convert(value.location3D, from: .local, to: dragEntity.parent!)
}
}
RealityView { content in
// ...
}
.gesture(dragGesture)
终究效果