1

I created a SKEmitterNode and I want the particleTexture property to animate images.

This is what I'm trying, but it is only showing the first image:

let animationImages = [UIImage(named: "image1")!, UIImage(named: "image2")!]

  let imageView = UIImageView()
  imageView.image = UIImage.animatedImage(with: animationImages, duration: 1)
  imageView.startAnimating()

  emitter.particleTexture = SKTexture(image: imageView.image!)
Joe
  • 355
  • 1
  • 10

2 Answers2

2

Your last line will always return the same "main" image, regardless of the animation. This was confirmed elsewhere on this site already, but see also Apple's documentation of UIImageView.image.

But even if you did manage to obtain the current image, it would still be a one-time assignment, and the texture wouldn't update automatically; that's something you will need to do yourself; as Apple puts it in Animating a Sprite by Changing its Texture in Apple's SpriteKit reference:

A sprite’s texture property points to its current texture. You can change this property to point to a new texture.

Supposed solution: particle action (doesn't seem to work since iOS 9)

Luckily, there's an SKAction to do just that (see the last link for some details), and it can theoretically even be applied to SKEmitterNode particles (something I just found out as I was about to tell you the opposite):

let animationTextures = [SKTexture(imageNamed: "image1"),
                         SKTexture(imageNamed: "image2"),
                         SKTexture(imageNamed: "image3")]
let animationAction = SKAction.repeatForever(SKAction.animate(with: textures, timePerFrame: 0.1))
emitter.particleAction = animationAction

EDIT: It doesn't work, and hasn't worked for years: particleActions are simply being ignored. It's quite incredible that Apple wouldn't fix such an obvious bug for half a decade, but this seems to be the case.

Workaround: implement your own emitter

SKEmitterNodes have a lot going for them (this bug notwithstanding):

  • Particles are lightweight, stripped-down SKSpriteNode-like thingies that are optimized for being thrown around in huge numbers without all the overhead associated with full-blown sprite nodes.
  • Emitters can be built in a visual designer in XCode, which saves a lot of time compared to programmatically creating everything.

However, I already found myself in a situation once where I was forced to implement my own "emitter." In my case, it involved little more than writing a method creating a fixed number of sprite nodes, and attaching a complex action (three levels of hierarchy of multiple groups and sequences) to each of them that controlled their movement, scale, alpha, and eventual disappearance (removal from parent). It was not a huge amount of work, and it gave me the precise control I needed over the particle animations that an emitter node couldn't provide – but I was lucky in that I only needed to emit a few dozen particles at a time.

I'd say implementing your own emitter would make the most sense if all of the following conditions are true:

  • You really need texture animation and can't wait for Apple to fix that bug, and/or you need some other particle behaviors that SKEmitterNode doesn't provide; and
  • you don't need a huge number of particles, especially concurrent particles (so that performance remains acceptable); and
  • you don't mind doing all the emitter design work programmatically, without the visual designer – although you can still prototype your emitter as an SKEmitterNode in Xcode, and re-use the parameters of your prototype in your final implementation; and
  • your particle animations (i.e. all changes in position, scale, alpha, rotation, tint, texture (the reason why you'd do this in the first place)) are not too overwhelming to specify all: in my case, I only had to implement one special case (whose complexity I could still manage), but if I'd had a need to design multiple emitters like this, I would have probably ended up creating a class with an API comparable to that of SKEmitterNode, which may not be a trivial task.

Once again, good luck: SpriteKit could be a fantastic framework if only Apple got around to fixing some of its pretty outrageous bugs. (I'm having issues with custom shaders… Maybe you will see a question or two from me in the near future.)

pua666
  • 326
  • 1
  • 7
  • Unfortunately, this is not working. Did you get it working somehow? I tried setting the particleTexture property to nil. Also, I tried: emitter.run(animationAction) but nothing seems to be working. No one else posted anything helpful elsewhere, that I can find. A lot of people are complaining about bugs. – Joe Oct 29 '20 at 23:17
  • @Joe – I'm sorry, you're right: as it turns out `particleAction` doesn't work, and hasn't worked in years. I tried everything to get it to work, but it just won't. See my edited answer. – pua666 Oct 30 '20 at 02:44
  • Thanks for trying to help! On another post where somebody's trying to do something similar, a user said what I'm attempting could be achieved with a Shader. When I looked into shaders it seems pretty complex, my app is more of an ARKit app, I'm not that familiar with SpriteKit, and shaders look like something completely different. Anyway, can the particles be changed using Shaders? Here's the post: https://stackoverflow.com/questions/35769213/spritekit-particle-emitter-multi-image#comment112747392_35769213 – Joe Oct 30 '20 at 23:09
  • Oh boy… shaders have a bit of a learning curve, you need to learn a new programming language and tell each pixel what to draw based on its coordinates… I can try if emitters work with shaders (can't trust what Apple says about this stuff…) What kind of effect are you trying to achieve? Can you say a bit more? – pua666 Oct 31 '20 at 00:07
  • 1
    Don't worry about. It's not a big part of the app. I'm going to submit this bug to Apple. Hopefully, they will fix it soon. Thanks for your help! – Joe Oct 31 '20 at 23:59
1
  let image1 = SKAction.run( { emitter.particleTexture = SKTexture(imageNamed: "image1") } )
  let image2 = SKAction.run( { emitter.particleTexture = SKTexture(imageNamed: "image2") } )
  let image3 = SKAction.run( { emitter.particleTexture = SKTexture(imageNamed: "image3") } )
  
  let seq = SKAction.sequence([image1, wait, image2, wait, image3, wait])
  let repeater = SKAction.repeatForever(seq)
  emitter.run(repeater)
Bobby
  • 6,115
  • 4
  • 35
  • 36