3
votes

I'm trying to create a scene with SpriteKit which has thousands of sprites (~500 - 2000). Each sprite is just a white pixel 1x1 - there's no need to even use textures for them.

Adding this much sprites to the scene directly at once is impossible (or at least i think so). On iPhone 6 I end up with ~200 sprites added, then system ends the adding process because of memory and the rest of the sprites aren't added.

I have found a clever solution to this called Bit Blitting where all the sprites are added to a node, which is then "converted" to texture with textureFromNode: method and from this texture is then created a single sprite which will be finally added to a screen. It works great and I'm able to create more than 10 000 sprites at once with great fps.

My problem is that I can't move these sprites afterwards (= change position). The texture always remains the same no matter what i do. What am I missing?

My Code:

override func didMoveToView(view: SKView) {
    self.generateRandomSprites()
}

func generateRandomSprites() {

    let texture = SKTexture(imageNamed: "whitePixel")

    self.canvasNode = SKNode()

    log("started generating all sprites")

    for var i = 0;i < 1000;i++ {

        let width = self.scene!.frame.size.width
        let height = self.scene!.frame.size.height

        let x: CGFloat = CGFloat(arc4random_uniform(UInt32(width)))
        let y: CGFloat = CGFloat(arc4random_uniform(UInt32(height)))

        let cell = SKSpriteNode(texture: texture)
        cell.position = CGPointMake(x, y)

        self.arrCells.append(cell)
        self.canvasNode.addChild(cell)
    }

    self.canvasTexture = self.view!.textureFromNode(self.canvasNode)
    self.canvasSprite = SKSpriteNode(texture: self.canvasTexture, size: self.frame.size)
    self.canvasSprite.anchorPoint = ccp(0,0)

    self.addChild(self.canvasSprite)
}


override func update(currentTime: CFTimeInterval) {
    for oneCell in self.arrCells {
        oneCell.position = CGPointMake(oneCell.position.x + 1, oneCell.position.y)
    }

    self.canvasTexture = self.view!.textureFromNode(self.canvasNode)
    self.canvasSprite.texture = self.canvasTexture
} 

Screenshot from app (this is static, nothing happens) :

enter image description here

2
Your screen shot is black and you say your texture is white. How do you even know your sprite is on the visible side of the screen.All I see in your screen shot is blacknessneo
there are white points in the screenshot...it's just that they're 1px so you must zoom/have good resolution ... click on the picture and you'll see...animal_chin
You are right, after clicking the image, I was able to see them.neo
The node is written to a texture - an image. Moving the nodes will have no-effect until you re-render the image. You'll have to call generateRandomSprites again, but don't add the child again.Chris
@Chris not true! Read the article from the question. ... Have you tried to cycle trough self.canvasNode.children instead of your array? I can't test it atm. so it's only a suggestion.DevAndArtist

2 Answers

2
votes

This is not the answer, but a collection of things I have tried.

I'm really curious about that code. I tried it and it didn't work. I changed the update method to just put the points at random positions and it works:

So I have a question. Why can't the code just update from it's current position, or is that a red-herring?

override func update(currentTime: CFTimeInterval) {

    let width = self.scene!.frame.size.width
    let height = self.scene!.frame.size.height

    for oneCell in self.canvasNode.children as! [SKSpriteNode] {

        let x: CGFloat = CGFloat(arc4random_uniform(UInt32(width)))
        let y: CGFloat = CGFloat(arc4random_uniform(UInt32(height)))

        oneCell.position = CGPointMake(x, y)
    }

    self.canvasTexture = self.view!.textureFromNode(self.canvasNode)
    self.canvasSprite.texture = self.canvasTexture

}

ok, I've been playing with this even more, this works, just getting a random value and adding that to the position...

override func update(currentTime: CFTimeInterval) {

    let width = self.scene!.frame.size.width
    let height = self.scene!.frame.size.height

    for oneCell in self.canvasNode.children as! [SKSpriteNode] {

        let x2: CGFloat = CGFloat(arc4random_uniform(UInt32(10)))
        let y2: CGFloat = CGFloat(arc4random_uniform(UInt32(10)))
        let x:CGFloat = oneCell.position.x - x2
        let y:CGFloat = oneCell.position.y - y2
        let point = CGPointMake(x, y)

        oneCell.position = point
    }

    self.canvasTexture = self.view!.textureFromNode(self.canvasNode)
    self.canvasSprite.texture = self.canvasTexture

}

Now I think it's something to do with the compiler as this works:

override func update(currentTime: CFTimeInterval) {

    let width = self.scene!.frame.size.width
    let height = self.scene!.frame.size.height


    var x3:CGFloat = 10.0
    var y3:CGFloat = 10.0
    for oneCell in self.canvasNode.children as! [SKSpriteNode] {

        let x:CGFloat = oneCell.position.x - x3
        let y:CGFloat = oneCell.position.y - y3
        let point = CGPointMake(x, y)

        oneCell.position = point
        x3 += 0.01
    }


    self.canvasTexture = self.view!.textureFromNode(self.canvasNode)
    self.canvasSprite.texture = self.canvasTexture

}

More investigation, Interesting, if you use the value 1.3 and run the code, you can see the dots shift once. (http://llvm.org/docs/LangRef.html#simple-constants)

override func update(currentTime: CFTimeInterval) {

    let width = self.scene!.frame.size.width
    let height = self.scene!.frame.size.height

    for oneCell in self.canvasNode.children as! [SKSpriteNode] {

        let x:CGFloat = oneCell.position.x - 1.3
        let y:CGFloat = oneCell.position.y - 1.3
        let point = CGPointMake(x, y)

        oneCell.position = point
    }

    self.canvasTexture = self.view!.textureFromNode(self.canvasNode)
    self.canvasSprite.texture = self.canvasTexture

}

A number like 50.3333333333 shows visible shakes, but then seems to stop.

0
votes

To be honest, I think the approach you suggest is not going to perform well and it seems really complex for no reason. For example, with SpriteKit you can just create a node with a background texture color and make it 1x1 without doing any of this blit stuff. Like this:

SKSpriteNode *node = [SKSpriteNode spriteNodeWithColor:[UIColor whiteColor]
size:CGSizeMake(1,1)];