创建、控制和管理实体

Jetpack XR SDK 允许您使用 Jetpack SceneCore 创建、控制和管理 Entity 实例,例如3D 模型立体视频以及PanelEntity>。

Jetpack SceneCore 采用两种常见的架构模式来支持 3D 开发:场景图实体-组件系统 (ECS)。

使用场景图创建和控制实体

要在 3D 空间中创建和控制对象,您可以使用 Jetpack SceneCore 的 Session API 来访问场景图。场景图与用户的真实世界对齐,允许您将面板和 3D 模型等 3D 实体组织成层次结构,并保存这些实体的状态。

一旦您获得场景图的访问权限,就可以使用 Jetpack Compose for XR 中的 API 在场景图中创建空间 UI(例如,SpatialPanelOrbiter 实例)。对于 3D 模型等 3D 内容,您可以直接访问 Session。要了解更多信息,请参阅本页上的关于 ActivitySpace

实体组件系统

实体-组件系统遵循组合优于继承的原则。您可以通过附加定义行为的组件来扩展实体的行为,这使您可以将相同的行为应用于不同类型的实体。要了解更多信息,请查看本页上的向实体添加常见行为

关于 ActivitySpace

每个 Session 都有一个随 Session 自动创建的 ActivitySpaceActivitySpace 是场景图中的顶级 Entity

ActivitySpace 代表一个三维空间,具有右手坐标系(x 轴指向右侧,y 轴指向上方,z 轴相对于原点向后)并以米为单位,与真实世界相符。ActivitySpace 的原点有些随意(因为用户可以在真实世界中重置 ActivitySpace 的位置),因此建议将内容相互定位,而不是相对于原点定位。

使用实体

实体是 SceneCore 的核心。用户看到和互动的大部分内容都是实体,代表面板、3D 模型等。

由于 ActivitySpace 是场景图的顶级节点,默认情况下,所有新实体都直接放置在 ActivitySpace 中。您可以通过调用 setParent()addChild() 来沿着场景图重新定位实体。

实体具有一些对所有实体通用的默认行为,例如更改位置、旋转或可见性。特定的 Entity 子类,例如 GltfEntity,具有支持该子类的附加行为。

操作实体

当您更改属于基本 Entity 类的 Entity 属性时,该更改将级联到其所有子项。例如,调整父 EntityPose 会导致其所有子项都具有相同的调整。在子 Entity 中进行更改不会影响其父项。

A Pose 表示实体在 3D 空间中的位置和旋转。位置是一个 Vector3,由 x、y、z 数值位置组成。旋转由 Quaternion 表示。Entity 的位置始终相对于其父实体。换句话说,位置为 (0, 0, 0) 的 Entity 将放置在其父实体的原点。

// Place the entity forward 2 meters
val newPosition = Vector3(0f, 0f, -2f)
// Rotate the entity by 180 degrees on the up axis (upside-down)
val newOrientation = Quaternion.fromEulerAngles(0f, 0f, 180f)
// Update the position and rotation on the entity
entity.setPose(Pose(newPosition, newOrientation))

要更改 Entity 的可见性,请使用 setHidden()

// Hide the entity
entity.setHidden(true)

要调整 Entity 的大小同时保持其整体形状,请使用 setScale()

// Double the size of the entity
entity.setScale(2f)

向实体添加常见行为

您可以使用以下组件向实体添加常见行为

实例化组件必须通过 Session 类中适当的创建方法完成。例如,要创建 ResizableComponent,请调用 ResizableComponent.create()

要将特定组件行为添加到 Entity,请使用 addComponent() 方法。

使用 MovableComponent 使实体可由用户移动

The MovableComponent 允许用户移动 Entity。您还可以指定实体是否可以锚定到水平或垂直表面等表面类型,或桌子、墙壁或天花板等特定语义表面。要指定锚点选项,请在创建 MovableComponent 时指定一组 AnchorPlacement

下面是一个实体示例,它可以移动并锚定到任何垂直表面以及仅限地板和天花板的水平表面。

val anchorPlacement = AnchorPlacement.createForPlanes(
    planeTypeFilter = setOf(PlaneSemantic.FLOOR, PlaneSemantic.TABLE),
    planeSemanticFilter = setOf(PlaneType.VERTICAL)
)

val movableComponent = MovableComponent.create(
    session = session,
    systemMovable = false,
    scaleInZ = false,
    anchorPlacement = setOf(anchorPlacement)
)
entity.addComponent(movableComponent)

当用户移动实体时,scaleInZ 参数会自动调整实体的比例,使其在远离用户时以类似于系统在主空间中缩放面板的方式进行缩放。由于实体组件系统的“级联”特性,父级的比例将影响其所有子级。

使用 ResizableComponent 使实体可由用户调整大小

The ResizableComponent 允许用户调整 Entity 的大小。ResizableComponent 包含视觉交互提示,邀请用户调整 Entity 的大小。创建 ResizeableComponent 时,您可以指定最小或最大尺寸(以米为单位)。您还可以选择在调整大小时指定固定宽高比,以便宽度和高度按比例调整。

使用 ResieableComponent 时,您必须指定一个 ResizeListener 以响应特定的调整大小事件,例如 onResizeUpdateonResizeEnd

下面是一个在 SurfaceEntity 上使用具有固定宽高比的 ResizableComponent 的示例

val resizableComponent = ResizableComponent.create(session)
resizableComponent.minimumSize = Dimensions(177f, 100f, 1f)
resizableComponent.fixedAspectRatio = 16f / 9f // Specify a 16:9 aspect ratio

resizableComponent.addResizeListener(
    executor,
    object : ResizeListener {
        override fun onResizeEnd(entity: Entity, finalSize: Dimensions) {

            // update the size in the component
            resizableComponent.size = finalSize

            // update the Entity to reflect the new size
            (entity as SurfaceEntity).canvasShape = SurfaceEntity.CanvasShape.Quad(finalSize.width, finalSize.height)
        }
    },
)

entity.addComponent(resizableComponent)

使用 InteractableComponent 捕获用户输入事件

The InteractableComponent 允许您捕获来自用户的输入事件,例如当用户与 Entity 交互或悬停在 Entity 上时。创建 InteractableComponent 时,您必须指定一个 InputEventListener 来接收输入事件。当用户执行任何输入操作时,将调用 onInputEvent 方法,并提供 InputEvent 参数中提供的特定输入信息。

有关所有 InputEvent 常量的完整列表,请参阅参考文档

以下代码片段展示了使用 InteractableComponent 的示例,通过右手增大实体大小,通过左手减小实体大小。

val executor = Executors.newSingleThreadExecutor()
val interactableComponent = InteractableComponent.create(session, executor) {
    // when the user disengages with the entity with their hands
    if (it.source == InputEvent.SOURCE_HANDS && it.action == InputEvent.ACTION_UP) {
        // increase size with right hand and decrease with left
        if (it.pointerType == InputEvent.POINTER_TYPE_RIGHT) {
            entity.setScale(1.5f)
        } else if (it.pointerType == InputEvent.POINTER_TYPE_LEFT) {
            entity.setScale(0.5f)
        }
    }
}
entity.addComponent(interactableComponent)