SpriteKit unable to unarchive an SKEmitterNode subclass from sks file

116 Views Asked by At

I have the following code:


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 }
    
    // We still need to set texture, because the texture file is not in main bundle
    emitter.particleTexture = SKTexture(image: textureImage)
    
    // Have to enable user interaction to receive touch
    emitter.isUserInteractionEnabled = true
    
    return emitter
  }
}

This is very similar to reading a SKScene subclass instance from SKS file (such as this: using sks file with SKScene subclass).

However, this doesn't work. The unarchiver's decoding always returns nil.

But unarchiving to SKEmitterNode class itself works. The subclass doesn't work.

1

There are 1 best solutions below

3
Luca Angeletti On BEST ANSWER

We can try this strategy:

  1. Decode SKEmitterNode
  2. Create a PassthroughEmitterNode from a SKEmitterNode

In order to do that let's add this init

public final class PassthroughEmitterNode: SKEmitterNode {
    
    init(emitterNode: SKEmitterNode) {
        super.init()

        self.emissionAngle = emitterNode.emissionAngle
        self.emissionAngleRange = emitterNode.emissionAngleRange
        self.numParticlesToEmit = emitterNode.numParticlesToEmit
        self.particleAlpha = emitterNode.particleAlpha
        self.particleAlphaRange = emitterNode.particleAlphaRange
        self.particleAlphaSpeed = emitterNode.particleAlphaSpeed
        self.particleBirthRate = emitterNode.particleBirthRate
        self.particleBlendMode = emitterNode.particleBlendMode
        self.particleColor = emitterNode.particleColor
        self.particleColorBlendFactor = emitterNode.particleColorBlendFactor
        self.particleColorBlendFactorRange = emitterNode.particleColorBlendFactorRange
        self.particleColorBlendFactorSpeed = emitterNode.particleColorBlendFactorSpeed
        self.particleLifetime = emitterNode.particleLifetime
        self.particleLifetimeRange = emitterNode.particleLifetimeRange
        self.particlePositionRange = emitterNode.particlePositionRange
        self.particleRotation = emitterNode.particleRotation
        self.particleRotationRange = emitterNode.particleRotationRange
        self.particleRotationSpeed = emitterNode.particleRotationSpeed
        self.particleScale = emitterNode.particleScale
        self.particleScaleRange = emitterNode.particleScaleRange
        self.particleScaleSpeed = emitterNode.particleScaleSpeed
        self.particleSpeed = emitterNode.particleSpeed
        self.particleSpeedRange = emitterNode.particleSpeedRange
        self.particleTexture = emitterNode.particleTexture
        self.position = emitterNode.position
        self.xAcceleration = emitterNode.xAcceleration
        self.yAcceleration = emitterNode.yAcceleration
        // TODO: Add additional properties I might have forgot.
    }
    
    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

That's it.

Now you can decode a standard SKEmitterNode and convert it into your PassthroughEmitterNode

let emitterNode: SKEmitterNode = ... // Decode as usual
let passthroughEmitterNode = PassthroughEmitterNode(emitterNode: emitterNode)

What if PassthroughEmitterNode has additional properties compared to SKEmitterNode?

The value for these additional properties cannot come from the sks file since there is no place for them. So you will need to populate them using some default value or custom logic.