2

I was just curious, is there a way to slowdown particle emitting along the other objects in the scene. Say you have one particle emitter, and one sprite. Sprite is moved using SKAction. An emitter is emitting. Both nodes are added to the scene. If you set self.speed to 0.5, only the sprite will slow down. Emitters will continue emitting at the same speed.

Is there a way to slowdown in some other way than using particleSpeed property, because that will change the behaviour of an emitter.

My question is inspired by this question: https://stackoverflow.com/a/41764180/3402095

Community
  • 1
  • 1
Whirlwind
  • 14,286
  • 11
  • 68
  • 157
  • You want to slow down the movement of particles **AND** the rate at which particles are emitted? – Confused Jan 23 '17 at 13:20
  • @Confused I think (but that is just a guessing because I haven't really tested) that something like you proposed would require each time different values / math for different emitter because many factors are involved in particle's movement. Maybe you can write an answer to show exactly what values / math you meant to use ? – Whirlwind Jan 23 '17 at 13:33

1 Answers1

1

Normally a SKEmitterNode have many properties involved in it's speed (xAcceleration, yAcceleration, particleSpeedRange, particleScaleSpeed..):

private func newExplosion() -> SKEmitterNode {
        let explosion = SKEmitterNode()
        let image = UIImage(named:"spark.png")!
        explosion.particleTexture = SKTexture(image: image)
        explosion.particleColor = SKColor.brown
        explosion.numParticlesToEmit = 100
        explosion.particleBirthRate = 450
        explosion.particleLifetime = 2
        explosion.emissionAngleRange = 360
        explosion.particleSpeed = 100
        explosion.particleSpeedRange = 50
        explosion.xAcceleration = 0
        explosion.yAcceleration = 0
        explosion.particleAlpha = 0.8
        explosion.particleAlphaRange = 0.2
        explosion.particleAlphaSpeed = -0.5
        explosion.particleScale = 0.75
        explosion.particleScaleRange = 0.4
        explosion.particleScaleSpeed = -0.5
        explosion.particleRotation = 0
        explosion.particleRotationRange = 0
        explosion.particleRotationSpeed = 0
        explosion.particleColorBlendFactor = 1
        explosion.particleColorBlendFactorRange = 0
        explosion.particleColorBlendFactorSpeed = 0
        explosion.particleBlendMode = SKBlendMode.add
        return explosion
    }

So to achieve your request I think it should be possible to set the targetNode:

/**
     Normally the particles are rendered as if they were a child of the SKEmitterNode, they can also be rendered as if they were a child of any other node in the scene by setting the targetNode property. Defaults to nil (standard behavior).
     */
    weak open var targetNode: SKNode?

You can find more details to the Apple official docs.

Some code to example:

class GameScene: SKScene {
    var myNode :SKSpriteNode!
    var myEmitter : SKEmitterNode!
    override func didMove(to view: SKView) {
        myNode = SKSpriteNode.init(color: .red, size: CGSize(width:100,height:100))
        self.addChild(myNode)
        myNode.position = CGPoint(x:self.frame.minX,y:self.frame.midY)
        let changePos = SKAction.run{
            [weak self] in
            guard let strongSelf = self else { return }
            if strongSelf.myNode.position.x > strongSelf.frame.maxX {
                strongSelf.myNode.position.x = strongSelf.frame.minX
            }
        }
        let move = SKAction.moveBy(x: 10.0, y: 0, duration: 0.5)
        let group = SKAction.group([move,changePos])
        myNode.run(SKAction.repeatForever(SKAction.afterDelay(1.0, performAction:group)))
        let showExplosion = SKAction.repeatForever(SKAction.afterDelay(3.0, performAction:SKAction.run {
            [weak self] in
            guard let strongSelf = self else { return }
            strongSelf.myEmitter = strongSelf.newExplosion()
            strongSelf.addChild(strongSelf.myEmitter)
            strongSelf.myEmitter.targetNode = strongSelf.myNode
            strongSelf.myEmitter.particleSpeed = strongSelf.myNode.speed
            print("emitter speed is: \(strongSelf.myEmitter.particleSpeed)")
            strongSelf.myEmitter.position = CGPoint(x:strongSelf.frame.midX,y:strongSelf.frame.midY)
            strongSelf.myEmitter.run(SKAction.afterDelay(2.0, performAction:SKAction.removeFromParent()))
        }))
        self.run(showExplosion)
    }
    private func newExplosion() -> SKEmitterNode {
        let explosion = SKEmitterNode()
        let image = UIImage(named: "sparkle.png")!
        explosion.particleTexture = SKTexture(image: image)
        explosion.particleColor = UIColor.brown
        explosion.numParticlesToEmit = 100
        explosion.particleBirthRate = 450
        explosion.particleLifetime = 2
        explosion.emissionAngleRange = 360
        explosion.particleSpeed = 100
        explosion.particleSpeedRange = 50
        explosion.xAcceleration = 0
        explosion.yAcceleration = 0
        explosion.particleAlpha = 0.8
        explosion.particleAlphaRange = 0.2
        explosion.particleAlphaSpeed = -0.5
        explosion.particleScale = 0.75
        explosion.particleScaleRange = 0.4
        explosion.particleScaleSpeed = -0.5
        explosion.particleRotation = 0
        explosion.particleRotationRange = 0
        explosion.particleRotationSpeed = 0
        explosion.particleColorBlendFactor = 1
        explosion.particleColorBlendFactorRange = 0
        explosion.particleColorBlendFactorSpeed = 0
        explosion.particleBlendMode = SKBlendMode.add
        return explosion
    }
    func randomCGFloat(_ min: CGFloat,_ max: CGFloat) -> CGFloat {
        return (CGFloat(arc4random()) / CGFloat(UINT32_MAX)) * (max - min) + min
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        myNode.speed = randomCGFloat(0.0,50.0)
        print("new speed is: \(myNode.speed)")
    }
}
extension SKAction {
    class func afterDelay(_ delay: TimeInterval, performAction action: SKAction) -> SKAction {
        return SKAction.sequence([SKAction.wait(forDuration: delay), action])
    }
    class func afterDelay(_ delay: TimeInterval, runBlock block: @escaping () -> Void) -> SKAction {
        return SKAction.afterDelay(delay, performAction: SKAction.run(block))
    }
}

In this example you can see how the speed applied to myNode involved the speed of myEmitter everytime I touch the screen, because the emitter have the targetNode property setted with myNode , and I've also change the particleSpeed to seems a more realistic slowdown/accelleration.

Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • 1
    Is it your suggestion that changing the speed of the targetNode will slow the particle system? – Confused Jan 23 '17 at 13:25
  • The targetNode should influence the particles involved – Alessandro Ornano Jan 23 '17 at 13:28
  • Interesting. I haven't tried this, nor used SK particles much. SCN particle systems have a speed property that impacts the speed of existing particles, and their rate of emittance. This sounds like it would achieve the same thing. If it works. – Confused Jan 23 '17 at 13:29
  • @AlessandroOrnano Will try this as soon as I find time, thanks. Hopefully this works, so that would be a solution at least for nodes that have targetNode set. – Whirlwind Jan 23 '17 at 13:31
  • Here's the speed value, at the bottom of this screenshot of the properties of an SCNParticleSystem: https://i.stack.imgur.com/4JR3n.png if you play with this, it slows everything about the system, all at once. – Confused Jan 23 '17 at 13:31
  • @AlessandroOrnano I tried to set a targetNode property and still, if I change the speed on a scene (or on the targetNode, which was the same in my example) the particles was not slowed down. Later on, I will try the actual code you have provided. Actually I tried to slow down a complete emitter. This works for example among sprite nodes. Their actions are affected for example by changing the node's speed. Also child nodes are affected. But this doesn't work with emitter node. – Whirlwind Jan 23 '17 at 16:38
  • @Whirlwind In my example, when you start to move a node , literally the emitter follow the node moving, so if you change the speed of the node, the emitter follow the exact behaviour (decelerate/accellerate) of the node. But the emitter have to do also with other speeds as reported to the top of my answer: I think these parameters are not involved as for example `particleSpeed` (that I've changed manually in my example) because they make part of the "internal" emitter animation. If you know the exact emitter’s lifetime you should correct parameters to make more realistic your slow-mo.. – Alessandro Ornano Jan 23 '17 at 16:50
  • @Whirlwind Last I checked, targetNode is broke, has anybody successfully used this? The particles are not affected by the speed property because particles are not nodes, so you can only affect new particles that get emitted. – Knight0fDragon Jan 23 '17 at 20:23
  • @Knight0fDragon What do you mean for "is broke" , have you found some "inconsistency" ? In xCode 8.2.1 seems it work as expected. Is it any bug reported? It did not surprise me, lately sprite-kit is pulling out many reproducible bugs – Alessandro Ornano Jan 23 '17 at 20:38
  • Last time I used it, the targetNode was not emitting particles, and I think the emitter itself locked up. – Knight0fDragon Jan 23 '17 at 20:39
  • Just wrote a quick app, still broken, does not emit from targetNode, emits from emitter. I am on 8.2.1 – Knight0fDragon Jan 23 '17 at 20:55
  • @Knight0fDragon I thought that `targetNode` thing was fixed long time ago. Also, when you say "you can only affect new particles that get emitted", what do you exactly mean? – Whirlwind Jan 23 '17 at 21:33
  • @Whirlwind unless I am doing it wrong, it does not work. When you change the emitter settings, this only changes particles that get born. If the particle already exists, these settings will not affect it – Knight0fDragon Jan 23 '17 at 21:34
  • @Knight0fDragon I get it now... Also, all this somehow seems incomplete to me (I never thought I will complain about SpriteKit haha). I mean, we can slow down actions, and nodes, but we can't particles. I asked this just from curiosity, but now I wonder, what if you wanted to make a slow-mo effect of a whole screen... That might be a bit problematic as I can see. – Whirlwind Jan 23 '17 at 21:38
  • @Whirwind Yup, I agree. particles are their own little beasts, I am assuming due to efficiency, they are not a part of the scene's update cycle, and instead rely on its own timers – Knight0fDragon Jan 23 '17 at 21:47
  • @Whirlwind I can't verify or trace this, but I have a gut feeling all the work for emitters are done on the GPU, not the CPU – Knight0fDragon Jan 23 '17 at 21:50
  • @Knight0fDragon I don't know that info. But yeah, they are not represented as objects, so you are probably right. – Whirlwind Jan 23 '17 at 21:52