iOS 的自定义粒子系统

Custom Particle System for iOS

我想使用 sprite kit 在 iOS 上创建一个粒子系统,我在其中定义了每个粒子的颜色。据我所知,现有的 SKEmitterNode 不可能做到这一点。 看来我能做的最好的就是指定一般行为。有什么方法可以指定每个粒子的起始颜色和位置吗?

这可以让您基本了解我在评论中的意思。但请记住,它未经测试,我不确定如果发生帧率下降它会如何表现。

此示例每秒创建 5 个粒子,沿着给定圆的周长顺序(逆时针方向)添加它们。每个粒子将具有不同的预定义颜色。您可以使用 Settings 结构属性来更改粒子生成速度或增加或减少要发射的粒子数。

几乎所有内容都有评论,所以我想你会没事的:

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's 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's 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")
        }
        
    }
}

Swift3.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's 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's 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")
        }

    }
}

添加橙色点仅用于调试目的,您可以根据需要删除该部分。

就我个人而言,我会说你想多了,但我可能是错的,因为没有明确描述你正在尝试制作什么以及如何使用它。请记住,SpriteKit 可以在单个绘制调用中以非常高效的方式渲染一堆精灵。如果谨慎使用,SKEmitterNode 也是如此。还有,不要小看SKEmitterNode...其实配置性很强

这里是 Particle Emitter Editor 的设置:

无论如何,这是最终结果:

请注意,节点数来自用于调试的橙色 SKSpriteNodes。如果你移除它们,你会看到只有一个节点被添加到场景中(发射器节点)。

你想要的是完全可能的,甚至可能是实时的。不幸的是,按照您将移动粒子描述为每个像素的粒子的方式来做这样的事情最好使用像素着色器来完成。我不知道有什么干净的方法可以让您使用像素着色器在场景顶部绘制,否则您所需要的只是一个像素着色器,它获取像素并将它们从中心移出。我个人不会尝试这样做,除非我用自己的自定义游戏引擎代替 spritekit 构建游戏。

话虽这么说,但我不确定在大多数情况下,每个像素的漫射效果是否最好。特别是如果你有卡通艺术。许多流行的游戏实际上会为他们希望着色的对象片段制作精灵。所以如果它是一架飞机,你可能有一个机翼精灵,甚至可能有电线挂在外面。然后当需要打碎飞机时,将其从场景中移除并用与飞机形状相同的碎片替换该区域……有点像拼图。这可能需要一些调整。然后你可以将 skphysicsbodies 添加到所有这些部分,并有一个力将它们向各个方向推出。此外,这并不意味着每个像素都有一个节点。我建议创造性地将它分成 10 个以下的部分。

正如 whirlwind 所说,您可以通过各种方式让事物看起来 "like" 它实际上是通过使用发射器节点分解的。只需扩大生成区域并尝试尽可能多地模拟颜色即可。为了让船消失,你也许可以做一个淡入淡出?或者 Mabye 一个爆炸精灵在它上面?通常使用实时特效和物理,或者使用 vfx,更多的是让它看起来像现实,然后实际模拟现实。有时您必须使用技巧才能使事情看起来不错 运行 real-time。

如果你想看看这看起来如何,我建议你看看像 jetpac joyride 这样的游戏。

祝你好运!