5

I'm learning SpriteKit game development for the fun of it & I've run across a seemingly simple problem that has me stumped.

Basically, after I scale a textured SKSpriteNode, the frame is NOT what I expect. I have figured out a few hacks to force it to what I want, but I'm trying to understand what is going on. Any ideas appreciated!

Here's my code WITHOUT SCALING:

func addSpaceship()
{
    let spaceship = SKSpriteNode.init(imageNamed: "rocketship.png")
    spaceship.name = "spaceship"
//  spaceship.setScale(0.50)

    let debugFrame = SKShapeNode.init(rect: spaceship.frame)
    debugFrame.strokeColor = SKColor.greenColor()
    spaceship.addChild(debugFrame)

    spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - 150)

    self.addChild(spaceship)


}

And my app looks like this: Unscaled ship with green debug frame

Now, if I comment back in the line of code which scales it (spaceship.setScale(0.50)), I get this:

Scaled ship with green debug frame

Notice that the spaceship is scaled down in the second image, but the frame is scaled even smaller. Why?

If I move the scaling line to after I add the spaceship to the scene, it does what I expect, but that seems wrong:

Here's the code with setScale called after adding the spaceship to the scene:

func addSpaceship()
{
    let spaceship = SKSpriteNode.init(imageNamed: "rocketship.png")
    spaceship.name = "spaceship"

    let debugFrame = SKShapeNode.init(rect: spaceship.frame)
    debugFrame.strokeColor = SKColor.greenColor()
    spaceship.addChild(debugFrame)

    spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - 150)

    self.addChild(spaceship)
    spaceship.setScale(0.50)


}

And here is what running my app looks like then:

Scaling after adding to the scene

So that works, but why is it necessary?

It has been suggested below, that this is a bug with SKShapeNode. But, replacing the SKShapeNode with an SKLabelNode has the same problem:

func addSpaceship()
{
    let spaceship = SKSpriteNode.init(imageNamed: "rocketship.png")
    spaceship.name = "spaceship"
    spaceship.setScale(0.50)

    let scoreNode = SKLabelNode(text: "100")
    scoreNode.position = CGPointMake(CGRectGetMidX(spaceship.frame), CGRectGetMaxY(spaceship.frame))

    scoreNode.fontColor = SKColor.redColor()
    scoreNode.fontSize = 15.0
    scoreNode.fontName = "Monaco"
    scoreNode.zPosition = 10.0
    spaceship.addChild(scoreNode)


    spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - 150)

    self.addChild(spaceship)
}

which gives us:

Same problem with an SKLabelNode

The intent is to have the score label (scoreNode) centered above the rocket, but as you can see it is on top of the top porthole. There is just something wrong with the spaceship's frame after I call spaceship.setScale.

I have made one additional discovery: the setScale call does not need to be after I add the spaceship to the scene. It just needs to be after I add the debugFrame/scoreNode to the spaceship. If I setScale AFTER that point, all is well:

func addSpaceship()
{
    let spaceship = SKSpriteNode.init(imageNamed: "rocketship.png")
    spaceship.name = "spaceship"

    let scoreNode = SKLabelNode(text: "100")
    scoreNode.position = CGPointMake(CGRectGetMidX(spaceship.frame), CGRectGetMaxY(spaceship.frame))

    scoreNode.fontColor = SKColor.redColor()
    scoreNode.fontSize = 15.0
    scoreNode.fontName = "Monaco"
    scoreNode.zPosition = 10.0
    spaceship.addChild(scoreNode)

    spaceship.setScale(0.50)

    spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - 150)

    self.addChild(spaceship)
}

which results in:

This worked

WonderMonster
  • 220
  • 3
  • 9

2 Answers2

5

Your problem may be in the order of these two lines :

  spaceship.setScale(0.50)
  let debugFrame = SKShapeNode.init(rect: spaceship.frame)

You scaled down the spaceship and then calculate the size of the rectangle with the scaled spaceship. Then when rendering the rectangle is scaled down to half its size which is quarter of the original spaceship size.

If you swap the lines, it should work as expected.

In general, it is better to make compose in the real size and then scale the whole just before adding it to the scene.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
  • Yes but this only happens when debugframe is a child of spaceship. If debugframe is added to the scene directly then it isn't scaled twice. Why is that – hamobi Dec 30 '14 at 16:47
  • 3
    It is not scaled twice! Suppose you have spaceship size equals to Ws,Hs. You scale down the spaceship so it has now size Ws/2,Hs/2. Then you compute an initial size for the debugframe Wd,Hd with Wd=Ws/2 and Hd=Hs/2, and now you set debugframe as the child of the spaceship so it is scaled down by 0.5 and is represented with size Wd/2,Hd/2 which gives you Ws/4,Hs/4. If you set debugframe as child of the scene, since the scene dosn't have a scale factor, your debugframe is represented with size Ws/2,Hs/2 which is what you want. – Jean-Baptiste Yunès Dec 30 '14 at 16:54
  • That makes more sense – hamobi Dec 30 '14 at 16:56
  • Thanks again, if you guys are interested & have the time, I have posted a slightly related question & would love any insights: http://stackoverflow.com/questions/27718232/confusion-about-coordinates-frames-child-nodes-in-spritekit-on-ios – WonderMonster Dec 31 '14 at 08:20
1

First of all, let me preface with SKShapeNode is a really funky (maybe buggy class). At least it was in previous iterations of spritekit. If your goal is to add a debug rectangle for physics purposes. You can turn on showsPhysics on your SKView class inside of your GameViewController

Heres my experiment

    let redbox = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 100, height: 100))
    redbox.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
    redbox.setScale(0.5)

    let debugFrame = SKShapeNode(ellipseOfSize: redbox.size)
    debugFrame.strokeColor = SKColor.greenColor()

    self.addChild(redbox)
    redbox.addChild(debugFrame)

enter image description here

looks same as yours. if i call setScale after i add the nodes then my circle fills up my red square.

Also if I keep everything the same, but I just add my debugframe to the scene directly it will be scaled the right way, weird huh??

ok another test. note I set greenbox to 50% of redboxes size so we can see the redbox beneath. If the bug was occuring here than greenbox would end up filling 25% of the redbox.

    let redbox = SKSpriteNode(color: SKColor.redColor(), size: CGSize(width: 100, height: 100))
    redbox.position = CGPoint(x: self.size.width/2, y: self.size.height/2)
    redbox.setScale(0.5)

    let greenbox = SKSpriteNode(color: SKColor.greenColor(), size: CGSize(width: 50, height: 50))

    self.addChild(redbox)
    redbox.addChild(greenbox)

enter image description here

Ok so i did the same thing using another SKSpriteNode, and it behaves the way we'd expect. So for whatever reason, when you use an SKShapeNode as a child.. setScale is being called twice on it; unless you set the scale after adding the nodes to the scene. But this doesnt happen with SKSpriteNode.

The answer is.. I don't think there's a good answer. It's probably a bug. SKShapeNode has a history of bugs. SpriteKit has a few bugs =/ Someone correct me if I'm wrong.

hamobi
  • 7,940
  • 4
  • 35
  • 64
  • Thanks, but unfortunately, I don't think it is a bug with SKShapeNode, though perhaps it is a SpriteKit bug. I say this because, using a SKLabelNode presents a similar problem. I'll explain above in an edit. – WonderMonster Dec 30 '14 at 12:19
  • 1
    I guess it's best to use setscale last .. This problem only happens when one node is a child of the other. If they're on the same level you can call setscale wherever you want and it'll work. Odd. – hamobi Dec 30 '14 at 16:52
  • So I think jean baptiste got to the bottom of it – hamobi Dec 30 '14 at 16:58