VisionPro开发系列首页:周游Apple Vision Pro

系列项目悉数代码:github.com/xuchi16/vis…


本文首要包括以下内容:

  • 视频原料(Video Material)
  • 操控视频原料的播映和暂停
  • 获取 USDZ 模型的子实体

终究成果:一台支撑播映/暂停功能的复古小电视

VisionPro开发 - 运用视频原料完成一台复古小电视
VisionPro开发 - 运用视频原料完成一台复古小电视

根本概念

在 RealityKit 中,原料(Material)是一种用于定义 3D 物体表面烘托作用的目标。视频原料是一种支撑动画纹路的原料,能够把视频映射到物体表面。视频原料是一种无光照(unlit)类型,意味着场景中的光照不会对它形成任何影响。一起,假如源视频文件的格局支撑透明度,视频原料也支撑透明度调整。

视频原料运用AVPlayer实例来操控视频播映,因此咱们能够运用任何AVPlayer支撑的视频格局。还能够运用其play()pause()方法等操控视频播映和暂停。

首要过程

  1. 创立视频播映操控和状况办理的模型(Model)
  2. 导入实体,并获取需求展现为视频原料的子实体
  3. 创立视频原料,并增加到子实体上
  4. 创立操控视频播映的视图
  5. 将播映企图经过附件(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.fillplay.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)
    }
}

终究作用

VisionPro开发 - 运用视频原料完成一台复古小电视

重视我

欢迎在上重视我和我的专栏VisionOS Workshop,以及各种保藏/围观/谈论/反应/批判/Star/点歌