了解RealityKit中各个部分的关系。
概述
RealityKit是一个用于构建应用程序、游戏和其他沉浸式体验的3D框架。尽管它是用面向对象的语言构建的,并使用面向对象的设计原则,但RealityKit的架构避免了大量使用组合的方式------其中对象是通过添加持有对其他对象的引用的实例变量来构建的------而是采用了一种基于实体组件系统(Entity Component System,ECS)的模块化设计,将应用程序对象分为三种类型之一。
遵循ECS范例使您能够在许多不同的实体中重复使用包含在组件中的功能,即使它们具有非常不同的继承关系。即使两个对象除了Entity之外没有共同的祖先,您也可以将相同的组件添加到它们两者,并赋予它们相同的行为或功能。
从实体开始
实体是RealityKit的核心角色。您可以放入场景的任何对象,无论是否可见,都是实体,必须是Entity的后代。实体可以是3D模型、形状基元、灯光,甚至是不可见的项目,如声音发射器或触发体积。向实体添加组件以使它们存储与特定类型功能相关的附加状态。实体本身包含相对较少的属性:几乎所有实体状态都存储在实体的组件上。
RealityKit提供了许多实体类型,用于表示不同类型的对象。例如,ModelEntity表示3D模型,比如从.usdz或.reality文件导入的模型。这些提供的实体本质上只是已经添加了某些组件的实体。例如,向Entity的一个实例添加ModelComponent
将导致具有与ModelEntity
相同功能的实体。
向实体添加组件
组件是您添加到实体的模块化构建块;它们标识系统将对哪些实体进行操作,并维护系统所依赖的每个实体的状态。组件可以包含逻辑,但将组件逻辑限制在验证其属性值或设置其初始状态的代码上。对于影响实体行为或可能在每一帧更改它们状态的任何逻辑,请使用系统。例如,要向实体添加可访问性信息,请添加AccessibilityComponent
,并用可访问性系统所需的信息填充其字段,比如将VoiceOver读取的描述放入其标签属性。
请注意,一个实体一次只能持有一个特定类型的组件的副本。因此,例如,您不能向一个实体添加两个可访问性组件。如果您向已经具有可访问性组件的实体添加了一个新组件,则新组件将替换之前的组件。
创建系统以实现实体行为
系统(System)
包含了RealityKit在每一帧调用以实现特定类型的实体行为或更新特定类型实体状态的代码。系统使用组件来存储其特定于实体的状态,并通过查找具有特定组件或组件组合的实体来查询要操作的实体。
例如,一个游戏可能有一个损伤系统,监视并更新每个可受损或可被销毁的实体的健康状况。系统通常与一个或多个组件一起工作,因此损伤系统可能使用健康组件来跟踪每个实体受到多少损伤以及每个实体在被销毁之前能够承受多少损伤。它还可能与其他组件互动。例如,一个实体可能具有提供给该实体保护的护甲组件,损伤系统还需要使用该组件中存储的状态。
每一帧,损伤系统查询具有健康组件的实体,并根据应用程序的当前状态更新这些实体组件上的值。如果一个实体受到了过多的损伤,系统可能会触发特定的动画或将实体从场景中移除。
在系统中编写实体逻辑可以避免重复工作。使用传统的面向对象设计模式,这种类型的逻辑通常会存在于实体类上,往往会导致相同的计算多次执行,每个受影响的实体都要进行一次计算。无论这种计算潜在地影响多少个实体,系统只需要执行一次计算。
在场景中为实体实施系统
使用Entity Component System(ECS)可以在RealityKit场景中为实体定义和应用逻辑。系统特别适用于实现影响场景中多个实体的行为,例如用于表示鸟群的一群实体的集群行为。在传统的面向对象方法中,您通过在实体类上编写代码来实现实体的行为,该代码在每一帧中在该类的每个实例上运行。如果场景中有大量实体,这种方法可能效率低下,因为RealityKit必须在每一帧中调用每个实体的更新方法。特别是如果一个实体的逻辑依赖于其他实体包含的状态,这一点尤为重要。
使用系统,RealityKit每帧仅调用每个系统的一个更新方法,而不是在每一帧中为每个实体调用一个更新方法。系统每帧迭代所有相关的实体,并根据需要更新它们的状态。以下是如何实现自己的系统。
创建一个系统类
创建一个遵循System协议的类,实现两个方法:init(scene:)
和 update(context:)
。在init(scene:)中执行系统的设置,如果您的系统不需要任何设置,则添加一个空的实现。在update(context:)中添加运行系统所需的逻辑,RealityKit会自动在每一帧调用该方法。
swift
class MySystem: System {
required init(scene: Scene) {
// 执行所需的初始化或设置。
}
func update(context: SceneUpdateContext) {
// RealityKit会自动在每一帧调用此方法。
}
}
警告:
由于RealityKit在每一帧中调用每个系统的update(context:)方法,请勿在该方法中执行不必要的工作。如果您的update(context:)方法花费很长时间才返回,可能会对应用程序的帧速率产生负面影响。
使用实体查询检索实体
为了有效地从场景中检索实体,请使用EntityQuery
,您可以使用它来获取所有实体,或仅获取与您的系统相关的实体子集。虽然一些系统对场景中的每个实体进行操作,但大多数系统只对定义的子集进行操作,通常是基于实体的组件。例如,物理模拟系统仅需要在参与场景物理模拟的实体上操作,渲染系统仅需要在可见的实体上操作。要检索场景实体的子集,请创建一个包含您的条件的QueryPredicate
,并在创建实体查询时将该谓词传递给初始化器。
将您的查询创建为系统的静态属性,除非您的查询条件在帧之间发生变化。如果条件在帧之间发生变化,请在update方法中创建查询。在update(context:)方法中使用实体查询,以迭代您的系统影响的所有实体。以下是一个系统如何迭代具有特定组件的所有实体的示例。
swift
struct MyComponent: Component {
// 可选,可以在这里放置所需的状态。
}
class MySystem: System {
// 定义一个查询,以返回所有具有MyComponent的实体。
private static let query = EntityQuery(where: .has(MyComponent.self))
required init(scene: Scene) { }
func update(context: SceneUpdateContext) {
context.scene.performQuery(Self.query).forEach { entity in
// 在此对每个实体进行每帧更改。
}
}
}
在Reality Composer Pro中创建和添加组件
如果在项目中使用了Reality Composer Pro包,您可以在编辑器中选择实体,单击"添加组件"按钮,然后选择"新建组件"选项来创建新的组件。这将为新组件创建一个Swift模板,然后您可以在Xcode中编辑该模板。
要将自定义组件添加到实体上,请在您的Reality Composer Pro包中的文件中定义自定义组件。选择要将自定义组件添加到的实体,单击"添加组件"按钮,然后选择您的自定义组件的名称。然后,该实体将根据与查询自定义组件相同的系统做出响应和行为。
指定系统依赖关系
如果一个系统依赖于另一个系统以便正常运行,或者如果需要为多个系统指定更新顺序,请在系统中声明一个dependencies
数组。要告诉RealityKit一个依赖关系必须在您的系统之前更新,请使用SystemDependency.before(_:)
枚举案例,并将另一个系统作为参数传递。对于必须在您的系统之后更新的依赖关系,请使用SystemDependency.before(_:)
。
php
class SystemB: RealityKit.System {
static var dependencies: [SystemDependency] {
[.after(SystemA.self), // 在SystemA之后运行SystemB。
.before(SystemC.self)] // 在SystemC之前运行SystemB。
}
}
注册系统
您不需要手动创建System实例,RealityKit会为您创建,但前提是在显示应用程序的ARView之前通过调用其registerSystem()
方法告诉RealityKit您的系统。一旦您注册了系统,RealityKit会自动为每个活动场景创建系统的实例,然后在每一帧中反复调用其update(context:)方法。
scss
MySystem.registerSystem()