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
相关产品推荐
相关产品推荐

