创建、控制和管理实体

借助 Jetpack XR SDK,您可以使用 Jetpack SceneCore 创建、控制和管理 Entity 实例,例如 3D 模型立体视频PanelEntity

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

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

如需在 3D 空间中创建和控制对象,您可以使用 Jetpack SceneCore 的 Session API 来访问场景图。场景图与用户的真实世界保持一致,可让您将面板和 3D 模型等 3D 实体整理成层次结构,并保持这些实体的状态。

获得场景图的访问权限后,您可以使用 Jetpack Compose for XR 中的 API 在场景图中创建空间界面(例如 SpatialPanelOrbiter 实例)。对于 3D 模型等 3D 内容,您可以直接访问会话。如需了解详情,请参阅本页面上的ActivitySpace 简介

实体组件系统

实体-组件系统遵循组合优于继承的原则。您可以通过附加定义行为的组件来扩展实体的行为,从而将相同的行为应用于不同类型的实体。如需了解详情,请参阅本页面上的向实体添加通用行为

ActivitySpace 简介

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

ActivitySpace 表示一个三维空间,采用右手坐标系(x 轴指向右侧,y 轴指向上方,z 轴指向后方,相对于原点),单位为米,与现实世界相符。ActivitySpace 的原点在某种程度上是任意的(因为用户可以在现实世界中重置 ActivitySpace 的位置),因此建议相对于彼此定位内容,而不是相对于原点定位内容。

处理实体

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

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

实体针对所有实体通用的事物(例如更改位置、旋转或可见性)具有一些默认行为。特定的 Entity 子类(例如 GltfModelEntity)具有支持该子类的其他行为。

操纵实体

如果您更改属于基本 Entity 类的 Entity 属性,相应更改会向下级联到其所有子级。例如,调整父 EntityPose 会导致其所有子项都进行相同的调整。更改子级 Entity 不会影响其父级。

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 使实体可供用户移动

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 使实体可供用户调整大小

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

使用 ResizableComponent 时,您必须指定 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 捕获用户输入事件

借助 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)