You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何添加不同对象的多个节点?仿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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:09:37