Bootstrap

IOS开发之AR问题汇总

问题一:怎么使用SwiftUI实现AR界面

目前IOS开发AR有两个框架,一个是ARKit的ARSCNView,另一个是RealityKit的ARView

后者比前者更新功能更强大一些,也更推荐使用

现在我们普遍使用的语言是SwiftUI,而上面两个框架继承的都是UIKit,是旧的框架

因此,我们实现的时候需要使用其他的类来桥接这两个框架

UIViewRepresentable 协议类

通过该协议就可以让我们轻松地在 SwiftUI 界面上使用 UIView

这个协议有两个必须提供的方法:makeUIViewupdateUIView

下面我使用ARView举例

struct TestARViewContainer: UIViewRepresentable {
    func makeUIView(context: Context) -> ARView {
        return ARView()
    }

    func updateUIView(_ uiView: ARView, context: Context) {
    }
}
  • makeUIView 方法创建了一个要表示的 UIView, 在其生命周期里只会调用一次
  • updateUIView 方法会在 UIView 的状态发生变化时被调用,所以在其生命周期内会被调用多次(即使这是一个空实现也会被调用多次)。

一般我们只会这用到makeUIView

问题二:AR模型有什么注意的点吗

有!AR模型仅支持usdz格式的,这是苹果独有的模型格式

这个格式我自认为很鸡肋,是因为模型里只能展示一个动画,也就是说,即使你的模型里有多个动画,也只能展示出一个,这个问题官方一直都没有管!!!

如果你要实现一个模型,在不同条件下做不同的动作,唯一解决的办法就是:

把每一个动作都单独提出来做成一个模型,当你需要切换动作的时候,直接切换包含对应动作的模型

模型动作分离的教程,我之前也有写过,小白的话可以看一下,很简单就能完成✅

但是代码部分的实现很头痛,因为模型加载时需要时间的,如果你切换动作的时候,先加载模型,再播放动画,会有明显的卡壳的感觉

这里我提供一个思路,就是使用全局静态,一次性异步加载所有我们需要的包含动作的模型

这里给大家代码示范一下

首先是静态类

//
//  TestModel.swift
//  ARDemo
//
//  Created by hp on 2024/9/2.
//

import Foundation
import RealityKit
import Combine
import SwiftUI

enum ModelCategory: CaseIterable{
    case rollover
    case play
    case shake
    case sit
    case stand
    
    var label: String{
        get{
            switch self{
            case .rollover:
                return "Rollover"
            case .play:
                return "Play"
            case .shake:
                return "Shake"
            case .sit:
                return "Sit"
            case .stand:
                return "Stand"
            }
        }
    }
}

class TestModel{
    var name: String
    var category: ModelCategory
    var modelEntity: ModelEntity?
    var scaleCompensation: Float
    
    private var cancellable: AnyCancellable?
    
    init(name: String, category: ModelCategory, scaleCompensation: Float = 1.0) {
        self.name = name
        self.category = category
        self.scaleCompensation = scaleCompensation
    }
    
    func asyncLoadModelEntity(){
        let filename = self.name + ".usdz"
        
        self.cancellable = ModelEntity.loadModelAsync(named: filename)
            .sink(receiveCompletion: { loadCompletion in
                switch loadCompletion{
                case .finished:
                    break
                case.failure(let error): print("Unable ton load modelEntity for \(filename).Error: \(error.localizedDescription)")
                }
            }, receiveValue: { modelEntity in
                self.modelEntity = modelEntity
                self.modelEntity?.scale /= 10
                self.modelEntity?.scale += self.scaleCompensation
                print("modelEntity for \(self.name) has been loaded")
                
            })
    }
}

//使用单例模式,异步初始化所有狗模型
struct TestModels{
    static let shared = TestModels()
    var all: [TestModel] = []
    
    private init(){
        print("TestModels init")
        let rollover = TestModel(name: "rollover", category: .rollover, scaleCompensation: 0.1/100)
        let play = TestModel(name: "play", category: .play, scaleCompensation: 0.1/100)
        let shake = TestModel(name: "shake", category: .shake, scaleCompensation: 0.1/100)
        let sit = TestModel(name: "sit", category: .sit, scaleCompensation: 0.1/100)
        let stand = TestModel(name: "stand", category: .sit, scaleCompensation: 0.1/100)
        rollover.asyncLoadModelEntity()
        play.asyncLoadModelEntity()
        shake.asyncLoadModelEntity()
        sit.asyncLoadModelEntity()
        stand.asyncLoadModelEntity()
        self.all += [rollover,play,shake,sit,stand]
    }
    
    func get(category: ModelCategory) ->[TestModel] {
        return all.filter({
            $0.category == category
        })
    }
}

使用单例模式后,在第一次创建AR模型的时候,会自动异步加载所有的模型,方便后续切换

比如我第一次加载一个坐着的狗模型,实际上会把其他所有动作的狗模型都加载了

var items = TestModels.shared.get(category: .sit)

那么接下来怎么替换呢

这里我给个样例

func replaceModel(action: ModelCategory){
    guard let arView = arView else{return}
    //arView.scene.anchors.removeAll()
    if let entity = arView.scene.findEntity(named: "dog"){
        //保存位置
        var position = entity.position
        //移除模型,后面那个数字是需要自己试的
        arView.scene.anchors.remove(at: 2)
        print("Remove Success!")
                 
        //新建有动画的模型
        var items = TestModels.shared.get(category: action)
        let model = items[0]
        if let modelEntity = model.modelEntity{
        let clonedEntity = modelEntity.clone(recursive: true)
        //添加旋转角等特性
        clonedEntity.generateCollisionShapes(recursive: true)
        arView.installGestures([.translation, .rotation], for: clonedEntity)
                     
        //创建锚点
        let anchorEntity = AnchorEntity(.plane(.horizontal, classification: .any, minimumBounds: SIMD2(repeating: 0)))
        clonedEntity.name = "dog"
        anchorEntity.addChild(clonedEntity)
        arView.scene.addAnchor(anchorEntity)
        print("Create success!")
                     
        //播放动画
        clonedEntity.playAnimation((clonedEntity.availableAnimations[0].repeat()), transitionDuration: 0.5, startsPaused: false)
        let animationDuration = clonedEntity.availableAnimations[0].definition.duration
        let timer = Timer.scheduledTimer(withTimeInterval: animationDuration, repeats: false, block: { _ in
             print("Animation over!")
             })
        }
    }
}

实际上位置变量没有用到,默认如果删除模型后新加的位置还是原来的位置

这是我进过测试总结出来的

问题三:怎么使用Unity作AR开发呢

做过AR开发的,或多或少都知道Unity的ARFundation可以进行AR开发

我也使用过,但是开发的是安卓版本的!

IOS的版本做好之后,需要打包成Xcode可以调用的库文件

但这时候会有个问题,就是所有的AR部分你必须在Unity中全部实现,因为Xcode调用Unity的库文件,是不能传输信息的

所以我最后没有选择用Unity进行IOS端的AR开发!

好了这就是本次博客的全部内容了,欢迎大家与我一起分享和讨论,我们一起学习,一起进步!!!

;