visionOS空间计算实战开发教程Day 8 附属视图和动画

简介: 通过前面的学习我们已经知道setupContentEntity用于初始化实体对象,getTargetEntity用于根据模型的名称查找到指定模型。addCube(name: String, position: SIMD3<Float>, color: UIColor)中包含三个参数,name用于指定所创建盒子的名称,position用于指定盒子所处的位置,同样是按人所处位置为参照坐标,color用于指定盒体的颜色。

本节我们开始初探在visionOS中添加动画效果,我们的入口文件和ContentViewDay 6中并没有什么区别,所以重点来看ViewModelImmersiveView

首先是ViewModel.swift文件:

import SwiftUI
import RealityKit
class ViewModel: ObservableObject {
    private var contentEntity = Entity()
    func setupContentEntity() -> Entity {
        return contentEntity
    }
    func getTargetEntity(name: String) -> Entity? {
        return contentEntity.children.first { $0.name == name }
    }
    func addCube(name: String, position: SIMD3<Float>, color: UIColor) -> ModelEntity {
        let entity = ModelEntity(
            mesh: .generateBox(size: 0.5, cornerRadius: 0),
            materials: [SimpleMaterial(color: color, isMetallic: false)],
            collisionShape: .generateBox(size: SIMD3<Float>(repeating: 0.5)),
            mass: 0.0
        )
        entity.name = name
        entity.position = position
        entity.components.set(InputTargetComponent(allowedInputTypes: .indirect))
        entity.components.set(CollisionComponent(shapes: [ShapeResource.generateBox(size: SIMD3<Float>(repeating: 0.5))], isStatic: true))
        entity.components.set(HoverEffectComponent())
        contentEntity.addChild(entity)
        return entity
    }
    func playAnimation(entity: Entity) {
        let goUp = FromToByAnimation<Transform>(
            name: "goUp",
            from: .init(scale: .init(repeating: 1), translation: entity.position),
            to: .init(scale: .init(repeating: 1), translation: entity.position + .init(x:0 ,y: 0.4, z: 0)),
            duration: 0.2,
            timing: .easeOut,
            bindTarget: .transform
        )
        let pause = FromToByAnimation<Transform>(
            name: "pause",
            from: .init(scale: .init(repeating: 1), translation: entity.position + .init(x:0 ,y: 0.4, z: 0)),
            to: .init(scale: .init(repeating: 1), translation: entity.position + .init(x:0 ,y: 0.4, z: 0)),
            duration: 0.1,
            bindTarget: .transform
        )
        let goDown = FromToByAnimation<Transform>(
            name: "goDown",
            from: .init(scale: .init(repeating: 1), translation: entity.position + .init(x:0 ,y: 0.4, z: 0)),
            to: .init(scale: .init(repeating: 1), translation: entity.position),
            duration: 0.2,
            timing: .easeOut,
            bindTarget: .transform
        )
        let goUpAnimation = try! AnimationResource.generate(with: goUp)
        let pauseAnimation = try! AnimationResource.generate(with: pause)
        let goDownAnimation = try! AnimationResource.generate(with: goDown)
        let animation = try! AnimationResource.sequence(with: [goUpAnimation, pauseAnimation, goDownAnimation])
        entity.playAnimation(animation, transitionDuration: 0.5)
    }
}

通过前面的学习我们已经知道setupContentEntity用于初始化实体对象,getTargetEntity用于根据模型的名称查找到指定模型。addCube(name: String, position: SIMD3<Float>, color: UIColor)中包含三个参数,name用于指定所创建盒子的名称,position用于指定盒子所处的位置,同样是按人所处位置为参照坐标,color用于指定盒体的颜色。

接下来就是本节的一个重要的知识点了,playAnimation用于创建动画效果,传入的参数entity就是要实现动画效果的模型。

结构体FromToByAnimation可用于实现实体对象和场景的动画,方式为逐渐修改参数值。from值表示动画属性的起始值,to表示动画结束时的属性值,也可以通过by值让框架来计算动画结束时的值。duration顾名思义是指动画的时长,timing参数通过AnimationTimingFunction结构体中的定时函数进行指定,包含的值有:

  • default:生成默认过渡曲线的定时函数,不指定时此即为默认值。
  • linear:生成线性过渡效果的定时函数。
  • easeIn:生成淡入效果的定时函数。
  • easeOut:生成淡出效果的定时函数。
  • easeInOut:生成淡入淡出效果的定时函数。

bindTarget参数指定为tranform时表示动画作用于对象本身。

我们定义了三个动画函数,分别为goUppausegoDown,实现在纵坐标上完成上下移动0.4个单位的动画效果。接下来通过这三个动画定义来生成动画资源并指定动画的顺序,最后对实体对象调用playAnimation来播放动画,这里的transitionDuration参数与三个动画函数中用时的总和相同。

接下来是我们的ImmersiveView:

import SwiftUI
import RealityKit
struct ImmersiveView: View {
    @State var model = ViewModel()
    @State var cube1 = ModelEntity()
    @State var cube2 = ModelEntity()
    var body: some View {
        RealityView { content, attachments in
            content.add(model.setupContentEntity())
            cube1 = model.addCube(name: "Cube1", position: SIMD3<Float>(x: 1, y: 1, z: -2), color: .red)
            cube2 = model.addCube(name: "Cube2", position: SIMD3<Float>(x: -1, y: 1, z: -2), color: .blue)
            if let attachment = attachments.entity(for: "cube1_label") {
                attachment.position = [0, -0.35, 0]
                cube1.addChild(attachment)
            }
            if let attachment = attachments.entity(for: "cube2_label") {
                attachment.position = [0, -0.35, 0]
                cube2.addChild(attachment)
            }
        } attachments: {
            Attachment(id: "cube1_label") {
                Text("Cube1")
                    .font(.system(size: 48))
            }
            Attachment(id: "cube2_label") {
                Text("Cube2")
                    .font(.system(size: 48))
            }
        }
        .gesture(
            SpatialTapGesture()
                .targetedToEntity(cube1)
                .onEnded { value in
                    print(value)
                    model.playAnimation(entity: cube1)
                }
        )
        .gesture(
            SpatialTapGesture()
                .targetedToEntity(cube2)
                .onEnded { value in
                    print(value)
                    model.playAnimation(entity: cube2)
                }
        )
    }
}

cube1为红色,位于我们的右侧,cube2为蓝色,位于我们的左侧,为方便标识,我们通过RealityViewAttachmentContentBuilder(即attachments参数)来创建附属视图,但附属视图并不会自动添加到RealityView的对象上。我们需要显式地进行指定,这里attachments.entity中的forAttachmentid相对应,我们只添加了两个文本视图,并通过position指定了相对于父对象的位置。

接着通过SpatialTapGesture指定在点击相应盒子时执行前面所定义的playAnimation

运行应用,点击Show ImmersiveSpace会显示两个盒子,盒子下方分别显示一个附属视图,点击盒体会执行上下移动的动画。


示例代码:GitHub仓库

其它相关内容请见虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记

相关文章
|
机器学习/深度学习 文字识别 数据挖掘
Visionpro从小白到大佬,第一章了解工具名称和用途
Visionpro从小白到大佬,第一章了解工具名称和用途
3153 0
Visionpro从小白到大佬,第一章了解工具名称和用途
|
搜索推荐
Gitlab----设置页面语言为简体中文
Gitlab----设置页面语言为简体中文
4673 0
Gitlab----设置页面语言为简体中文
|
12月前
|
JavaScript 数据管理 编译器
揭秘 ArkTS 的五大优势:如何让鸿蒙系统开发更高效、更简单?
【10月更文挑战第18天】ArkTS是专为鸿蒙系统设计的开发语言,结合了TypeScript的类型系统,并在分布式开发、UI开发、性能优化和API支持等方面进行了优化。它提供了一系列专门的API和语法糖,简化多设备协同开发,支持高效能和低功耗,助力开发者充分利用鸿蒙系统的分布式架构和强大功能。
847 5
|
11月前
|
机器学习/深度学习 传感器 人工智能
2024.11|全球具身智能的端到端AI和具身Agent技术发展到哪里了
2024年,具身智能领域取得显著进展,特别是在端到端AI控制系统和多模态感知技术方面。这些技术不仅推动了学术研究的深入,也为科技公司在实际应用中带来了突破。文章详细介绍了端到端AI的演化、自监督学习的应用、多模态感知技术的突破、基于强化学习的策略优化、模拟环境与现实环境的迁移学习、长程任务规划与任务分解、人机协作与社会交互能力,以及伦理与安全问题。未来几年,具身智能将在多模态感知、自监督学习、任务规划和人机协作等方面继续取得重要突破。
609 2
|
12月前
|
前端开发 JavaScript 中间件
前端全栈之路Deno篇(四):Deno2.0如何快速创建http一个 restfulapi/静态文件托管应用及oak框架介绍
Deno 是由 Node.js 创始人 Ryan Dahl 开发的新一代 JavaScript 和 TypeScript 运行时,旨在解决 Node.js 的设计缺陷,具备更强的安全性和内置的 TypeScript 支持。本文介绍了如何使用 Deno 内置的 `Deno.serve` 快速创建 HTTP 服务,并详细讲解了 Oak 框架的安装和使用方法,包括中间件、路由和静态文件服务等功能。Deno 和 Oak 的结合使得创建 RESTful API 变得高效且简便,非常适合快速开发和部署现代 Web 应用程序。
448 2
|
12月前
|
存储 人工智能 自然语言处理
Github上的十大RAG(信息检索增强生成)框架
信息检索增强生成(RAG)是一种结合了检索系统和生成模型优势的技术,能够显著提升大型语言模型的性能。RAG通过从外部知识库中检索相关信息,增强模型的输入,从而生成更加准确、符合上下文、实时更新的响应。GitHub上涌现出多个开源RAG框架,如Haystack、RAGFlow、txtai等,每个框架都有独特的功能和特性,适用于不同的应用场景。这些框架不仅提高了模型的准确性和可靠性,还增强了过程的透明度和可解释性。
1361 2
|
弹性计算 Kubernetes Linux
如何使用minikube搭建k8s集群
如何使用minikube搭建k8s集群
|
Kubernetes Java Linux
轻量级日志系统Loki原理简介和使用(3)
轻量级日志系统Loki原理简介和使用(3)
1147 0
轻量级日志系统Loki原理简介和使用(3)
|
API Python
Blender导出带透明贴图的gltf模型
Blender导出带透明贴图的gltf模型
548 0
Blender导出带透明贴图的gltf模型
|
安全 Ubuntu Linux
在Docker中,镜像内没有curl,kill,ipconfig等指令如何添加?
在Docker中,镜像内没有curl,kill,ipconfig等指令如何添加?