3

enter image description here

When press the screen, balls created with the following codes:

var n = 1
func addBall(_ x:CGFloat){
    let numShape =  SKShapeNode(circleOfRadius: 30)
    numShape.name  = "ball"
    numShape.position = CGPoint(x: x, y: frame.maxY-40)
    numShape.physicsBody = SKPhysicsBody(circleOfRadius: 30)
    numShape.fillColor = SKColor.white
    numShape.physicsBody?.density = 0.1
    numShape.physicsBody?.affectedByGravity = true
    numShape.physicsBody?.friction = 0;
    numShape.physicsBody?.restitution = 0.6
    numShape.physicsBody?.allowsRotation = true
    numShape.physicsBody?.isDynamic = true

    let numLabel = SKLabelNode(fontNamed: "Helvetica")
    numLabel.text = "\(n)"
    numLabel.name = "\(n)"
    numLabel.fontColor = .red
    numLabel.position = CGPoint(x: 0, y: 0)
    numLabel.verticalAlignmentMode = .center

    numShape.addChild(numLabel)
    self.addChild(numShape)

    n += 1
}

The balls can rotate, but their childNode numlabel don't rotate in company with them. I try to update their zRotation like below:

   override func update(_ currentTime: TimeInterval) {
        self.enumerateChildNodes(withName: "ball") {
            node, stop in
            if (node is SKShapeNode) {
                let ball = node as! SKShapeNode
                let num = ball.children[0]
                num.zRotation = ball.zRotation
            }
        }

    }

They still refuse to rotate. If I change zRotation directly like num.zRotation = 2, they work.

How can I make them rotate in company with SKShapeNode?

Thx.

jdleung
  • 1,088
  • 2
  • 10
  • 26
  • Where do you make balls to rotate ? Could you show this code please? – Coldsteel48 Jul 08 '17 at 10:56
  • @ColdSteel I have posted the codes. I update their zRotation in `override func update(_ currentTime: TimeInterval)` – jdleung Jul 08 '17 at 11:11
  • `num.zRotation = ball.zRotation` but what changes the ball zRotation ? I suggest you to log the ball.zRotation inside the update method and tell me if it changes. P.S. you don't need to synchronize the Z rotation of num with the ball while num is child of ball it will rotate with the ball – Coldsteel48 Jul 08 '17 at 11:43
  • @ColdSteel When starts, the ball drop down from top of the screen, and collisions change its zRotation about from -9.xx to 9.xx, but if there is no more collision, its zRotation becomes 0.0xxxxxx. I had found the SKLabelNode did not rotate at first, so I tried to synchronize them. ;-) – jdleung Jul 08 '17 at 11:57
  • 2
    @ColdSteel The solution found! I tried to edit each of the variable of physicsBody, I found that friction must not be 0. Thanks your help. – jdleung Jul 08 '17 at 12:31
  • @jdleung please post your findings as an answer, so others can benefit from this :) And so I can +1 your answer too :) – Fluidity Jul 08 '17 at 16:22
  • also, you are using a lot of shapenodes!! If your framerate drops, you should convert them to sprites (and to save battery!) Shape node needs _1 SK draw() per node, which is MUCH more than if using sprites. In fact, you can convert the shapeNode + labelNode to just _one_ spriteNode :) @fluidity if you want me to show you how. – Fluidity Jul 08 '17 at 16:24
  • @Fluidity Will SK really spend a draw call per each SKShapeNode even so the shape is Circle? (Wouldn't we expect SK to batch Circle nodes even so they are defined as SKShapeNode while all of them are circles with the same "texture"/ color ?). I think SK team could optimize and batch it quite easily. But nice to know thanks for pointing so :-) – Coldsteel48 Jul 09 '17 at 00:40
  • It will require at least one draw pass in the case of SKShapeNode, but it can require even more : https://stackoverflow.com/a/31610775 Not performant at all, and if should be used sparingly. It is likely that is meant only for debugging purposes. – Whirlwind Jul 09 '17 at 03:12
  • there are some times that you NEED to use a shapenode, like when you have to constantly alter a cgpath @Whirlwind otherwise you should bit-blit or convert it to a sprite – Fluidity Jul 09 '17 at 03:19
  • @Fluidity Yeah, I agree, but the point is, you should use them sparingly. Otherwise if wont work :) You can't have 100 of them on the screen without some fps drops (depending on device). Well sometimes, you can't even have 20 of them depending on a shape :) – Whirlwind Jul 09 '17 at 03:22
  • But if you make an app called "Drain My Battery", than uncontrolled using of `SKShapeNode` could be ideal :) – Whirlwind Jul 09 '17 at 03:29
  • @Fluidity But what would happen if you create an atlas programatically at runtime with those textures? It should be fine then (batch drawing should work). – Whirlwind Jul 09 '17 at 03:30
  • @Fluidity Comon guys - it is quite bugish IMO why SK can't just batch everything that uses the same texture (in this case SKColor) also it is as easy to batch the (built-in simple geometry like circle) I think we should feature request it from Apple. – Coldsteel48 Jul 09 '17 at 14:59
  • @ColdSteel we barely got anything this year from wwdc so wont get hopes up – Fluidity Jul 10 '17 at 02:17
  • @Fluidity This Year Apple Claimes to fix half of my reported bugs - so who knows :-0 – Coldsteel48 Jul 10 '17 at 11:10

1 Answers1

1

You need to set your friction to a number other than 0.

Also, concerning the shape node performance:

look at the draw count at the bottom of 50 shapes:

enter image description here

for _ in 1...50 {
  let x = arc4random_uniform(UInt32(frame.maxX));
  let xx = CGFloat(x) - size.width/4

  let y = arc4random_uniform(UInt32(frame.maxY))
  let yy = CGFloat(y) - size.width/4

  let shape = SKShapeNode(circleOfRadius: 50)
  shape.strokeColor = .blue
  shape.lineWidth = 1
  shape.position = CGPoint(x: xx, y: yy)
  addChild(shape)
}

But now compare that to this image of only 2 draws with only a few lines of refactoring:

func addFiftySprites() {

  let shape = SKShapeNode(circleOfRadius: 50)
  shape.strokeColor = .blue
  shape.lineWidth = 1

  let texture = SKView().texture(from: shape)

  for _ in 1...50 {
    let x = arc4random_uniform(UInt32(frame.maxX));
    let xx = CGFloat(x) - size.width/4

    let y = arc4random_uniform(UInt32(frame.maxY))
    let yy = CGFloat(y) - size.width/4

    let sprite = SKSpriteNode(texture: texture)
    sprite.position = CGPoint(x: xx, y: yy)

    addChild(sprite)
  }
}

enter image description here


The magic here is using let texture = SKView().texture(from: <SKNode>) to convert the shape to a sprite :) let sprite = SKSpriteNode(texture: texture)

Fluidity
  • 3,985
  • 1
  • 13
  • 34
  • And what you would do if there was many different textures ? :) Don't forget atlases. – Whirlwind Jul 09 '17 at 03:45
  • @Whirlwind I have never had any assets to work with... so.. :P I always just use shapes in my games haha – Fluidity Jul 09 '17 at 04:00
  • 1
    Really learn much more from your comments. Actually, the balls have more than 4 different colors. I had tried to create SKSpriteNode with different "ball background" textures, and add SkLabelNode as its child, the balls display in normal but the labels erratically don't show their text, sometimes in serval balls , sometimes all , sometimes none of them. :-( – jdleung Jul 09 '17 at 09:19
  • @jdleung interesting... perhaps a new question with that code? – Fluidity Jul 09 '17 at 09:21
  • @jdleung I wonder if that is as simple as a `.zPosition` issue? – Fluidity Jul 09 '17 at 09:24
  • @Fluidity Ok, I closed this question first and ask another. Hope you can help ;-) – jdleung Jul 09 '17 at 09:24
  • @Fluidity you may get the point, haha. I did not set any zPosition for both nodes, now I give an upper zPosition to the labels, they all show text now, I've tried more than 10 times. Thanks so much. P.S. 30 balls, I got 61 nodes, 25 draws, 20fps. – jdleung Jul 09 '17 at 09:34