spritekit: trouble with particle keyframe sequence - ios

This should be really straightforward. I'm trying to have my particles fade out using a keyframe sequence.. but when I use the keyframe sequence they dont fade out at all. Not sure what I could be doing wrong.
particle creation:
static func debris(size: Int) -> Array<SKEmitterNode> {
if size > 5 {
fatalError("we don't have that much debris")
}
var debrisArr: [SKEmitterNode] = []
for i in 1...size {
let debris: SKEmitterNode = SKEmitterNode(fileNamed: "debris")
debris.particleTexture = SKTexture(imageNamed: "debris\(i)")
convertNumRef(&debris.particleScale)
convertNumRef(&debris.particleScaleRange)
debris.particleRotationSpeed = debris.particleRotationSpeed * CGFloat.randomSign()
// THE PART WE CARE ABOUT
debris.particleAlphaSequence = SKKeyframeSequence(keyframeValues: [0.5, 1.0, 0.0], times: [0.0, 3.0, 4.0])
debrisArr.append(debris)
}
add particles to game scene here
func makeDebris(){
for debrisEmitter in self.debris {
debrisEmitter.resetSimulation()
debrisEmitter.position = self.position
self.gameScene.gameLayer.addChild(debrisEmitter)
debrisEmitter.runAction(SKAction.removeFromParentAfterDelay(10))
}
}
I've tried this using a simpler example too.
fire is the default spritekit "fire" particle
let fire = SKEmitterNode(fileNamed: "MyParticle")
fire.particleColorSequence = SKKeyframeSequence(keyframeValues: [SKColor.blueColor(), SKColor.blueColor(), SKColor.yellowColor()], times: [0.0, 1.0, 2.0])
fire.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
self.addChild(fire)
the emitter is only emitting blue particles. it just picks whichever color is first in the array. I must be missing something.

Ok I misunderstood. the times arent the times in seconds. they are fractions of your particle's lifespan
debris.particleLifetime = 8
debris.particleAlphaSequence = SKKeyframeSequence(keyframeValues: [1.0, 1.0, 0.0], times: [0.0, 0.7, 1.0])

Related

Create gradient background for SKScene (Swift)

I'm having trouble finding a way to create a gradient background for an SKScene. I have tried using CAGradientLayer but to no success. The only way I know to change the background is to use, for example, backgroundColor = SKColor.cyan. But I want to just create a linear gradient to make the background of my scene look nice and smooth. As of now this is what I have tried and it does not work.
CODE:
let gradientBG = CAGradientLayer()
gradientBG.frame = (self.view?.bounds)!
gradientBG.colors = [SKColor.black, SKColor.cyan]
gradientBG.locations = [0.0, 0.5]
gradientBG.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientBG.endPoint = CGPoint(x: 1.0, y: 0.0)
gradientBG.zPosition = 1
self.scene?.view?.layer.addSublayer(gradientBG)
is there any way to do this using CAGradientLayer? Am I missing anything? If you have any suggestions, I would love to hear. Thanks in advance!

SceneKit Physics simulation does not match actual Node location

I am attempting to implement a first-person space shooter in Scenekit, and I am having the (familiar, I know) problem of getting the physics simulation match the actual position and transform of the SCNNodes the physics simulation is supposed to represent.
The enemy drone ship is created using this function, which places the node in a SCNnode called SectorObjectNode which contains all game objects external to the ship(enemies, stars, etc) and its torpedoes (both of which live in the scene's root node:
func spawnDrone(_ sender: UIButton) {
let humonshipScene = SCNScene(named: "Humon.scn")
let humonShip = humonshipScene?.rootNode.childNodes[0]
self.enemyDrone = humonShip
let droneShape = SCNBox(width: 10, height: 5, length: 5, chamferRadius: 0)
let dronePhysicsShape = SCNPhysicsShape(geometry: droneShape, options: nil)
self.enemyDrone?.physicsBody = SCNPhysicsBody(type: .dynamic, shape: dronePhysicsShape)
self.enemyDrone?.physicsBody?.isAffectedByGravity = false
self.enemyDrone?.physicsBody?.friction = 0
self.enemyDrone?.physicsBody?.categoryBitMask = 0b00000010
self.enemyDrone?.physicsBody?.contactTestBitMask = 0b00000010
self.enemyDrone?.name = "drone"
self.enemyDrone?.pivot = SCNMatrix4MakeTranslation(0.5, 0.5, 0.5)
self.enemyDrone?.position = SCNVector3Make(0, 0, -30)
self.enemyDrone?.scale = SCNVector3Make(1, 1, 1)
let actualPosition = self.scene.rootNode.convertPosition((self.enemyDrone?.position)!, from: self.enemyDrone)
self.enemyDrone?.position = self.scene.rootNode.convertPosition(actualPosition, to: self.sectorObjectsNode)
self.sectorObjectsNode.addChildNode(self.enemyDrone!)
}
The sectorObjectsNode is rotated in reaction to onScreen joystick (thereby rotating the "universe" around the ship to simulate motion) using this code:
func turnShip() {
self.rotate(self.sectorObjectsNode, around: SCNVector3Make(1, 0, 0), by: CGFloat(self.yThrust))
self.rotate(self.sectorObjectsNode, around: SCNVector3Make(0, 1, 0), by: CGFloat(self.xThrust))
}
func rotate(_ node: SCNNode, around axis: SCNVector3, by angle: CGFloat) {
let rotation = SCNMatrix4MakeRotation(Float(angle), axis.x, axis.y, axis.z)
let newTransform = SCNMatrix4Mult(node.worldTransform, rotation)
// Set the new transform
if let parent = node.parent {
node.transform = parent.convertTransform(newTransform, from: nil)
} else {
node.transform = newTransform
}
}
But this code causes the physics simulation to reset ( The grey box in the center of the screen is the physics bounding box for the drone as depicted by the engine when sceneView.debugOptions is set to .showPhysicsShapes), with the following results:
I've tried capturing the drone's presentation position before rotation and then applying it after the two rotate functions, but this causes the ship to move down and to the left. I'm stymied as to how to get the physics simulation of the drone (which I'm using pretty exclusively for collision detection) to stick to the actual position of the enemyDrone node.
As per usual, the issue was RTFM. I set the physics body to be the wrong type:
self.enemyDrone?.physicsBody = SCNPhysicsBody(type: .dynamic, shape: dronePhysicsShape)
Needed to be changed to
self.enemyDrone?.physicsBody = SCNPhysicsBody(type: .kinematic, shape: dronePhysicsShape)

Colorising a Sprite with a Texture Doesn't Work

I'm trying to make game sprites flash a colour when they are damaged with the colour quickly fading back to normal. All the sprites have bitmapped textures but they are not affected by the command. I tried the same on a SpriteNode with a plain coloured background and it does work, but the colour fades in not out:
//defines sprite
let mySprite = SKSpriteNode(imageNamed: "myTexture")
//defines action to colourise sprite over half a second
let tintSprite = SKAction.colorizeWithColor(SKColor.redColor(), colorBlendFactor: 1, duration: 0.5)
mySprite.runAction(tintSprite)
Nothing seems to happen and I assume this action doesn't work on textures. testing it on a simple sprite with no texture works but fades in over the duration:
let test = SKSpriteNode.init(color: SKColor.blackColor(), size: CGSizeMake(80, 80))
test.position.x = 100
test.position.y = 200
let tintTest = SKAction.colorizeWithColor(SKColor.redColor(), colorBlendFactor: 1, duration: 5)
test.runAction(tintTest)
self.addChild(test)
This works fine but fades up from the original black to red over 2 seconds.
Does anyone know how to:
1. Tint textured sprites
2. set a colour instantly then make it fade back to normal.
Any help would be appreciated, many thanks.
Kw
The first "method" it's working if you add self.addChild(mySprite)
//defines sprite
let mySprite = SKSpriteNode(imageNamed: "myTexture")
self.addChild(mySprite) // missing
//defines action to colourise sprite over half a second
let tintSprite = SKAction.colorizeWithColor(SKColor.redColor(), colorBlendFactor: 1, duration: 0.5)
mySprite.runAction(tintSprite)
To fade it back:
let tintSpriteBack = SKAction.colorizeWithColorBlendFactor(0, duration: 0.5)
mySprite.runAction(tintSpriteBack)
For SWIFT 3
let tintSprite = SKAction.colorize(with: SKColor.red, colorBlendFactor: 1, duration: 0.5)
mySprite.run(tintSprite)
and
let tintSpriteBack = SKAction.colorize(withColorBlendFactor: 0, duration: 0.5)
mySprite?.run(tintSpriteBack)

SceneKit: Make blocks more lifelike or 3D-like

The code below is used to create a scene and create blocks in SceneKit. The blocks come out looking flat and not "3D enough" according to our users. Screenshots 1-2 show our app.
Screenshots 3-5 show what users expect the blocks to look like, that is more 3D-like.
After speaking to different people, there are different opinions about how to render blocks that look more like screenshots 3-5. Some people say use ambient occlusion, others say voxel lighting, some say use spot lighting and use shadows, or directional lighting.
We previously tried adding omni lighting, but that didn't work so it was removed. As you can see in the code, we also experimented with an ambient light node but that also didn't yield the right results.
What is the best way to render our blocks and achieve a comparable look to screenshots 3-5?
Note: we understand the code is not optimized for performance, i.e., that polygons are shown that should not be shown. That is okay. The focus is not on performance but rather on achieving more 3D-like rendering. You can assume some hard limit on nodes, like no more than 1K or 10K in a scene.
Code:
func createScene() {
// Set scene view
let scene = SCNScene()
sceneView.jitteringEnabled = true
sceneView.scene = scene
// Add camera node
sceneView.pointOfView = cameraNode
// Make delegate to capture screenshots
sceneView.delegate = self
// Set ambient lighting
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = UIColor(white: 0.50, alpha: 1.0)
//scene.rootNode.addChildNode(ambientLightNode)
//sceneView.autoenablesDefaultLighting = true
// Set floor
setFloor()
// Set sky
setSky()
// Set initial position for user node
userNode.position = SCNVector3(x: 0, y: Float(CameraMinY), z: Float(CameraZoom))
// Add user node
scene.rootNode.addChildNode(userNode)
// Add camera to user node
// zNear fixes white triangle bug while zFar fixes white line bug
cameraNode.camera = SCNCamera()
cameraNode.camera!.zNear = Double(0.1)
cameraNode.camera!.zFar = Double(Int.max)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0) //EB: Add some offset to represent the head
userNode.addChildNode(cameraNode)
}
private func setFloor() {
// Create floor geometry
let floorImage = UIImage(named: "FloorBG")!
let floor = SCNFloor()
floor.reflectionFalloffEnd = 0
floor.reflectivity = 0
floor.firstMaterial!.diffuse.contents = floorImage
floor.firstMaterial!.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(floorImage.size.width)/2, Float(floorImage.size.height)/2, 1)
floor.firstMaterial!.locksAmbientWithDiffuse = true
floor.firstMaterial!.diffuse.wrapS = .Repeat
floor.firstMaterial!.diffuse.wrapT = .Repeat
floor.firstMaterial!.diffuse.mipFilter = .Linear
// Set node & physics
// -- Must set y-position to 0.5 so blocks are flush with floor
floorLayer = SCNNode(geometry: floor)
floorLayer.position.y = -0.5
let floorShape = SCNPhysicsShape(geometry: floor, options: nil)
let floorBody = SCNPhysicsBody(type: .Static, shape: floorShape)
floorLayer.physicsBody = floorBody
floorLayer.physicsBody!.restitution = 1.0
// Add to scene
sceneView.scene!.rootNode.addChildNode(floorLayer)
}
private func setSky() {
// Create sky geometry
let sky = SCNFloor()
sky.reflectionFalloffEnd = 0
sky.reflectivity = 0
sky.firstMaterial!.diffuse.contents = SkyColor
sky.firstMaterial!.doubleSided = true
sky.firstMaterial!.locksAmbientWithDiffuse = true
sky.firstMaterial!.diffuse.wrapS = .Repeat
sky.firstMaterial!.diffuse.wrapT = .Repeat
sky.firstMaterial!.diffuse.mipFilter = .Linear
sky.firstMaterial!.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(2), Float(2), 1);
// Set node & physics
skyLayer = SCNNode(geometry: sky)
let skyShape = SCNPhysicsShape(geometry: sky, options: nil)
let skyBody = SCNPhysicsBody(type: .Static, shape: skyShape)
skyLayer.physicsBody = skyBody
skyLayer.physicsBody!.restitution = 1.0
// Set position
skyLayer.position = SCNVector3(0, SkyPosY, 0)
// Set fog
/*sceneView.scene?.fogEndDistance = 60
sceneView.scene?.fogStartDistance = 50
sceneView.scene?.fogDensityExponent = 1.0
sceneView.scene?.fogColor = SkyColor */
// Add to scene
sceneView.scene!.rootNode.addChildNode(skyLayer)
}
func createBlock(position: SCNVector3, animated: Bool) {
...
// Create box geometry
let box = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
box.firstMaterial!.diffuse.contents = curStyle.getContents() // "curStyle.getContents()" either returns UIColor or UIImage
box.firstMaterial!.specular.contents = UIColor.whiteColor()
// Add new block
let newBlock = SCNNode(geometry: box)
newBlock.position = position
blockLayer.addChildNode(newBlock)
}
Screenshots 1-2 (our app):
Screenshots 3-5 (ideal visual representation of blocks):
I still think there's a few easy things you can do that will make a big difference to how your scene is rendered. Apologies for not using your code, this example is something I had lying around.
Right now your scene is only lit by an ambient light.
let aLight = SCNLight()
aLight.type = SCNLightTypeAmbient
aLight.color = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
let aLightNode = SCNNode()
aLightNode.light = aLight
scene.rootNode.addChildNode(aLightNode)
If I use only this light in my scene I see the following. Note how all faces are lit the same irrespective of the direction they face. Some games do pull off this aesthetic very well.
The following block of code adds a directional light to this scene. The transformation applied in this light won't be valid for your scene, it's important to orientate the light according to where you want the light coming from.
let dLight = SCNLight()
dLight.type = SCNLightTypeDirectional
dLight.color = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1.0)
let dLightNode = SCNNode()
dLightNode.light = dLight
var dLightTransform = SCNMatrix4Identity
dLightTransform = SCNMatrix4Rotate(dLightTransform, -90 * Float(M_PI)/180, 1, 0, 0)
dLightTransform = SCNMatrix4Rotate(dLightTransform, 37 * Float(M_PI)/180, 0, 0, 1)
dLightTransform = SCNMatrix4Rotate(dLightTransform, -20 * Float(M_PI)/180, 0, 1, 0)
dLightNode.transform = dLightTransform
scene.rootNode.addChildNode(dLightNode)
Now we have shading on each of the faces based on their angle relative to the direction of the light.
Currently SceneKit only supports shadows if you're using the SCNLightTypeSpot. Using a spotlight means we need to both orientate (as per directional light) and position it. I use this as a replacement for the directional light.
let sLight = SCNLight()
sLight.castsShadow = true
sLight.type = SCNLightTypeSpot
sLight.zNear = 50
sLight.zFar = 120
sLight.spotInnerAngle = 60
sLight.spotOuterAngle = 90
let sLightNode = SCNNode()
sLightNode.light = sLight
var sLightTransform = SCNMatrix4Identity
sLightTransform = SCNMatrix4Rotate(sLightTransform, -90 * Float(M_PI)/180, 1, 0, 0)
sLightTransform = SCNMatrix4Rotate(sLightTransform, 65 * Float(M_PI)/180, 0, 0, 1)
sLightTransform = SCNMatrix4Rotate(sLightTransform, -20 * Float(M_PI)/180, 0, 1, 0)
sLightTransform = SCNMatrix4Translate(sLightTransform, -20, 50, -10)
sLightNode.transform = sLightTransform
scene.rootNode.addChildNode(sLightNode)
In the above code we first tell the spotlight to cast a shadow, by default all nodes in your scene will then cast a shadow (this can be changed). The zNear and zFar settings are also important and must be specified so that the nodes casting shadows are within this range of distance from the light source. Nodes outside this range will not cast a shadow.
After shading/shadows there's a number of other effects you can apply easily. Depth of field effects are available for the camera. Fog is similarly easy to include.
scene.fogColor = UIColor.blackColor()
scene.fogStartDistance = 10
scene.fogEndDistance = 110
scenekitView.backgroundColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
Update
Turns out you can get shadows from a directional light. Modifying the spotlight code from above by changing its type and setting the orthographicScale. Default value for orthographicScale seems to be 1.0, obviously not suitable for scenes much larger than 1.
let dLight = SCNLight()
dLight.castsShadow = true
dLight.type = SCNLightTypeDirectional
dLight.zNear = 50
dLight.zFar = 120
dLight.orthographicScale = 30
let dLightNode = SCNNode()
dLightNode.light = dLight
var dLightTransform = SCNMatrix4Identity
dLightTransform = SCNMatrix4Rotate(dLightTransform, -90 * Float(M_PI)/180, 1, 0, 0)
dLightTransform = SCNMatrix4Rotate(dLightTransform, 65 * Float(M_PI)/180, 0, 0, 1)
dLightTransform = SCNMatrix4Rotate(dLightTransform, -20 * Float(M_PI)/180, 0, 1, 0)
dLightTransform = SCNMatrix4Translate(dLightTransform, -20, 50, -10)
dLightNode.transform = dLightTransform
scene.rootNode.addChildNode(dLightNode)
Produces the following image.
The scene size is 60x60, so in this case setting the orthographic scale to 30 produces shadows for the objects close to the light. The directional light shadows appear different to the spot light due to the difference in projections (orthographic vs perspective) used when rendering the shadow map.
Ambient occlusion calculations will give you the best results, but is very expensive, particularly in a dynamically changing world, which it looks like this is.
There are several ways to cheat, and get the look of Ambient occlusion.
Here's one:
place transparent, gradient shadow textures on geometry "placards" used to place/present the shadows at the places required. This will involve doing checks of geometry around the new block before determining what placards to place, with which desired texture for the shadowing. But this can be made to look VERY good, at a very low cost in terms of polygons, draw calls and filtrate. It's probably the cheapest way to do this, and have it look good/great, and can only really be done (with a good look) in a world of blocks. A more organic world rules this technique out. Please excuse the pun.
Or, another, similar: Place additional textures onto/into objects that have the shadow, and blend this with the other textures/materials in the object. This will be a bit fiddly, and I'm not an expert on the powers of materials in Scene Kit, so can't say for sure this is possible and/or easy in it.
Or: Use a blend of textures with a vertex shader that's adding a shadow from the edges that touch or otherwise need/desire a shadow based on your ascertaining what and where you want shadows and to what extent. Will still need the placards trick on the floors/walls unless you add more vertices inside flat surfaces for the purpose of vertex shading for shadows.
Here's something I did for a friend's CD cover... shows the power of shadows. It's orthographic, not true 3D perspective, but the shadows give the impression of depths and create the illusions of space:
all answers above (or below) seem to be good ones (at the time of this writing) however,
what I use (just for setting up a simple scene) is one ambient light (lights everything in all directions) to make things visible.And then one omnidirectional light positioned somewhere in the middle of your scene, the omni light can be raised up (Y up I mean) to light the whole of your scene. The omni light gives the user a sense of shading and the ambient light makes it more like a sun light.
for example:
Imagine sitting in a living room (like I am right now) and the sun-light peers through the window to your right.
You can obviously see a shadow of an area that the couch is not getting sun light, however you can still see details of what is in the shadow.
Now! all the sudden your wold gets rid of ambient light BOOM! The shadow is now pitch black, you can't anymore see what is in the shadow.
Or say the ambient light came back again (what a relief), but all the sudden the omni light stopped working. (probably my fault :( ) Everything now is lighted the same, no shadow, no difference, but if you lay a paper on the table, and look at it from above, there is no shadow! So you think it is part of the table! In a world like this your rely on the contour of something in order to see it- you would have to look at the table from side view, to see the thickness of the paper.
Hope this helps (at least a little)
Note: ambient lighting give a similar effect to emissive material

Custom Particle System for iOS

I want to create a particle system on iOS using sprite kit where I define the colour of each individual particle. As far as I can tell this isn't possible with the existing SKEmitterNode.
It seems that best I can do is specify general behaviour. Is there any way I can specify the starting colour and position of each particle?
This can give you a basic idea what I was meant in my comments. But keep in mind that it is untested and I am not sure how it will behave if frame rate drops occur.
This example creates 5 particles per second, add them sequentially (in counterclockwise direction) along the perimeter of a given circle. Each particle will have different predefined color. You can play with Settings struct properties to change the particle spawning speed or to increase or decrease number of particles to emit.
Pretty much everything is commented, so I guess you 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'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")
}
}
}
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'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")
}
}
}
Orange dots are added just for debugging purposes and you can remove that part if you like.
Personally I would say that you are overthinking this, but I might be wrong because there is no clear description of what you are trying to make and how to use it. Keep in mind that SpriteKit can render a bunch of sprites in a single draw call in very performant way. Same goes with SKEmitterNode if used sparingly. Also, don't underestimate SKEmitterNode... It is very configurable actually.
Here is the setup of Particle Emitter Editor:
Anyways, here is the final result:
Note that nodes count comes from an orange SKSpriteNodes used for debugging. If you remove them, you will see that there is only one node added to the scene (emitter node).
What you want is completely possible, probably even in real time. Unfortunately to do such a thing the way you describe with moving particles as being a particle for each pixel would be best done with a pixel shader. I don't know of a clean method that would allow you to draw on top of the scene with a pixel shader otherwise all you would need is a pixel shader that takes the pixels and moves them out from the center. I personally wouldn't try to do this unless I built the game with my own custom game engine in place of spritekit.
That being said I'm not sure a pixel per pixel diffusion is the best thing in most cases. Expecially if you have cartoony art. Many popular games will actually make sprites for fragments of the object they expect to shader. So like if it's an airplane you might have a sprite for the wings with perhaps even wires hanging out of this. Then when it is time to shatter the plane, remove it from the scene and replace the area with the pieces in the same shape of the plane... Sorta like a puzzle. This will likely take some tweaking. Then you can add skphysicsbodies to all of these pieces and have a force push them out in all directions. Also this doesn't mean that each pixel gets a node. I would suggest creatively breaking it into under 10 pieces.
And as whirlwind said you could all ways get things looking "like" it actually disintegrated by using an emitter node. Just make the spawn area bigger and try to emulate the color as much as possible. To make the ship dissappear you could do a fade perhaps? Or Mabye an explosion sprite over it? Often with real time special effects and physics, or with vfx it is more about making it look like reality then actually simulating reality. Sometimes you have to use trickery to get things to look good and run real-time.
If you want to see how this might look I would recommend looking at games like jetpac joyride.
Good luck!

Resources