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.
We can try this strategy:
SKEmitterNode
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)
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.