ARKit中ModelEntity点击变色及切换恢复原材质的问题
ARKit选中ModelEntity后恢复原始材质的问题解决
问题原因
你的代码中保存原始材质时,直接赋值了entity.model!.materials,而Material是引用类型,后续修改实体的材质(比如改成蓝色)时,保存到modelEntitiesMaterials里的“原始材质”会被同步修改。导致再次选中该实体时,先恢复的已经是被修改过的蓝色材质,再设置蓝色就看不到变化了。
解决方案
1. 保存材质的副本而非引用
需要为每个实体的原始材质创建独立副本,确保后续修改实体材质不会影响保存的原始数据。
2. 优化选中状态管理
直接跟踪当前选中的实体,避免每次遍历所有实体,提升性能的同时简化逻辑。
修改后的代码
替换状态跟踪变量
enum EntityState { case unselected case selected case correctName case wrongName } private var entitiesState: [String: EntityState] = [:] private var modelEntities: [ModelEntity] = [] private var modelEntitiesMaterials: [String: [Material]] = [:] // 新增:直接跟踪当前选中的实体,替代遍历查找 private var currentSelectedEntity: ModelEntity?
修改handleTap中的材质保存逻辑
@objc private func handleTap(sender: UITapGestureRecognizer) { let tapLocation = sender.location(in: arView) let result = arView.hitTest(tapLocation) guard let hitTest = result.first, hitTest.entity.name != "Ground Plane" else { selectEntity(withSelectedEntity: nil) return } guard let entity = hitTest.entity as? ModelEntity else { return } let keyExists = modelEntitiesMaterials[entity.name] != nil if !keyExists { // 保存材质的副本,避免引用导致原始材质被修改 let originalMaterials = entity.model!.materials.map { material in if let simpleMat = material as? SimpleMaterial { // 手动复制SimpleMaterial的所有属性,创建新实例 return SimpleMaterial( color: simpleMat.color, roughness: simpleMat.roughness, isMetallic: simpleMat.isMetallic, specularColor: simpleMat.specularColor, clearcoat: simpleMat.clearcoat, clearcoatRoughness: simpleMat.clearcoatRoughness, normalTexture: simpleMat.normalTexture, occlusionTexture: simpleMat.occlusionTexture, emissiveColor: simpleMat.emissiveColor, emissiveTexture: simpleMat.emissiveTexture, transparency: simpleMat.transparency, texture: simpleMat.texture ) } else { // 其他材质类型使用NSObject的copy方法 return material.copy() as! Material } } modelEntitiesMaterials[entity.name] = originalMaterials } selectEntity(withSelectedEntity: entity) }
重写selectEntity函数
private func selectEntity(withSelectedEntity: ModelEntity?) { modelInformationView.isHidden = false // 先取消当前选中的实体,恢复其原始材质 if let currentSelected = currentSelectedEntity { entitiesState[currentSelected.name] = .unselected if let originalMaterials = modelEntitiesMaterials[currentSelected.name] { currentSelected.model!.materials = originalMaterials } currentSelectedEntity = nil } guard let selectedEntity = withSelectedEntity else { modelInformationView.isHidden = true // 未选中任何实体时隐藏信息视图 return } // 如果点击的是已选中的实体,直接取消(可选逻辑,根据需求调整) if selectedEntity == currentSelectedEntity { return } // 设置新的选中实体 entitiesState[selectedEntity.name] = .selected currentSelectedEntity = selectedEntity // 应用选中状态的蓝色材质 let selectedMaterial = SimpleMaterial(color: .link, isMetallic: false) selectedEntity.model?.materials[0] = selectedMaterial }
可选:确保实体名称唯一
在placeObject中,为每个子实体设置唯一名称,避免modelEntitiesMaterials的键冲突:
if let geomChildrens = geomChildrens { for children in geomChildrens.children { let childModelEntity = children as! ModelEntity childModelEntity.collision = CollisionComponent(shapes: [ShapeResource.generateConvex(from: childModelEntity.model!.mesh)]) // 设置唯一名称,避免键冲突 childModelEntity.name = UUID().uuidString entitiesState[childModelEntity.name] = .unselected modelEntities.append(childModelEntity) } }
说明
- 材质副本:通过手动复制
SimpleMaterial属性或使用copy()方法,确保保存的原始材质与实体当前材质完全独立,不会被后续修改影响。 - 状态跟踪:直接维护
currentSelectedEntity变量,避免每次遍历所有实体查找选中状态,代码更简洁,性能更优。
内容的提问来源于stack exchange,提问作者user13965197
相关产品推荐
相关产品推荐

