UIKit 和 SpriteKit 坐标系之间的转换
Convert between UIKit and SpriteKit coordinate systems
我是iOS编程新手,对SpriteKit几乎没有任何经验,所以如果这个问题很荒谬,请原谅我。
我一直在尝试用二维数组制作一个基本网格,我更愿意从左上角开始使用它 0, 0
。
在研究了 UIKit 和 SpriteKit 之间坐标系的差异后,我遇到了 this answer about 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)
}
}
}
}
与其说是实施答案,不如说这是哲学答案。至于以某种方式翻转 SpriteKit 的坐标系,嗯,你将不断地与它作斗争。最好接受系统的现状。
虽然你的问题的本质更多的是模型和视图的分离。当你说
I would prefer to work with it from top-left being 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 开始您的心智模型并思考游戏的运作方式,通常会以单元格为单位。好的,这表明二维数组或数组数组可能是自然的。也许这些单元格最终会成为您游戏中的 class。他们可能会有一个存储 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 坐标恰好随着行索引的增加而减小的事实将被完全掩盖。
将所有适用的节点和场景的锚点设置为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 轴,或为所有节点分配 -1 的 yScale。在垂直导入之前翻转所有资产是最便宜的方法,我相信你也可以在 xcassets 中翻转它,但我需要在我再次使用 MacOS 时验证这一点。
我是iOS编程新手,对SpriteKit几乎没有任何经验,所以如果这个问题很荒谬,请原谅我。
我一直在尝试用二维数组制作一个基本网格,我更愿意从左上角开始使用它 0, 0
。
在研究了 UIKit 和 SpriteKit 之间坐标系的差异后,我遇到了 this answer about 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)
}
}
}
}
与其说是实施答案,不如说这是哲学答案。至于以某种方式翻转 SpriteKit 的坐标系,嗯,你将不断地与它作斗争。最好接受系统的现状。
虽然你的问题的本质更多的是模型和视图的分离。当你说
I would prefer to work with it from top-left being 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 开始您的心智模型并思考游戏的运作方式,通常会以单元格为单位。好的,这表明二维数组或数组数组可能是自然的。也许这些单元格最终会成为您游戏中的 class。他们可能会有一个存储 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 坐标恰好随着行索引的增加而减小的事实将被完全掩盖。
将所有适用的节点和场景的锚点设置为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 轴,或为所有节点分配 -1 的 yScale。在垂直导入之前翻转所有资产是最便宜的方法,我相信你也可以在 xcassets 中翻转它,但我需要在我再次使用 MacOS 时验证这一点。