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

news/2024/7/20 21:03:03 标签: 空间计算, swiftui, 增强现实, apple vision pro, ios

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

首先是​​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​​时表示动画作用于对象本身。

我们定义了三个动画函数,分别为​​goUp​​​、​​pause​​​和​​goDown​​​,实现在纵坐标上完成上下移动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​​​为蓝色,位于我们的左侧,为方便标识,我们通过​​RealityView​​​的​​AttachmentContentBuilder​​​(即​​attachments​​​参数)来创建附属视图,但附属视图并不会自动添加到​​RealityView​​​的对象上。我们需要显式地进行指定,这里​​attachments.entity​​​中的​​for​​​与​​Attachment​​​的​​id​​​相对应,我们只添加了两个文本视图,并通过​​position​​指定了相对于父对象的位置。

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

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

visionOS<a class=空间计算实战开发教程Day 8 附属视图和动画" height="1200" src="https://img-blog.csdnimg.cn/img_convert/3938260501860354fe6d8d2d145f3dad.jpeg" width="1200" />

示例代码:​​GitHub仓库​​

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


http://www.niftyadmin.cn/n/5257069.html

相关文章

利用冒泡排序了解如何将数组作为参数传递给函数

目录 前言:冒泡排序简介步骤动图演示 错误的冒泡排序函数数组名正确的冒泡排序函数 前言:冒泡排序 简介 冒泡排序是一种简单直观的排序算法。 它重复地访问要排序的数&#xff0c;一次比较两个元素&#xff0c;如果他们的顺序错误就把他们交换过来。访问数需要重复地进行直到…

JAVA面向对象知识点—全面详解

一、方法 1.什么是方法? 方法就是具有一定功能的代码块 2.为什么用方法 划分功能块&#xff08;利于开发和维护&#xff09;可以让代码块重新执行方法也是一种封装( 不问细节&#xff0c;只管调用) 3.方法的介绍 &#xff1a; **方法就是具有一定功能的代码块**注&#x…

【算法每日一练]-结构优化(保姆级教程 篇5 树状数组)POJ3067日本 #POJ3321苹果树 #POJ2352星星 #快排变形

目录 今天知识点 求交点转化求逆序对&#xff0c;每次操作都维护一个y点的前缀和 树的变动转化成一维数组的变动&#xff0c;利用时间戳将节点转化成区间 离散化数组来求逆序对数 先将y排序&#xff0c;然后每加入一个就点更新求一次前缀和 POJ3067&#xff1a;日本 思路&…

【Vue】Vue 响应式初步

Vue 响应式初步 在上一节 Vue 数据劫持中&#xff0c;介绍了 Vue 响应式的实现基础 — 数据劫持&#xff0c;在那篇文章的最后&#xff0c;我们说到&#xff0c;要实现响应式&#xff0c;仅仅实现对数据的劫持还不够&#xff0c;还需要能够对数据的读写做出相应的处理&#xf…

AI全栈大模型工程师(二十八)如何做好算法备案

互联网信息服务算法 什么情况下要备案&#xff1f; 对于B2B业务&#xff0c;不需要备案。 但在B2C领域&#xff0c;一切要视具体情况而定。 如果我们自主训练大型模型&#xff0c;这是必要的。 但如果是基于第三方模型提供的服务&#xff0c;建议选择那些已获得备案并且具有较大…

【华为数据之道学习笔记】3-5 规则数据治理

在业务规则管理方面&#xff0c;华为经常面对“各种业务场景业务规则不同&#xff0c;记不住&#xff0c;找不到”“大量规则在政策、流程等文件中承载&#xff0c;难以遵守”“各国规则均不同&#xff0c;IT能否一国一策、快速上线”等问题。 规则数据是结构化描述业务规则变量…

q2-qt-多线程

是的&#xff0c;Qt框架中提供了专门用于线程池的API。Qt的线程池API位于QtConcurrent命名空间下&#xff0c;以及QThreadPool类中。 QtConcurrent命名空间提供了一些高级的API&#xff0c;可以方便地使用线程池来执行并行任务。其中&#xff0c;QtConcurrent::run()函数可以用…

加班、效率和价值

效率不等于单位时间单位人干的活&#xff0c;而是等于单位时间单位人产出的价值&#xff0c;衡量工作量的难度很大&#xff0c;而如何选择工作重点&#xff0c;挖掘工作价值难度更大。 加班的不可持续在于两点&#xff0c;第一点是对身体和精神的损害&#xff0c;降低内在动力…