2019年WWDC的《 Session 609 - Building AR Experiences with Reality Composer 》 主要内容速览:
- Reality Composer 简介
- 构建场景
- 添加行为
- 使用物理效果
- 构建应用程序
Reality Composer 简介
Reality Composer 是苹果新推出的 AR 和 3D 内容创作工具,支持 Mac(与 Xcode 集成) 和 iOS 平台。苹果声称他的特点有:
- 进入 AR 和 3D 的良好开端
- 布局和视觉化预览
- 自带内容库
- 交互简单
- Xcode集成
用 Reality Composer 创建一个简单的 AR 应用只需要 4 步:构建场景、添加动作、使用物理效果、构建应用程序。
示例 Demo 展示的动画效果如下:启动后就开始旋转,镜头接近就跳动,手指点击跳动,点击播放 usdz 动画,点击播放各种强调动画
还有一个例子,是一个小岛的展示,点击开始后会播放大海的声音,鸟儿开始飞翔。再点击出现的直升机,它也会按照固定路线飞起来。另外,点击各个点的标识,会展示出景点名称和图片
需要说明的是,整个场景中,只有小岛和直升机是导入的外部素材,其他都是用自带素材和动作生成的。
比如,这一排小鸟,其实只是用几个三角形拼接起来的,再加上启动翅膀的动画,和环绕岛飞行的动画就可以了。因为它们实在太小了,而且创建起来相当麻烦。
下面展示的是,直升机出现的动画
构建场景
先说一下什么是场景(Scene)
场景剖析
一个场景文件内部,实际包含了锚点、物体、行为、物理世界等
锚定场景
创建时要选择一种锚点类型。如果是在桌面上进行的游戏,就用Horizontal类型。如果是在墙面上玩的游戏,就用Vertical类型。如果是图片锚点,就用Image类型。如果是人脸面部增强效果,就用Face类型。
创建场景
首先,点击**+**号按钮,添加一个球体
然后长按复制出三个,拖动到图中位置,并缩放改变大小
然后添加文字和箭头。先添加一个文字:Autumn
再添加箭头,你会注意到:随着镜头位置的移动,箭头周围的圆环也会改变,始终保持与镜头垂直的方向
接下来,双击锁定操纵圆环的位置,然后点击左上方,倒数第 2 个按钮,使用吸附对齐功能,将箭头与太阳和文字对齐。再点击左上方最后一个按钮,编辑物体,改变箭头的形状和比例
最后,替换太阳和地球,并将月球设置成灰色。需要说明一下,替换后,原来已经设置的动画和各种属性,也会保留,不用重新设置。
这样,基本的场景就搭建完成了。剩余的部分大家可以自行美化完善。
添加动作
动作
动作有两部分组成:触发器和动作序列
触发器
共有 5 种触发器:
- Start:场景一启动就触发
- Tap:用户点击后触发
- Proximity:摄像机/手机接近后触发
- Collision:两个物体碰撞后触发
- Notification:代码通知触发(放在最后与 Xcode集成部分介绍)
动作序列
主要有动作组(如下图 Action 2 与 Action 3),与动作循环
独占性动作序列
所谓独占性动作序列,就是一次只能播放一个的动作序列。
当一个独占性动作序列启动时,已经在播放中的独占性动作序列会被暂停,而非独占性则不受影响。
反之,当一个非独占性动作序列启动时,其它独占性、非独占性动作序列都不受影响。
可见性动作
是指物体出现和消失时的动作
动画动作
有强调动画,旋转动画,环绕动画,usdz 动画
移动动作与面向(Look At)动作
有相对移动与绝对移动。另外 Look At 可以记物体始终朝向摄像机(即手机或其他 AR 设备)
音频动作
音频有三种:
- Play Sound:从 3D 物体发出的声音,即有近大远小效果,场景回声效果,物体遮挡效果
- Play Ambient:场景中的环境音,受到场景中物体的影响
- Play Music:从手机中发出的音乐,完成不受 AR 场景的影响
下面还要给场景添加各种动作,包括太阳自转,地球自转,地球公转,月球绕地球转,箭头的显示与隐藏等
使用物理效果
场景搭建完成后,就需要添加一些物理效果了。
如下图,包括碰撞,重力,等
物理材质
首先要选择的是物理材质,自带支持 6 种物理材质如下:
力
碰撞
默认情况下,物体是没有碰撞效果的。如果只开启碰撞,则物体可以互相碰撞,但不会受到碰撞的影响。想要真实的物理效果,还需要开启模拟(Simulates)
构建应用程序
真正构建一个 app,需要 Xcode 的协作。共有三种方式:
- 直接用 Xcode的 AR 或游戏模版生成 app
- 用 Reality Composer 创建一个新工程
- 从 Reality Composer 导出 Reality File 然后添加到 app 中
下面我们着重介绍后两种
Reality Composer Project
Reality Composer Project 是:
- Reality Composer 的项目文件
- 包含在 ReaityKit AR 和游戏模版中
- 可以在 Xcode 中预览
- Xcode 自动输出为 Reality File(在 Xcode 中 build 时自动生成)
Reality文件
Reality File 包含了用于渲染和模拟的所有数据,且对 RealityKit 进行了专门优化。它可以:
- 从 Reality Composer 中导出
- 用 Xcode 中自动输出
- 在应用中被直接引用
- 在 AR Quick Look 中预览 更多详情可以参考Advances in AR Quick Look
Reality File 结构如下,包含了多个场景(Scene),和内部的 3D 物体模型
而实际上,在 RealityKit 和 Xcode 中,场景就是锚点(Anchor),而 3D 物体就是实体(Entity)
Xcode 中的代码生成
当有了 Relity 文件后,实际上已经可以通过代码和字符串来访问里面的 3D 物体了,但是为了让 Reality 文件更好用,Xcode 还会自动生成 Swift 类文件
那么这个自动生成的 Swift 类文件中包含什么呢?是一些 app 相关的 API,如:
- 场景(Scene)
- 已命名的实体(Named entities)命名见下图
- 通知动作
- 通知触发器
转换前后的对应关系如下图:
同时苹果也提供两种方法来加载 Reality 文件,首先是同步方法加载 Reality 文件:
let seasonsChapter = try? SolarSystem.loadSeasonsChapter()
// Use the loaded anchor here
异步加载方法:
SolarSystem.loadSeasonsChapterAsync { result in
switch result {
case .success(let anchor):
// Use loaded anchor here
case .failure(let error):
// Handle failure
}
}
访问实体(Entity):
// Load Reality File anchor, `seasonsChapter`, above...
let sun = seasonsChapter.sun
let earth = seasonsChapter.earth
let moon = seasonsChapter.moon
// Load the “Seasons Chapter” scene from the “SolarSystem” Reality File
let seasonsChapter = try! SolarSystem.loadSeasonsChapter()
// Add the seasons chapter anchor into the scene
arView.scene.anchors.append(seasonsChapter)
以上是将 Reality 文件拖动到 Xcode 中,自动生成相关 Swift 类的加载方法。如果一个 Reality 已经在 Bundle 中,且没有 Swift 文件,或者是从网络下载的,那要怎么处理呢?看下面 同步加载方法:
guard let url = Bundle.main.url(forResource: "SolarSystem", withExtension: "reality") else { return
}
let anchor = try? Entity.loadAnchor(contentsOf: url, withName: "SeasonsChapter") // Use the loaded anchor here...
异步加载:
guard let url = Bundle.main.url(forResource: "SolarSystem", withExtension: "reality") else { return
}
let loadRequest = Entity.loadAnchorAsync(contentsOf: url, withName: "SeasonsChapter") _ = loadRequest.sink(receiveCompletion: { completion in
// Handle completion state
}, receiveValue: { anchor in
// Use loaded anchor here
})
// Load Reality File anchor above...
let sun = anchor.findEntity(named: "Sun")
let earth = anchor.findEntity(named: "Earth")
let moon = anchor.findEntity(named: "Moon")
// Use fetched entities below...
通知动作
前面讲触发器,特地强调了通知动作和通知触发器是用代码来实现的。
- 通知动作是在 Reality Composer 中预先建立的
- 会在动作序列中被调用
- 可以在 app 代码中用闭包来设置
- 可以在代码中通过名称来访问
seasonsChapter.actions.displayEarthDetails.onAction = { entity in
// Display details about Fall
}
需要说明的是,通知动作和通知触发器也是包含在生成 Swift 文件中的
通知触发器
通知触发器也是类似,它可以用来自定义触发条件,用代码决定何时触发
- 通知触发器是在 Reality Composer 中预先建立的
- 会启动动作序列
- 可以在 app 代码中发送
- 可以在代码中通过名称来访问
seasonsChapter.notifications.showGoldStar.post()
// Replaces targets in the action sequence named `originalTarget.name` with `newTarget`
seasonsChapter.notifications.showGoldStar.post(overrides: [originalTarget.name: newTarget])
Demo
此处继续上面的例子,完善加载逻辑
func loadSizeChapter() {
SolarSystemLesson.loadSizeChapterAsync { (loadedAnchor, error) in
guard let sizeAnchor = loadedAnchor, error == nil else {
return
}
// 为点击触发器设置碰撞形状
sizeAnchor.genrateCollisionShapes(recursive: true)
// 添加锚点到场景中
self.arView.scene.anchors.append(sizeAnchor)
self.sizeChapter = sizeAnchor
self.setupNotifyActions() // 用来更新 UI 描述,暂未实现
}
}
运行之后发现,点击月球,地球,太阳后,3D 物体上方出现了相应的简介,但 UI 界面最下方的简介并没有跟着变化(因为通知发出后,Action 没有实现)
所以,我们就需要在加载时,用代码设置通知动作,这样就能在收到点击触发器通知时,触发自定义的动作(点击触发器已经在 Reality Composer 中分别设置好了),更新 UI 界面下方的说明文字。
func setupNotifyActions() {
let allDisplayActions = sizeChapter.actions.allActions.filter({
$0.identifier.hasPrefix("Display") })
for displayAction in allDisplayActions {
displayAction.onAction = { entity in
self.displayDetails(for: entity)//更新对应的 UI 描述
}
}
}
同时,我们还需要用代码,给 UI 层最上面的 segmentedControl 按键添加逻辑,发送通知来触发设置好的触发器。这样当用户切换显示比例时,会发出通知,3D 模型里(swift 文件里也有)的触发器被触发,就会执行相应的缩小和放大动作。
@IBAction func segmentedControlValueChanged(segmentedControl: UISegmentedControl) {
if segmentedControl.selectedSegmentIndex == 0 {
// 缩放到同样大小
sizeChapter.notifications.scaleToSameSize.post()
} else if segmentedControl.selectedSegmentIndex == 1 {
// 缩放到相对大小
sizeChapter.notifications.scaleToRelativeSizes.post()
}
}
类似的,我们还要在结束时,发送通知,以触发相应的结束动画,这里我们假设玩家获得了三星级评价,所以需要根据结果用代码控制星星的数量
@IBAction func didSelectLessonCompletedButton() {
UIView.animate(withDuration:0.5) {
self.detailsView.alpha = 0
self.lessonCompletedButton.alpha = 0
}
// 发送通知,触发结束动作
sizeChapter.notifications.chapterCompleted.post()
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.displayGoldStars() // 产生星星动画效果
}
}
func displayGoldStars() {
guard let star = sizeChapter.specialStar else {
return
}
// 展示金色星星
let notifications = sizeChapter. notifications
notifications. showGoldStar.post( )
// 复制一个星星,在右侧展示
DispatchQueue. main. asyncAfter ( deadline: .now() + 2.5) {
let rightStar = star.clone (recursive: true)
rightStar. setPosition( SIMD3<Float>(0.2, 0.01, 0.0),relativeTo: star)
self. sizeChapter. children. append (rightStar)
notifications. showGoldStar.post (overrides: [ star. name: rightStar])
}
// 复制一个星星,在左侧展示
DispatchQueue. main. asyncAfter ( deadline: .now() + 5.0) {
let leftStar = star.clone (recursive: true)
leftStar. setPosition( SIMD3<Float>(-0.2, -0.01, 0.0),relativeTo: star)
self. sizeChapter. children. append (leftStar)
notifications. showGoldStar.post (overrides: [ star. name: leftStar])
}
}
参考资料
相关视频,WWDC2019