本节我们开始初探在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?
?结构体中的定时函数进行指定,包含的值有:
??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?
?会显示两个盒子,盒子下方分别显示一个附属视图,点击盒体会执行上下移动的动画。
示例代码:??GitHub仓库??
其它相关内容请见??虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记??