在iOS的SpriteKit中,关于坐标、框架和子节点的困惑?

7

我正在学习iOS中的SpriteKit,并进行了大量的阅读和实验。在坐标、框架和子节点方面,我发现了一些令人困惑的问题。

考虑以下代码片段,我试图在我的宇宙飞船精灵周围绘制一个绿色的框以进行调试:

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

    // VERSION 1
    spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))

    let debugFrame = SKShapeNode(rect: spaceship.frame)

    debugFrame.strokeColor = SKColor.greenColor()
    spaceship.addChild(debugFrame)

    // VERSION 2
   //       spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))

    spaceship.setScale(0.50)


    self.addChild(spaceship)
}

如果我在标记为“版本1”的行上添加宇宙飞船精灵,我会得到这个:

Version 1

显然是错误的。但是如果我注释掉上面标记为“版本1”的那一行,并使用标记为“版本2”的那一行,我就能得到我想要的结果:

Version 2

请注意,标记为版本1和版本2的实际代码是相同的:spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))

那么,为什么设置宇宙飞船精灵的位置很重要呢?

在我看来,宇宙飞船精灵的位置与debugFrame的放置无关,因为debugFrame是宇宙飞船精灵的子级,因此,它的坐标应该相对于宇宙飞船精灵的框架。

谢谢 WM

*这与我昨天提出的一个问题有些相关:

在iOS上的SpriteKit中,缩放纹理精灵会产生错误的帧?

但是,a)我现在明白了那个问题,b)这个问题足够不同,值得提出自己的问题。

更新:

嗯-谢谢,大家提供的想法,但我仍然不明白,也许这会有所帮助。

我修改了我的代码以打印相关位置和框架:

func addSpaceship()
{
    let spaceship = SKSpriteNode.init(imageNamed: "rocketship.png")
    spaceship.name = "spaceship"
    println("Spaceship0 Pos \(spaceship.position) Frame = \(spaceship.frame)")

    // VERSION 1 (WRONG)
    spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
    println("Spaceship1 Pos \(spaceship.position) Frame = \(spaceship.frame)")

    let debugFrame = SKShapeNode(rect: spaceship.frame)
    println("DEBUG Pos \(debugFrame.position) Frame = \(debugFrame.frame)")

    debugFrame.strokeColor = SKColor.greenColor()
    spaceship.addChild(debugFrame)

    // VERSION 2 (RIGHT)
//      spaceship.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
//      println("Spaceship2 Pos \(spaceship.position) Frame = \(spaceship.frame)")

    spaceship.setScale(0.50)


    self.addChild(spaceship)
}

接下来,我分别运行了两种方式,并得到了以下结果。由于我理解"版本2",让我们从那里开始。

使用"版本2(右侧)"的代码运行后,我得到了以下结果:

Spaceship0 Pos (0.0,0.0) Frame = (-159.0,-300.0,318.0,600.0)
DEBUG Pos (0.0,0.0) Frame = (-159.5,-300.5,319.0,601.0)
Spaceship2 Pos (384.0,512.0) Frame = (225.0,212.0,318.0,600.0)

默认情况下,宇宙飞船节点(Spaceship0)的位置在屏幕左下角。它的框架也是根据其锚点(中心)设置在屏幕左下角来表示的,因此其框架矩形的原点为负数。
然后创建调试框架,其位置默认设置为0, 0,其框架设置与宇宙飞船相同。
接下来的代码(Spaceship2)将宇宙飞船节点移动到视图坐标系中的一个位置(384.0,512.0),并通过将新位置添加到旧原点来移动其框架的原点(即384 + -159 = 225)。
一切都很好。
不幸的是,我仍然无法理解版本1。
当我运行“版本1(错误)”时,得到以下结果:
Spaceship0 Pos (0.0,0.0) Frame = (-159.0,-300.0,318.0,600.0)
Spaceship1 Pos (384.0,512.0) Frame = (225.0,212.0,318.0,600.0)
DEBUG Pos (0.0,0.0) Frame = (0.0,0.0,543.5,812.5)

如上所述,飞船节点(Spaceship0)默认从屏幕左下角开始。其框架也是以锚点(中心)设置在屏幕左下角表示的,因此框架矩形的原点为负数。
代码(Spaceship1)将飞船节点移动到视图坐标为(384.0,512.0)的位置,并通过将新位置添加到旧原点来移动其框架的原点(即384 + -159 = 225)。
然后创建调试框架,其位置默认设置为0,0,框架设置为奇怪的宽度(543.5)和奇怪的高度(812.5)。由于我正在使用spaceship.frame初始化debugFrame.frame,所以我希望新的debugFrame.frame与飞船的frame相同 - 但实际上不是!调试框架的宽度和高度值似乎来自于将实际宽度和高度添加到飞船节点框架的原点(543.5 = 225 + 318.5)。但如果是这样,为什么t的框架矩形原点仍然是0,0而不是添加(225.0 + 0 = 225.0)后的值?
我不明白。

1
记录飞船框架,你会注意到它的起点取决于位置。由于形状是飞船的子级,因此形状的位置相对于飞船而言。因此,如果frame.origin为0,0,则一切正常,但如果为200,200,则形状将相应地偏移。 - CodeSmile
非常感谢 - 但恐怕我还是不明白。请看我上面的更新。 - WonderMonster
3个回答

7
你正在使用精灵的帧来创建形状节点,该帧是在场景坐标系下的。由于形状将成为精灵的子节点,因此应该在精灵的坐标系下创建节点。例如,如果你使用飞船的大小创建SKShapeNode
let debugFrame = SKShapeNode(rectOfSize: spaceship.size)

debugFrame 将会以飞船为中心而非飞船的框架。无论您何时设置飞船的位置,debugFrame 都将居中于飞船,并随着飞船的缩放/移动而相应进行调整。


1

针对你的“我不明白”的问题。你的代码没问题,但存在逻辑问题。“frame”是相对于父级坐标系进行计算的,请注意在你的代码中,飞船和调试窗口的父级不同。

解决问题的两种方法:

  1. 为调试窗口添加零偏移量,并将飞船作为其父级。这样做的优点是,调试窗口会随着飞船的移动和缩放而移动和缩放: let rectDebug = CGRectMake( 0, 0, spaceship.frame.size.width, spaceship.frame.size.height) let debugFrame = SKShapeNode(rect:rectDebug) spaceship.addChild(debugFrame)
  2. 使用飞船“frame”坐标将调试窗口添加到飞船的父级(即“self”)。这样做的缺点是,你必须在代码中自己移动和缩放调试窗口,因为它不会附加到飞船上: let debugFrame = SKShapeNode(rect:spaceship.frame) self.addChild(debugFrame)

这两种解决方案都被广泛使用。你应该选择最适合你情况的方案。

可能会出现三个其他问题:

1.我的代码中可能会有错误,因为我直接在Web窗口中输入,没有使用Xcode进行语法检查。

2. 两个对象的锚点可能不同,因此您可能需要在代码中进行对齐。

3. 还应考虑对象的zPosition,以便这些对象不会被其他一些对象隐藏。

针对您要解决的问题,也许showPhysics会有所帮助:

    skView.showsFPS = YES;
    skView.showsNodeCount = YES;
    skView.showsPhysics = YES;

0
这几乎是与您提到的另一个问题相同的问题。frame是一个属性,包含了positionsize。它们都受到其祖先节点的缩放影响。请阅读"节点将其许多属性应用于其后代"一节https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Nodes/Nodes.html#//apple_ref/doc/uid/TP40013043-CH3-SW13
再次强调:永远不要对节点应用缩放,除非您想要一些特殊或奇怪的效果,在完全构建其层次结构之前,永远不要移动节点。

但是在两种情况下,我都会在完全构建节点层次结构之后才进行缩放。我所做的唯一改变就是移动了设置父级位置的位置? - WonderMonster
好的,我犯了一个错误。但是你的问题和之前一样:假设X、Y是你的飞船的初始位置(通常为0,0),在版本1中,你将移动到X=S/2,Y=S/2的位置。然后,在S/2,S/2的位置创建一个调试框架,并缩小整个框架,但缩放不会缩放节点的位置,而是缩放其后代的位置,因此相对于飞船,你的调试框架位于S/4,S/4的位置。在版本2中,调试框架在0,0处初始化,当你移动飞船时,它会移动到S/2,S/2的位置,调试框架相对于飞船仍然在0,0的位置。 - Jean-Baptiste Yunès
非常感谢 - 但恐怕我仍然无法理解。请参阅我上面的更新。 - WonderMonster
1
似乎SKNodeShape有点问题。我尝试使用通过spriteNodeWithColor:size:初始化的SKSpriteNode,它运行良好。我选择的颜色是[NSColor colorWithCalibratedRed:0.5 green:0.5 blue:0.5 alpha:0.5],大小为spaceship.frame.size - Jean-Baptiste Yunès
我因为这个原因避免使用SKShapeNode。它并不总是按照你的期望行事。 - hamobi
我认为这不是一个错误。我认为上面的“0x141E”是正确的。SKShapeNode(rect :)希望在场景坐标空间中使用矩形,由于我没有将debugFrame作为场景的子节点,而是将其作为飞船的子节点,所以我的坐标有误。我的选择是使用SKShapeNode(rectOfSize :),如“0x141E”所建议的那样,或者(愚蠢地)使用SKShapeNode(rect :),但偏移原点以补偿不同的坐标系统:SKShapeNode(rect:CGRectMake(-1 *(spaceship.frame.size.width / 2),-1 *(spaceship.frame.size.height / 2),spaceship.frame.size.width,spaceship.frame.size.height)) - WonderMonster

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