在UIKit和SpriteKit坐标系之间转换

时间:2020-01-02 01:23:22

标签: swift xcode sprite-kit coordinate-systems

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

我一直在尝试使用2D数组制作基本的网格,我更喜欢从左上角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值没有任何影响。

我发现当我在行y: -cy中更改为“ let cellCoordinates = CGPoint(x: cx, 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)
         }
      }
   }
}

2 个答案:

答案 0 :(得分: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,然后思考游戏的运行方式,那么通常是在单元格方面。好的,这表明2D数组或数组数组是自然的。也许细胞最终将成为您游戏中的一类。它们可能具有存储SpriteKit节点的node属性。您可能会遇到这样的事情:

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坐标随行索引的增加而减小的事实将被完全掩盖。

答案 1 :(得分:0)

设置所有适用的节点,并将场景的锚点设置为0.1,以将其安装到左上角并设置世界节点的坐标(如果您没有该节点,我建议添加它,这是一个基本的SKNode,您可以用来放置所有游戏节点,从而使您可以将一个单独的节点用于不适用于游戏世界的事物(例如平视显示器和叠加层)。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时,我需要进行验证。