Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In SpriteKit on iOS, scaling a textured sprite produces an incorrect frame?

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

like image 824
WonderMonster Avatar asked Dec 30 '14 08:12

WonderMonster


2 Answers

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.

like image 122
Jean-Baptiste Yunès Avatar answered Nov 15 '22 05:11

Jean-Baptiste Yunès


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.

like image 29
hamobi Avatar answered Nov 15 '22 05:11

hamobi