如何添加不同对象的多个节点?仿Waazy应用3D对象collectionView交互求助
嘿,我来帮你搞定这个多3D模型加载与放置的问题!其实核心思路是先统一管理模型信息,再按需加载/复用节点,完全不需要提前把所有带纹理的节点都塞进数组(那样反而会浪费内存),下面是具体的实现步骤:
一、先给你的3D模型做个「数据包装」
首先定义一个结构体,把每个模型的名称、模型文件路径、纹理文件路径都存在一起,这样后续不管是CollectionView展示还是加载模型,都能统一处理:
struct ARModel { let displayName: String // 给用户看的名称,比如"马匹" let modelFile: String // 模型文件名,比如"horse.obj" let textureFile: String? // 纹理文件名,可选(有些模型自带纹理) }
然后把你的三个模型放进数组里,当作CollectionView的数据源:
let availableModels: [ARModel] = [ ARModel(displayName: "马匹", modelFile: "horse.obj", textureFile: "horse_texture.png"), ARModel(displayName: "恐龙", modelFile: "dino.obj", textureFile: "dino_texture.jpg"), ARModel(displayName: "人类", modelFile: "human.obj", textureFile: "human_texture.png") ]
二、写一个通用的「模型加载工具函数」
这个函数负责根据ARModel的信息,加载模型并绑定纹理,最终返回一个可直接用的SCNNode:
func loadModel(with modelData: ARModel) -> SCNNode? { // 1. 找到模型文件路径 guard let modelURL = Bundle.main.url(forResource: modelData.modelFile, withExtension: nil) else { print("找不到模型文件:\(modelData.modelFile)") return nil } // 2. 加载模型场景 do { let scene = try SCNScene(url: modelURL) let rootNode = SCNNode() // 把场景里的所有子节点合并到一个根节点(方便后续操作) scene.rootNode.childNodes.forEach { rootNode.addChildNode($0) } // 3. 绑定纹理(如果有纹理文件的话) if let textureName = modelData.textureFile, let textureURL = Bundle.main.url(forResource: textureName, withExtension: nil) { let texture = try SKTexture(url: textureURL) // 遍历模型的所有几何体,给材质设置纹理 rootNode.enumerateChildNodes { node, _ in node.geometry?.materials.forEach { $0.diffuse.contents = texture } } } // 4. 调整模型大小(根据你的模型实际尺寸修改) rootNode.scale = SCNVector3(0.1, 0.1, 0.1) return rootNode } catch { print("加载模型出错:\(error.localizedDescription)") return nil } }
三、处理CollectionView的选中逻辑
在CollectionView的代理方法里,记录用户选中的模型:
var selectedARModel: ARModel? func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { selectedARModel = availableModels[indexPath.row] // 这里可以给选中的cell加个高亮效果,比如改变背景色 collectionView.cellForItem(at: indexPath)?.backgroundColor = .systemBlue } // 别忘了处理取消选中的情况 func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { collectionView.cellForItem(at: indexPath)?.backgroundColor = .clear }
四、实现「点击屏幕放置模型」的核心逻辑
给你的ARView添加点击手势,然后在回调里完成平面检测、模型加载与放置:
// 先给ARView添加点击手势 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleScreenTap(_:))) arView.addGestureRecognizer(tapGesture) @objc func handleScreenTap(_ gesture: UITapGestureRecognizer) { // 1. 先判断用户有没有选中模型 guard let selectedModel = selectedARModel else { print("请先从列表中选择一个3D模型哦!") return } // 2. 检测点击位置的平面(AR必备步骤) let tapLocation = gesture.location(in: arView) guard let hitResult = arView.hitTest(tapLocation, types: .existingPlaneUsingExtent).first else { print("还没检测到平面,请先移动设备扫描周围环境~") return } // 3. 加载选中的模型节点 guard let modelNode = loadModel(with: selectedModel) else { print("加载\(selectedModel.displayName)失败!") return } // 4. 设置模型位置为点击的平面位置 let worldPosition = hitResult.worldTransform.columns.3 modelNode.position = SCNVector3(worldPosition.x, worldPosition.y, worldPosition.z) // 5. 把模型添加到AR场景中 arView.scene.rootNode.addChildNode(modelNode) }
可选优化:预加载模型提升响应速度
如果希望用户点击时模型立刻出现,不想等待加载,可以提前把所有模型加载好并缓存(注意内存占用,模型多的话不建议):
// 用字典缓存预加载的节点 var preloadedNodes: [String: SCNNode] = [:] // 预加载所有模型 func preloadAllModels() { for model in availableModels { if let node = loadModel(with: model) { preloadedNodes[model.displayName] = node } } } // 点击时直接克隆缓存的节点(同一个节点不能重复添加到场景,必须克隆) @objc func handleScreenTap(_ gesture: UITapGestureRecognizer) { guard let selectedModel = selectedARModel, let cachedNode = preloadedNodes[selectedModel.displayName]?.clone() else { // 处理未选中或未预加载的情况 return } // 后续设置位置、添加到场景的逻辑和之前一样... }
这样一套流程下来,就能实现你要的「选模型→点击屏幕→显示对应3D模型」的功能啦,纹理绑定也完全没问题~
内容的提问来源于stack exchange,提问作者khan
相关产品推荐
相关产品推荐

