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

SpriteKit无法从sks文件解归档SKEmitterNode子类问题求助

解决SKEmitterNode子类解归档返回nil的问题

问题场景

我编写了如下Swift代码,尝试从sks文件加载自定义的PassthroughEmitterNode(继承自SKEmitterNode):

final class PassthroughEmitterNode: SKEmitterNode {
  
  static func load(fnWithoutExtension: String, in bundle: Bundle) -> SKEmitterNode? {
    guard
      let sksPath = bundle.path(forResource: fnWithoutExtension, ofType: "sks"),
      let sksData = try? Data(contentsOf: URL(fileURLWithPath: sksPath)),
      let unarchiver = try? NSKeyedUnarchiver(forReadingFrom: sksData),
      let texturePath = bundle.path(forResource: fnWithoutExtension, ofType: "png"),
      let textureImage = UIImage(contentsOfFile: texturePath)
    else { return nil }
    
    // Required to decode into subclass
    unarchiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKEmitterNode")
    let emitter = unarchiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as? PassthroughEmitterNode
    unarchiver.finishDecoding()
    
    guard let emitter else { return nil }
    
    // 纹理文件不在主bundle中,需手动设置
    emitter.particleTexture = SKTexture(image: textureImage)
    
    // 需启用用户交互以接收触摸事件
    emitter.isUserInteractionEnabled = true
    
    return emitter
  }
}

这段代码的思路和从sks文件读取SKScene子类实例的方式类似,但解归档时始终返回nil。直接将sks文件解归档为SKEmitterNode本身是可行的,但子类就不行。


解决方案

问题出在类映射的API使用和调用逻辑上,以下是修正步骤:

  1. 修正类映射调用
    替换原代码中unarchiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKEmitterNode")为:

    unarchiver.setClass(PassthroughEmitterNode.self, forClassName: "SKEmitterNode")
    

    classForKeyedUnarchiver()是Objective-C兼容API,Swift中直接使用子类.self更可靠,能确保解归档器正确识别子类类型。

  2. 调整解归档流程
    把空值判断提前,避免无效的解归档后续操作,同时确保finishDecoding()在所有解码操作后调用。

修正后的完整代码:

final class PassthroughEmitterNode: SKEmitterNode {
  
  static func load(fnWithoutExtension: String, in bundle: Bundle) -> SKEmitterNode? {
    guard
      let sksURL = bundle.url(forResource: fnWithoutExtension, withExtension: "sks"),
      let sksData = try? Data(contentsOf: sksURL),
      let unarchiver = try? NSKeyedUnarchiver(forReadingFrom: sksData),
      let textureURL = bundle.url(forResource: fnWithoutExtension, withExtension: "png"),
      let textureImage = UIImage(contentsOfFile: textureURL.path)
    else { return nil }
    
    // 正确映射子类到归档中的SKEmitterNode类名
    unarchiver.setClass(PassthroughEmitterNode.self, forClassName: "SKEmitterNode")
    
    // 提前判断解码结果,避免无效操作
    guard let emitter = unarchiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as? PassthroughEmitterNode else {
      unarchiver.finishDecoding()
      return nil
    }
    
    unarchiver.finishDecoding()
    
    // 手动设置纹理
    emitter.particleTexture = SKTexture(image: textureImage)
    // 启用用户交互
    emitter.isUserInteractionEnabled = true
    
    return emitter
  }
}

额外优化建议

  • 优先使用URL而非字符串路径获取资源,减少路径拼接的潜在错误
  • 可以将try?替换为do-catch块,捕获具体错误信息,方便调试定位问题

内容的提问来源于stack exchange,提问作者OMGPOP

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 18:43:20