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) :
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.
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)];