VisionPro开发系列首页:周游Apple Vision Pro
系列项目悉数代码:github.com/xuchi16/vis…
本文首要包括以下内容:
- 视频原料(Video Material)
- 操控视频原料的播映和暂停
- 获取 USDZ 模型的子实体
终究成果:一台支撑播映/暂停功能的复古小电视
根本概念
在 RealityKit 中,原料(Material)是一种用于定义 3D 物体表面烘托作用的目标。视频原料是一种支撑动画纹路的原料,能够把视频映射到物体表面。视频原料是一种无光照(unlit)类型,意味着场景中的光照不会对它形成任何影响。一起,假如源视频文件的格局支撑透明度,视频原料也支撑透明度调整。
视频原料运用AVPlayer
实例来操控视频播映,因此咱们能够运用任何AVPlayer
支撑的视频格局。还能够运用其play()
和pause()
方法等操控视频播映和暂停。
首要过程
- 创立视频播映操控和状况办理的模型(Model)
- 导入实体,并获取需求展现为视频原料的子实体
- 创立视频原料,并增加到子实体上
- 创立操控视频播映的视图
- 将播映企图经过附件(attachment) 附加到实体上
根本完成
视频播映和状况办理
首先,咱们需求创立一个办理视频播映状况的模型,其中包括播映器和播映内容。当应用启动时,初始化该 Model,而且传递给不同的 View。
class PlayModel: ObservableObject {
var player = AVPlayer()
var item: AVPlayerItem?
func load(_ url: URL) {
item = AVPlayerItem(url: url)
player.replaceCurrentItem(with: item)
}
}
实体导入及子实体获取
将电视的 USDZ 文件增加到 Packages 中,并在 ImmersiveView 中加载该模型。这一模型包括两个部分:电视的主体和屏幕。能够经过 Entity 类型的findEntity(named:)
方法找到屏幕对应的子实体。
RealityView { content, attachments in
let tv = try! await Entity(named: "tv_retro")
if let screen = tv.findEntity(named: "tv_retro_2_RetroTVScreen") as? ModelEntity {
// 获取到了屏幕实体
}
content.add(tv)
}
创立视频原料
在 ImmersiveView 中从 Model 中获取到播映器,增加到视频原料上,并绑定到屏幕实体上。而且当实体加载完成后播映视频。
var playModel: PlayModel
RealityView { content in
let tv = try! await Entity(named: "tv_retro")
if let screen = tv.findEntity(named: "tv_retro_2_RetroTVScreen") as? ModelEntity {
let player = playModel.player
let material = VideoMaterial(avPlayer: player)
screen.model?.materials = [material]
player.play()
}
content.add(tv)
}
.onAppear() {
if let url = Bundle.main.url(forResource: "GetReadyRemixRB", withExtension: "mp4") {
playModel.load(url)
}
}
创立视频播映操控视图
播映操控企图中只包括一个按钮:
- 当视频为暂停状况时,为播映按钮,点击后即可播映
- 当视频为播映状况时,为暂停按钮,点击后暂停视频
因此需求对PlayModel
进行增强,增加对应视频播映状况判断以及播映操控的功能。其中,togglePlayPause()
用于在不同状况下对视频进行播映和暂停操作。observePlayer()
用于监听视频的播映状况。
class PlayModel: ObservableObject {
var player = AVPlayer()
var item: AVPlayerItem?
private var subscriptions = Set<AnyCancellable>()
@Published var isPlaying: Bool = false
init() {
observePlayer()
}
// ...
func togglePlayPause() {
if player.rate == 0 {
player.play()
} else {
player.pause()
}
}
private func observePlayer() {
player.publisher(for: .timeControlStatus)
.receive(on: RunLoop.main)
.sink(receiveValue: { [weak self] status in
switch status {
case .playing:
self?.isPlaying = true
default:
self?.isPlaying = false
}
})
.store(in: &subscriptions)
}
}
在此基础上,新增一个 ControllerView,用于展现播映操控按钮。
- 图标根据视频状况不同,分别用
pause.fill
和play.fill
表示 - 按钮行为经过调用 Model 中的
togglePlayPause()
函数完成操控
struct ControllerView: View {
@ObservedObject var playModel = PlayModel()
var body: some View {
Button(action: {
playModel.togglePlayPause()
}) {
Image(systemName: playModel.isPlaying ? "pause.fill" : "play.fill")
.font(.system(size: 40))
.accessibilityLabel(playModel.isPlaying ? "Pause" : "Play")
.padding(.all, 40)
}
.padding(12)
}
}
为实体增加附件
回到 ImmversiveView 中,在电视实体下方增加上述新建的 ControllerView。咱们为新建一个 entity 用于容纳 tv 实体及其附件,而且将它们都增加到 entity 中即可。
@State var entity = Entity()
RealityView { content, attachments in
// load tv
entity.addChild(tv)
if let attachment = attachments.entity(for: "controller") {
attachment.position = [0, 0.98, -1.2]
entity.addChild(attachment)
}
content.add(entity)
} attachments: {
Attachment(id: "controller") {
ControllerView(playModel: playModel)
}
}
终究作用
重视我
欢迎在上重视我和我的专栏VisionOS Workshop,以及各种保藏/围观/谈论/反应/批判/Star/点歌