在iOS的SpriteKit中,对带纹理的精灵进行缩放会产生不正确的框架?

5

我正在学习SpriteKit游戏开发,只是出于兴趣,但却遇到了一个看似简单的问题,让我束手无策。

基本上,在我缩放有纹理的SKSpriteNode之后,它的框架不是我期望的。我已经想出了一些方法来强制它达到我想要的效果,但我想要了解发生了什么。任何想法都会受到赞赏!

这是我的代码没有进行缩放:

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)


}

我的应用程序看起来是这样的: “带有绿色调试框架的未缩放船”src 现在,如果我把将其缩放的代码行注释掉(spaceship.setScale(0.50)),我会得到这个: “带有绿色调试框架的缩放船”src 请注意,在第二张图片中,宇宙飞船被缩小了,但边框也被缩小了。为什么?
如果我将缩放行移动到我将宇宙飞船添加到场景之后,它将做出我期望的操作,但那似乎是错误的:
这是添加宇宙飞船到场景后调用setScale的代码:
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)


}

这是我的应用程序运行时的情况:

添加到场景后的缩放

虽然这样可以工作,但为什么需要这样做呢?

下面有人建议说这是SKShapeNode的一个bug。但是,将SKShapeNode替换为SKLabelNode也会遇到同样的问题:

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)
}

这给我们带来了以下问题:

同样的问题出现在SKLabelNode上

本意是让得分标签(scoreNode)位于火箭正上方,但是你可以看到它在顶部舷窗的上方。在我调用spaceship.setScale之后,飞船的框架有些问题。

我发现了一个额外的问题:setScale调用不需要在我将飞船添加到场景后进行。它只需要在我将debugFrame/scoreNode添加到飞船后进行。如果我在那个时候进行setScale设置,一切都很好:

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)
}

这会导致:

This worked

2个回答

5

你的问题可能在这两行代码的顺序上:

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

你先缩小了宇宙飞船,然后计算了缩小后的宇宙飞船所对应的矩形的大小。接着,在渲染时,该矩形被缩小到其一半的大小,即原始宇宙飞船大小的四分之一。
如果你交换这些行的顺序,它应该可以正常工作。
通常,最好在实际大小上进行合成,然后在添加到场景之前缩放整个内容。

是的,但这只会在 debugframe 是 spaceship 的子级时发生。如果 debugframe 直接添加到场景中,则不会被缩放两次。为什么会这样呢? - hamobi
3
不会缩放两次!假设您的太空船大小为Ws,Hs。您将太空船缩小,使其大小现在为Ws/2,Hs/2。然后,您计算出debugframe的初始大小Wd、Hd,其中Wd=Ws/2,Hd=Hs/2,并将debugframe设置为太空船的子级,因此它被缩小了0.5倍,表示为大小Wd/2、Hd/2,这给出了Ws/4、Hs/4的大小。如果您将debugframe设置为场景的子级,由于场景没有缩放因子,您的debugframe将用大小Ws/2、Hs/2表示,这是您想要的结果。 - Jean-Baptiste Yunès
那样更有意义。 - hamobi
再次感谢,如果你们有兴趣并且有时间,我已经发布了一个稍微相关的问题,并且非常希望得到任何见解:https://dev59.com/u4bca4cB1Zd3GeqPWX_5 - WonderMonster

1
首先,让我说明一下,SKShapeNode 是一个非常奇怪(也许是有缺陷的)的类。至少在之前的 spritekit 版本中是这样的。如果您的目标是添加一个用于物理目的的调试矩形,则可以在 GameViewController 中的 SKView 类上启用 showsPhysics
以下是我的实验。
    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

看起来与您的相同。如果我在添加节点后调用setScale,那么我的圆形将填满我的红色正方形。

另外,如果我保持一切不变,但是直接将我的debugframe添加到场景中,它将按正确的比例缩放,很奇怪吧?

好的,再来一个测试。请注意,我将greenbox设置为redbox大小的50%,以便我们可以看到下面的redbox。如果错误发生在这里,那么greenbox最终会填充redbox的25%。

    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

好的,所以我使用另一个SKSpriteNode做了同样的事情,它的行为符合我们的预期。但出于某种原因,当你将SKShapeNode作为子节点使用时,它的setScale会被调用两次;除非你在将节点添加到场景后设置比例。但是SKSpriteNode不会发生这种情况。

答案是...我认为没有好的答案。这可能是一个bug。SKShapeNode有一些bug。SpriteKit也有一些bug =/ 如果我错了,请纠正我。


谢谢,但不幸的是,我不认为这是SKShapeNode的错误,尽管可能是SpriteKit的错误。我这么说是因为,使用SKLabelNode也会出现类似的问题。我会在编辑中解释。 - WonderMonster
1
我猜最好最后使用setscale。这个问题只会在一个节点是另一个节点的子节点时发生。如果它们在同一级别上,您可以在任何地方调用setscale并且它都可以工作。奇怪。 - hamobi
所以我认为Jean Baptiste已经找到了问题的根源。 - hamobi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接