Custom particle system for iOS

I want to create a particle system iOS

using a set of sprites where I define the color of each individual particle. As far as I can tell, this is not possible with the existing one SKEmitterNode

. It seems that the best I can do this is to specify the general behavior. Is there a way to specify the starting color and position of each particle?

+1


source to share


2 answers


This might give you a general idea of ​​what I had in mind in my comments. But keep in mind that it is untested and I am not sure how it will behave if a frame rate drop occurs.

This example creates 5 particles per second, add them sequentially (in a counter-clockwise direction) around the perimeter of the given circle. Each particle will have a different predefined color. You can play with the structural property settings to change the spawn rate of the particles or increase or decrease the number of particles that emit.

Quite a lot is commented, so I think everything will be fine:

Swift 2

import SpriteKit

struct Settings {

    static var numberOfParticles = 30
    static var particleBirthRate:CGFloat = 5   //Means 5 particles per second, 0.2 means one particle in 5 seconds etc.
}

class GameScene: SKScene {

    var positions       = [CGPoint]()
    var colors          = [SKColor]()

    var emitterNode:SKEmitterNode?

    var currentPosition = 0

    override func didMoveToView(view: SKView) {

        backgroundColor = .blackColor()


        emitterNode = SKEmitterNode(fileNamed: "rain.sks")

        if let emitter = emitterNode {

            emitter.position =  CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
            emitter.particleBirthRate = Settings.particleBirthRate
            addChild(emitter)


            let radius = 50.0
            let center = CGPointZero

            for var i = 0; i <= Settings.numberOfParticles; i++ {

                //Randomize color
                colors.append(SKColor(red: 0.78, green: CGFloat(i*8)/255.0, blue: 0.38, alpha: 1))

                //Create some points on a perimeter of a given circle (radius = 40)
                let angle = Double(i) * 2.0 * M_PI / Double(Settings.numberOfParticles)
                let x = radius * cos(angle)
                let y = radius * sin(angle)


                let currentParticlePosition = CGPointMake(CGFloat(x) + center.x, CGFloat(y) + center.y)

                positions.append(currentParticlePosition)

                if i == 1 {
                    /*
                    Set start position for the first particle.
                    particlePosition is starting position for each particle in the emitter coordinate space. Defaults to (0.0, 0,0).
                    */
                    emitter.particlePosition = positions[0]
                    emitter.particleColor = colors[0]

                    self.currentPosition++
                }

            }

            // Added just for debugging purposes to show positions for every particle.
            for particlePosition in positions {

                let sprite = SKSpriteNode(color: SKColor.orangeColor(), size: CGSize(width: 1, height: 1))
                sprite.position = convertPoint(particlePosition, fromNode:emitter)
                sprite.zPosition = 2
                addChild(sprite)
            }


            let block = SKAction.runBlock({

                // Prevent strong reference cycles.
                [unowned self] in

                if self.currentPosition < self.positions.count {

                    // Set color for the next particle
                    emitter.particleColor = self.colors[self.currentPosition]

                    // Set position for the next particle. Keep in mind that particlePosition is a point in the emitter coordinate space.
                    emitter.particlePosition = self.positions[self.currentPosition++]

                }else {

                    //Stop the action
                    self.removeActionForKey("emitting")
                    emitter.particleBirthRate = 0
                }

           })


            // particleBirthRate is a rate at which new particles are generated, in particles per second. Defaults to 0.0.

            let rate = NSTimeInterval(CGFloat(1.0) / Settings.particleBirthRate)

            let sequence = SKAction.sequence([SKAction.waitForDuration(rate), block])

            let repeatAction = SKAction.repeatActionForever(sequence)


            runAction(repeatAction, withKey: "emitting")
        }

    }
}

      

Swift 3.1

import SpriteKit

struct Settings {

    static var numberOfParticles = 30
    static var particleBirthRate:CGFloat = 5   //Means 5 particles per second, 0.2 means one particle in 5 seconds etc.
}

class GameScene: SKScene {

    var positions = [CGPoint]()
    var colors = [SKColor]()

    var emitterNode: SKEmitterNode?

    var currentPosition = 0

    override func didMove(to view: SKView) {

        backgroundColor = SKColor.black


        emitterNode = SKEmitterNode(fileNamed: "rain.sks")

        if let emitter = emitterNode {

            emitter.position = CGPoint(x: frame.midX, y: frame.midY)
            emitter.particleBirthRate = Settings.particleBirthRate
            addChild(emitter)


            let radius = 50.0
            let center = CGPoint.zero

            for var i in 0...Settings.numberOfParticles {

                //Randomize color
                colors.append(SKColor(red: 0.78, green: CGFloat(i * 8) / 255.0, blue: 0.38, alpha: 1))

                //Create some points on a perimeter of a given circle (radius = 40)
                let angle = Double(i) * 2.0 * Double.pi / Double(Settings.numberOfParticles)
                let x = radius * cos(angle)
                let y = radius * sin(angle)


                let currentParticlePosition = CGPoint.init(x: CGFloat(x) + center.x, y: CGFloat(y) + center.y)

                positions.append(currentParticlePosition)

                if i == 1 {
                    /*
                    Set start position for the first particle.
                    particlePosition is starting position for each particle in the emitter coordinate space. Defaults to (0.0, 0,0).
                    */
                    emitter.particlePosition = positions[0]
                    emitter.particleColor = colors[0]

                    self.currentPosition += 1
                }

            }

            // Added just for debugging purposes to show positions for every particle.
            for particlePosition in positions {

                let sprite = SKSpriteNode(color: SKColor.orange, size: CGSize(width: 1, height: 1))
                sprite.position = convert(particlePosition, from: emitter)
                sprite.zPosition = 2
                addChild(sprite)
            }


            let block = SKAction.run({

                // Prevent strong reference cycles.
                [unowned self] in

                if self.currentPosition < self.positions.count {

                    // Set color for the next particle
                    emitter.particleColor = self.colors[self.currentPosition]

                    // Set position for the next particle. Keep in mind that particlePosition is a point in the emitter coordinate space.
                    emitter.particlePosition = self.positions[self.currentPosition]

                    self.currentPosition += 1

                } else {

                    //Stop the action
                    self.removeAction(forKey: "emitting")
                    emitter.particleBirthRate = 0
                }

            })


            // particleBirthRate is a rate at which new particles are generated, in particles per second. Defaults to 0.0.

            let rate = TimeInterval(CGFloat(1.0) / Settings.particleBirthRate)

            let sequence = SKAction.sequence([SKAction.wait(forDuration: rate), block])

            let repeatAction = SKAction.repeatForever(sequence)


            run(repeatAction, withKey: "emitting")
        }

    }
}

      



Orange dots are added for debugging purposes only and you can remove this part if you like.

Personally, I would say that you are thinking too much about it, but I could be wrong because there is no clear description of what you are trying to do and how to use it. Keep in mind that SpriteKit can display a bunch of sprites in a single callback very efficiently. The same thing happens with SKEmitterNode if used sparingly. Also, don't underestimate the SKEmitterNode ... It's very customizable actually.

Here is the Particle Emitter Editor setup :

Particle emitter editor

Anyway, here's the final result:

emitter

Note that the node count comes from the orange SKSpriteNodes used for debugging. If you remove them, you will see that only one node (emitter node) has been added to the scene.

+3


source


What you want is completely possible, perhaps even in real time. Unfortunately, doing the way you describe moving particles as particles for each pixel is best done with a pixel shader. I don't know of a clean method that will allow you to paint over a scene using a pixel shader, otherwise you need a pixel shader that maps the pixels and pushes them out of the center. I personally wouldn't do this if I hadn't built the game with my own game engine instead of spritekit.

This suggests that I'm not sure if it's best to use pixel per pixel diffusion in most cases. Especially if you have cartoon art. Many popular games actually create sprites for the fragments of the object they are expecting for the shader. So if you have an airplane, you might have a sprite for the wings, maybe even wires dangling from it. Then when it's time to crash the plane, remove it from the scene and replace the area with pieces in the same plane view ... Sort like a puzzle. This will probably lead to some customization. Then you can add skfysicsbodies to all of these items and make them pop them in all directions. It also doesn't mean that every pixel gets a node. I suggest creatively breaking it down to 10 pieces.

And as Vortex said you could all make things look "like", they actually disintegrated using the emitter node. Just increase your caviar area and try to emulate the color as much as possible. To make the ship disappear, could you fade? Or will Mabi Blast handle it? Often with real time effects and physics, or with vfx, it looks more like it looks like reality and then actually mimics reality. Sometimes you have to use trickery to keep things looking good and working in real time.



If you want to see what this might look like, I would recommend looking at games like jetpac joyride.

Good luck!

+2


source







All Articles