I'm working on a metaballs effect in SpriteKit. I have an SKEffectNode with a shader that compresses the colour range of its buffer. This effectnode has a hundred-odd SKSpriteNode as its children. The effect works great for a couple of seconds, but suddenly, over the course of a few frames, all of the spritenodes vanish from the screen, and the nodecount drops from 100-odd down to 2 (I guess the 2 remaining are the effectnode itself, and the edge chain that forms the container to hold the balls?)
If I add the ball spritenodes directly to the rootnode of the scene, this does not happen. It's only when they are made children of the effectnode that they seem to get aggressively culled after a few seconds.
Interestingly, (and this is visible in the simulator if the frame rate is quite low) the SpriteNodes seem to get culled in the vertical order that they are currently displayed on the screen, from the top of the screen to the bottom.
If the view's showsPhysics
property is set to true, I can see the SKSpriteNode's physics bodies are still there onscreen, and rolling around. But their visual representation has vanished, and the view's node count has been depleted. According to the docs showsNodeCount
"shows physics bodies that are visible in the scene." So because the nodes are no longer visible, the node count has dropped.
Anyone know what could be causing this, or what tools I could use to debug it?
Standard SpriteKit iOS Swift 2.2 Xcode 7 template, replace the GameScene file with this:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
// create walls to contain balls
let box = SKPhysicsBody(edgeLoopFromRect: CGRect(x: 0, y: 0, width: 800, height: 800))
let boxNode = SKNode()
boxNode.physicsBody = box
addChild(boxNode)
let radius: CGFloat = 5
let texture = createMetaballTexture(radius)
let centre = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
let rows = 12
let columns = 10
let metaballs = SKEffectNode()
let shaderString = [
"void main()",
"{",
"vec4 c = texture2D(u_texture, v_tex_coord);",
"gl_FragColor = smoothstep(0.3, 0.6, c); ",
"}"].joinWithSeparator("\n")
let map = SKShader(source: shaderString)
metaballs.shader = map
for i in 0...(rows * columns) {
let ball = SKSpriteNode(texture: texture)
let body = SKPhysicsBody(circleOfRadius: radius)
body.friction = 0.1
body.restitution = 0.8
ball.physicsBody = body
ball.position = CGPoint(x: centre.x + CGFloat(i % columns) * radius, y: centre.y + CGFloat(i / rows) * radius)
ball.blendMode = .Add
metaballs.addChild(ball) // change this to "addChild(ball)", and the nodes don't get culled
}
addChild(metaballs)
physicsWorld.speed = 0.2
backgroundColor = SKColor.darkGrayColor()
}
func createMetaballTexture(radius: CGFloat) -> SKTexture {
let metaballScale: CGFloat = 8
let ellipseOrigin = (metaballScale / 2) - 1.5
UIGraphicsBeginImageContext(CGSize(width: radius * metaballScale, height: radius * metaballScale))
let context = UIGraphicsGetCurrentContext()
SKColor.whiteColor().setFill()
CGContextFillEllipseInRect(context, CGRect(x: radius * ellipseOrigin, y: radius * ellipseOrigin, width: radius * 3, height: radius * 3))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return SKTexture(CGImage: blur(image!))
}
}
func blur(image: UIImage) -> CGImage {
let gaussianBlurFilter = CIFilter(name: "CIGaussianBlur")
let inputImage = CIImage(CGImage: image.CGImage!)
gaussianBlurFilter?.setValue(inputImage, forKey:kCIInputImageKey)
gaussianBlurFilter?.setValue(5, forKey: kCIInputRadiusKey)
let outputImage = gaussianBlurFilter?.outputImage
let context = CIContext(options: nil)
return context.createCGImage(outputImage!, fromRect: inputImage.extent)
}