【问题标题】:Convert between UIKit and SpriteKit coordinate systems在 UIKit 和 SpriteKit 坐标系之间转换
【发布时间】:2020-01-02 01:23:22
【问题描述】:

我是 iOS 编程新手,几乎没有使用 SpriteKit 的经验,所以如果这是一个荒谬的问题,请原谅我。

我一直在尝试用二维数组制作一个基本网格,我更愿意从左上角使用它0, 0

在研究了 UIKit 和 SpriteKit 之间坐标系的差异之后,我遇到了 this answer 关于 Converting Between View and Scene Coordinates 但它似乎并没有像我想象的那样改变 y 值。我猜我没有正确使用它,或者这不是它的本意,我不知道。

当我尝试这个时:

let convertedCoordinates = convert(cellCoordinates, to: grid)
print(cellCoordinates.y, convertedCoordinates.y)

它似乎对 y 值没有任何影响。

我发现当我在let cellCoordinates = CGPoint(x: cx, y: cy) 行中更改为“y: -cy”时 然后它似乎确实按我希望的方式工作,但我不知道这是否是唯一的解决方案,或者这样做是否会在更复杂的情况下按预期工作。

这是我正在使用的代码:

import SpriteKit
import GameplayKit

class GameScene: SKScene {

   override func didMove(to view: SKView) {
      var background: SKShapeNode!
      background = SKShapeNode(rectOf: CGSize(width: frame.size.width, height: frame.size.height))
      background.fillColor = SKColor.lightGray
      self.addChild(background)

      let margin = CGFloat(50)
      let width = frame.size.width - margin
      let height = frame.size.height - margin

      let centerX = frame.midX - width / 2
      let centerY = frame.midY - height / 2

      var grid: SKShapeNode!
      grid = SKShapeNode(rectOf: CGSize(width: width, height: height))
      grid.strokeColor = SKColor.clear
      self.addChild(grid)

      let numRows = 2
      let numCols = 3

      let cellWidth = width / CGFloat(numCols)

      for r in 0..<numRows {
         for c in 0..<numCols {

            let cx = centerX + (cellWidth / 2) + (CGFloat(c) * cellWidth)
            let cy = centerY + (cellWidth / 2) + (CGFloat(r) * cellWidth)

            //***
            let cellCoordinates = CGPoint(x: cx, y: cy)
            //***

            let cellNode = SKShapeNode(rectOf: CGSize(width: cellWidth, height: cellWidth))

            let convertedCoordinates = convert(cellCoordinates, to: grid)
            print(cellCoordinates.y, convertedCoordinates.y)

            cellNode.strokeColor = SKColor.black
            cellNode.lineWidth = 5
            cellNode.fillColor = SKColor.darkGray
            cellNode.position = convertedCoordinates

            let textNode = SKLabelNode(text: String("\(r),\(c)"))
            textNode.fontName = "Menlo"
            textNode.fontSize = 60
            textNode.verticalAlignmentMode = .center
            textNode.position = convertedCoordinates

            grid.addChild(cellNode)
            grid.addChild(textNode)
         }
      }
   }
}

【问题讨论】:

    标签: swift xcode sprite-kit coordinate-systems


    【解决方案1】:

    这更像是一个哲学答案,而不是一个实施答案。至于以某种方式翻转 SpriteKit 的坐标系,好吧,你将不断地与它作斗争。最好直接接受系统。

    您的问题的本质更多是模型和视图的分离。当你说

    我希望从左上角开始使用它,0, 0

    您的意思是,您在心理上将游戏视为左上角有 0,0 的单元格网格。这是非常好的和自然的。那是你的游戏模型。但是你在代码中写了什么?

    let cx = centerX + (cellWidth / 2) + (CGFloat(c) * cellWidth)
    let cy = centerY + (cellWidth / 2) + (CGFloat(r) * cellWidth)
    let cellCoordinates = CGPoint(x: cx, y: cy)
    let convertedCoordinates = convert(cellCoordinates, to: grid)
    

    这就是你难以摆脱的观点。你有一个抽象模型网格,你用左上角的 r,c 和 0,0 进行索引,并且它的坐标以单位步进向下和向右增加。然后是模型的视图,它可能取决于屏幕分辨率、纵横比、设备方向等。如果您将两者在精神上分开,您通常会发现您可以将两个系统之间的转换隔离到一个小接口。在这些地方,你可能需要做一些事情,比如缩放轴或翻转其中一个,或者在一个方向上拉伸东西以匹配纵横比。

    在这种情况下,如果您从左上角首选 0,0 的心智模型开始,并考虑游戏的运作方式,则通常会根据单元格进行操作。好的,这表明二维数组或数组数组可能是自然的。也许细胞最终会成为你游戏中的一个类。他们可能会有一个 node 属性来存储 SpriteKit 节点。你可能会得到这样的结果:

    struct boardPosition {
      let row: Int
      let col: Int
    }
    
    class Cell {
      let pos: boardPosition
      let node: SKNode
    
      init(pos: boardPosition, in board: Board) {
        self.pos = pos
        node = SKShapeNode(...)
        board.node.addChild(node)
      }
    }
    
    class Board {
      let cells: [[Cell]]
      let node: SKNode
    
      init(numRows: Int, numColumns: Int) {
        ...
      }
    
      func movePiece(from: boardPosition, to: boardPosition) {
        let piece = cell[from.row][from.col].removePiece()
        cell[to.row][to.col].addPiece(piece)
      }
    }
    

    游戏的运作将根据您的心智模型进行。随着行索引的增加,单元格的SKNode 节点的 y 坐标恰好减小的事实将被完全掩盖。

    【讨论】:

    • 这是一个我还没有开始考虑的好点,不要指望不断地重新排列坐标系会更好,按照它的方式使用它。但是你能告诉我,convert( to:) 函数是否应该帮助解决这种情况,还是用于其他情况?你能给我一个你提到的翻译界面的基本例子吗?
    • 转换方法不适用于此。在 SpriteKit 中,您通常有一个节点树,并且坐标是相对的(例如,如果子节点在其父节点中的位置为 10,15,那么父节点中的点 10,15 对应于子节点中的 0,0)。如果您需要在坐标系之间切换(尽管很少需要),这就是转换方法的用途。
    • 关于一个例子,你大多写了一个已经和你的代码混在一起的例子。我唯一建议的是您将framegrid 混淆了一点。 Frame 指的是整个场景,并且您从中派生 centerX 和 centerY 。但随后您使用它们来计算作为grid 的子节点添加的节点的坐标,grid 有自己的独立坐标系。想想网格的自然坐标(0,0 是在中心还是角落取决于你)。计算网格空间中的单元格坐标。然后在您的场景中整体定位 grid
    【解决方案2】:

    将所有适用的节点和场景的锚点设置为 0,1 以使其安装到左上角并设置您的世界节点(如果您没有,我建议添加它,它是您的基本 SKNode用于放置所有游戏节点,允许您为不适用于游戏世界的事物使用单独的节点,例如 hud 和覆盖)yScale 为 -1 以使 y 向下而不是向上递增。

    编辑:

    在处理 SKShapeNodes 时,您不必担心图像被反转,除非您的形状不明显。为晦涩的形状设计 CGPath 时,只需翻转即可。

    shape.path = shape.path!.copy(using:CGAffineTransform(scaleX:1,y:-1))
    

    更大的问题是 SKShapeNode 没有锚点。相反,您需要移动整个 CGPath

    为此,请添加以下行:

    shape.path = shape.path!.copy(using:CGAffineTransform(translationX:shape.frame.width/2,y:shape.frame.height/2))
    

    如果将来处理 SKSprite 节点.... 这将导致您的资产倒置,因此您需要做的就是在导入之前翻转资产,使用辅助节点翻转 y 轴,或为所有节点分配 yScale 为 -1。在垂直导入之前翻转所有资产将是最便宜的方法,我相信你也可以在 xcassets 中翻转它,但是当我再次回到 MacOS 时,我需要验证这一点。

    【讨论】:

    • 我已经看到了这个建议,但它不会导致子节点中的艺术品倒置吗? (即,你必须绕过 yScaling 它们 -1 才能取消效果。)这就是为什么我认为这只是与自然作斗争。
    • 是的,只需插入第二个节点即可撤消它。感谢您的评论
    • 我还没有在 GameScene.sks 中做任何事情,到目前为止,我所拥有的一切都是由我发布的代码创建的。我刚刚设置了场景锚点,我可以看到它现在位于左上角。但我不知道如何按照您的建议更改 SKShapeNodes 的锚点或比例。我还尝试在场景编辑器中添加形状节点,但即便如此我也看不到任何设置其锚点或比例的方法。
    • SKShapeNodes 基于您提供的 CGPath 工作,稍微复杂一点。我认为您正在使用基于您的代码的基本形状,因此您无需担心在这方面 y 翻转,但您的锚点是不可能的。您需要将其包装在 SKNode 中,或移动 CGPath,因为它附带的 inits 不允许更改原点。
    • 我现在提供了如何使用 SKShapeNodes 执行此操作的代码
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-29
    • 2015-07-07
    • 1970-01-01
    • 2013-10-30
    • 2021-02-20
    相关资源
    最近更新 更多