Search code examples
swiftxcodecore-animationscenekitparticle-system

SCNParticleSystem: animating "particleColor" property in code


I would like to animate a certain color sequence in a SceneKit Particle System in code only. (Like one can do within the Particle System Editor in XCode.)

I was trying the following, and the App is crashing each time the Animation is attached to the particle system. The compiler does not complain, Xcode does not indicate any error within the syntax. (without the animation part, the particle system works fine)

func particleSystemTesting(shape: SCNGeometry) -> SCNParticleSystem {
    
    let explosion = SCNParticleSystem(named: "explosion.scnp", inDirectory: nil)!
    explosion.emitterShape = shape
    explosion.birthLocation = .surface
    explosion.birthDirection = .surfaceNormal
    explosion.isAffectedByGravity = true
    explosion.isLightingEnabled = false
    explosion.loops = true
    explosion.sortingMode = .none
    explosion.isBlackPassEnabled = true
    explosion.blendMode = .additive

    explosion.particleColor = UIColor.white // using as default, should even not be required

    // Animation Part        
    let animation = CABasicAnimation(keyPath: "particleColor")
    animation.fromValue = UIColor.blue
    animation.toValue = UIColor.red
    animation.duration = 2.0
    animation.isRemovedOnCompletion = true
    
    explosion.addAnimation(animation, forKey: nil) // causing the crash
    
    return explosion
    
}

This are the errors the console is printing out:

2020-08-23 18:25:53.281120+0200 MyTestApp[1684:892874] Metal GPU Frame Capture Enabled
2020-08-23 18:25:53.281349+0200 MyTestApp[1684:892874] Metal API Validation Enabled
2020-08-23 18:25:56.563208+0200 MyTestApp[1684:892874] -[SCNParticleSystem copyAnimationChannelForKeyPath:animation:]: unrecognized selector sent to instance 0x101041500
2020-08-23 18:25:56.564524+0200 MyTestApp[1684:892874] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[SCNParticleSystem copyAnimationChannelForKeyPath:animation:]: unrecognized selector sent to instance 0x101041500'
*** First throw call stack:
(0x1998d5654 0x1995f7bcc 0x1997d9dd8 0x1998d97f8 0x1998db71c 0x1ade571f8 0x1ade55f24 0x1ade59268 0x1ade5af2c 0x1ade23d84 0x1adf18cf0 0x199852f2c 0x19984de20 0x19984e29c 0x19984dba8 0x1a39bd344 0x19d9893e4 0x100566384 0x1996d58f0)
libc++abi.dylib: terminating with uncaught exception of type NSException

After some more research and consulting apples docs reference it seems that my first approach was wrong, because that would affect all spawned particles at the same time.

BUT IT STILL DOES NOT WORK AS EXPECTED - all particles are gone/invisible on scene - otherwise no errors. this should change the colours of each particle over time. What is wrong?

func particleSystemTesting(shape: SCNGeometry) -> SCNParticleSystem {

    let explosion = SCNParticleSystem(named: "explosion_testing.scnp", inDirectory: nil)!
    // let explosion = SCNParticleSystem() // makes no difference
    explosion.emitterShape = shape
    explosion.birthLocation = .surface
    explosion.birthDirection = .surfaceNormal
    explosion.isAffectedByGravity = true
    explosion.isLightingEnabled = false
    explosion.loops = true
    explosion.sortingMode = .none
    explosion.isBlackPassEnabled = true
    explosion.blendMode = .additive
    
    // explosion.particleColor = UIColor.black

    let red = UIColor.red
    let green = UIColor.green
    let blue = UIColor.blue
    let yellow = UIColor.yellow
    
    let color1 = SCNVector4(red.redValue, red.greenValue, red.blueValue, 1.0)
    let color2 = SCNVector4(green.redValue, green.greenValue, green.blueValue, 1.0)
    let color3 = SCNVector4(blue.redValue, blue.greenValue, blue.blueValue, 1.0)
    let color4 = SCNVector4(yellow.redValue, yellow.greenValue, yellow.blueValue, 1.0)
    
    let animation = CAKeyframeAnimation()
    // animation.keyPath = "color" // has like no effect (?...)
    animation.values = [color1,color2,color3,color4]
    animation.keyTimes = [0, 0.333, 0.666, 1]
    animation.duration = 2.0
    animation.isAdditive = false // should overwrite default colours
    animation.isRemovedOnCompletion = true

     let colorController = SCNParticlePropertyController(animation: animation)
    explosion.propertyControllers = [SCNParticleSystem.ParticleProperty.color: colorController]

    return explosion
    
}

Apple Docs says this:

Apple Discussion

This property’s value is a four-component vector (an NSValue object containing an SCNVector4 value for particle property controllers, or an array of four float values for particle event or modifier blocks). The particle system’s particleColor and particleColorVariation properties determine the initial color for each particle.


Solution

  • I believe the documentation is wrong. Your animation values array should contain UIColor objects.

    animation.values = [UIColor.red, UIColor.green, UIColor.blue, UIColor.yellow]