9

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)
}
OliverD
  • 1,074
  • 13
  • 19
  • 1
    I have the same problem. However in my case the `node count` label still indicates 200-something nodes. Even if they are no longer visibile on the screen. – Luca Angeletti Nov 01 '16 at 20:18
  • 1
    Steps to recreate the issue: __1)__ add an `SKEffectNode` to the scene. __2)__ add at least 200 `SKSpriteNode(s)` (with physics bodies) to the `SKEffectNode`. __3)__ Add some heavy physics simulation to cause a frame drop (like changing the gravity vector at every touch). __4)__ After the frame drop the 200-something `SKSpriteNode(s)` are no longer visible on the screen, even if the `node count` still count them and their physics bodies are still there. – Luca Angeletti Nov 01 '16 at 20:21
  • This is absolutely amazing. Must be a skeleton staff doing testing of SpriteKit, and only a ghost testing SceneKit updates. Over the last couple of years Apple has done some bizarre attempts at optimisation in SpriteKit, SceneKit, Core Animation and Metal, all of which are completely opaque. Lately there's efforts to force frame rates to fixed rates when 60fps isn't consistently possible. Odd, because it's mostly iOS interrupts that cause stutters. Similarly, in other optimisation efforts, for a couple of years, for example, shouldRasterize basically had a mind of its own, in Core Animation. – Confused Nov 02 '16 at 13:38

0 Answers0