3

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

Community
  • 1
  • 1
animal_chin
  • 6,610
  • 9
  • 37
  • 41
  • 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 blackness – neo Aug 14 '15 at 14:01
  • 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 Aug 14 '15 at 14:07
  • 1
    You are right, after clicking the image, I was able to see them. – neo Aug 14 '15 at 14:09
  • 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 Aug 14 '15 at 14:17
  • @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 Aug 14 '15 at 14:24
  • ah yep, I missed the comment: // re-draw the texture from the node hierarchy self.canvasTexture = [self.view textureFromNode:self.canvasNode]; – Chris Aug 14 '15 at 14:27
  • @animal_chin You should be able to move them. Name them and enumerate them and apply action to them. That would be one way. Or, access them by the name in update like you are doing already. https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKNode_Ref/#//apple_ref/occ/instm/SKNode/enumerateChildNodesWithName:usingBlock: And, why don't you use emitters for effect like this ? (or that isn't possible for your setup ? ) – Whirlwind Aug 14 '15 at 18:29

2 Answers2

2

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.

Chris
  • 2,739
  • 4
  • 29
  • 57
  • thanks for the answer, you're absolutely correct! ... i've got same results as you, tried all the options... it's really weird. like...really REALLY weird :) i'm gonna spend some time on it and will let you know if i figure something out... – animal_chin Aug 17 '15 at 13:29
  • Sadly, no. I was trying several things two weeks ago (mostly based on your observations), but didn't make any progress worth mentioning. I totally agree that this has to do something with compiler as it's really weird and I'm unable to explain it any other way... I will let you know if I found out something in the future (this is a side project, I'm working on it only sometimes). – animal_chin Sep 09 '15 at 08:26
0

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)];
MoDJ
  • 4,309
  • 2
  • 30
  • 65