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

SceneKit/ARKit开发:如何从CollectionView选表情并添加至SCNScene

嗨,我来帮你捋清楚这个实现思路~其实核心就是把「选中表情」和「触摸屏幕放置3D节点」这两个环节串起来,一步步拆解下来就很清晰了:

核心实现步骤拆解

1. 先记录用户选中的表情

首先得在CollectionView的选中事件里,把用户选的表情对应的资源标识(比如图片名、3D模型文件名)存下来,方便后续生成节点用:

// 类级别变量,存选中的表情资源名
var selectedEmojiAsset: String?

// CollectionView选中代理方法
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    // 假设你的表情数组是emojiAssets,里面存的是每个表情对应的资源文件名
    selectedEmojiAsset = emojiAssets[indexPath.item]
    // 可选:给选中的cell加个UI反馈,比如高亮
    if let cell = collectionView.cellForItem(at: indexPath) {
        cell.backgroundColor = UIColor.systemGray5
    }
}

2. 封装生成表情3D节点的工具函数

把生成节点的逻辑单独抽成一个函数,这样代码更整洁,也方便后续修改或扩展:

func createEmojiNode(from assetName: String) -> SCNNode? {
    // 两种实现可选:根据你的表情类型选一种
    
    // 👉 方案1:2D图片转平面节点(适合简单表情)
    guard let emojiImage = UIImage(named: assetName) else { return nil }
    let planeGeometry = SCNPlane(width: 0.1, height: 0.1) // 0.1米大小,可按需调整
    planeGeometry.firstMaterial?.diffuse.contents = emojiImage
    planeGeometry.firstMaterial?.isDoubleSided = true // 确保正反都能看到
    let emojiNode = SCNNode(geometry: planeGeometry)
    emojiNode.eulerAngles.x = -.pi / 2 // 把垂直平面转成水平,贴合AR地面
    
    // 👉 方案2:加载3D模型文件(比如.dae/.scn格式,适合立体表情)
    // guard let emojiScene = SCNScene(named: assetName) else { return nil }
    // let emojiNode = SCNNode()
    // emojiScene.rootNode.childNodes.forEach { emojiNode.addChildNode($0) }
    // emojiNode.scale = SCNVector3(0.1, 0.1, 0.1) // 缩放模型到合适大小
    
    return emojiNode
}

3. 处理触摸事件,将表情放置到AR场景

在你的ARViewController里重写触摸方法,获取触摸点对应的AR世界坐标,然后把生成的表情节点添加进去:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)
    
    // 先判断用户有没有选中表情
    guard let selectedAsset = selectedEmojiAsset else {
        print("请先从表情列表中选择一个表情哦~")
        // 也可以弹个提示框给用户
        return
    }
    
    // 获取触摸点在屏幕上的位置
    guard let touchPoint = touches.first?.location(in: sceneView) else { return }
    
    // 用ARKit的hitTest检测真实世界中的平面
    let hitResults = sceneView.hitTest(touchPoint, types: .existingPlaneUsingExtent)
    
    // 如果找到可放置的平面,就生成节点并添加到场景
    if let hitResult = hitResults.first {
        guard let emojiNode = createEmojiNode(from: selectedAsset) else { return }
        
        // 设置节点位置为检测到的平面坐标
        emojiNode.position = SCNVector3(
            hitResult.worldTransform.columns.3.x,
            hitResult.worldTransform.columns.3.y,
            hitResult.worldTransform.columns.3.z
        )
        
        // 添加到AR场景根节点
        sceneView.scene.rootNode.addChildNode(emojiNode)
        
        // 可选:给节点加个小动画,提升交互感
        let popAnimation = SCNAction.sequence([
            SCNAction.scale(to: 1.2, duration: 0.2),
            SCNAction.scale(to: 1.0, duration: 0.2)
        ])
        emojiNode.runAction(popAnimation)
    }
}

额外优化小技巧

  • 节点缓存:如果用户频繁选同一个表情,可以缓存生成好的节点,用的时候clone一份,提升性能:
    var emojiNodeCache: [String: SCNNode] = [:]
    
    func createEmojiNode(from assetName: String) -> SCNNode? {
        if let cachedNode = emojiNodeCache[assetName] {
            return cachedNode.clone()
        }
        // 原来的节点生成代码...
        emojiNodeCache[assetName] = emojiNode
        return emojiNode
    }
    
  • 取消选中状态:可以在CollectionView的didDeselectItemAt方法里取消之前选中cell的高亮状态,让UI更清晰。

内容的提问来源于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 09:25:50