SKSpriteNode 定位数学让我无法理解?
更具体地说,从
UIBezierPath
创建 CGRect
所需的定位。
接下来是一些简短的代码片段...
roomWidth = UIScreen.main.bounds.width
roomHeight = UIScreen.main.bounds.height
// trackOffset = a constant
tracksWidth = roomWidth - 2*trackOffset // inset from screen edge
tracksHeight = 0.40*roomHeight - trackOffset
// the `SKSpriteNode` = myTracks
myTracks.anchorPoint = CGPoint(x: 0.5, y: 0.5)
tracksPosX = 0.0
tracksPosY = -roomHeight/2 + trackOffset + tracksHeight/2
myTracks.position = CGPoint(x: tracksPosX, y: tracksPosY)
后来,我从我的
UIBezierPath
创建了一个 trackRect
,定义如下:
trackRect = CGRect(x: tracksPosX - tracksWidth/2,
y: tracksPosY,
width: tracksWidth,
height: tracksHeight)
trainPath = UIBezierPath(ovalIn: trackRect)
但是有些问题
y: tracksPosY
因为在iOS中,原点位于左上角,矩形向右下角延伸。
鉴于 Apple 的此规范,它不应该是:
y: tracksPosY + tracksHeight/2
?
我们需要尝试简化事情——并逐步进行。
所以,让我们使用一个简单的游戏场景,我们将定义一个
rect
并添加一个具有矩形路径的 SKShapeNode
和一个具有椭圆形路径的 SKShapeNode
:
import UIKit
import GameplayKit
class SimpleGameScene: SKScene {
override func didMove(to view: SKView) {
guard let thisSKView = self.view else { return }
let sz = thisSKView.frame.size
let offset: CGFloat = 80.0
let x: CGFloat = offset
let y: CGFloat = offset
let w: CGFloat = sz.width - (offset * 2.0)
let h: CGFloat = sz.height * 0.4
let myRect: CGRect = .init(x: x, y: y, width: w, height: h)
let shapeNodeRect = SKShapeNode(path: UIBezierPath(rect: myRect).cgPath)
shapeNodeRect.lineWidth = 7
shapeNodeRect.strokeColor = .darkGray
addChild(shapeNodeRect)
let shapeNodeOval = SKShapeNode(path: UIBezierPath(ovalIn: myRect).cgPath)
shapeNodeOval.lineWidth = 5
shapeNodeOval.strokeColor = .systemBlue
addChild(shapeNodeOval)
}
}
看起来像这样:
并且,正如我们所知,y 轴与场景套件“反转”,因此我们的原点为
80,80
,宽度和高度为正值:
如果我们使用相同的值在“普通”应用程序中定义矩形,请使用
CAShapeLayer
而不是场景套件节点:
import UIKit
class ShapeTestVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .init(red: 0.148, green: 0.148, blue: 0.148, alpha: 1.0)
let thisSKView = UIView()
thisSKView.frame = view.bounds
view.addSubview(thisSKView)
let sz = thisSKView.frame.size
let offset: CGFloat = 80.0
let x: CGFloat = offset
let y: CGFloat = offset
let w: CGFloat = sz.width - (offset * 2.0)
let h: CGFloat = sz.height * 0.4
let myRect: CGRect = .init(x: x, y: y, width: w, height: h)
let shapeNodeRect = CAShapeLayer()
shapeNodeRect.path = UIBezierPath(rect: myRect).cgPath
shapeNodeRect.lineWidth = 7
shapeNodeRect.strokeColor = UIColor.darkGray.cgColor
shapeNodeRect.fillColor = UIColor.clear.cgColor
thisSKView.layer.addSublayer(shapeNodeRect)
let shapeNodeOval = CAShapeLayer()
shapeNodeOval.path = UIBezierPath(ovalIn: myRect).cgPath
shapeNodeOval.lineWidth = 5
shapeNodeOval.strokeColor = UIColor.systemBlue.cgColor
shapeNodeOval.fillColor = UIColor.clear.cgColor
thisSKView.layer.addSublayer(shapeNodeOval)
}
}
我们得到这个:
具有非倒置 y 轴:
这个问题可能令人困惑的是,倒矩形或椭圆形看起来没有任何不同。
所以,让我们添加一个“箭头”贝塞尔曲线路径形状:
let p1: CGPoint = .init(x: myRect.midX - 60.0, y: myRect.minY + 80.0)
let p2: CGPoint = .init(x: myRect.midX, y: myRect.minY)
let p3: CGPoint = .init(x: myRect.midX + 60.0, y: myRect.minY + 80.0)
let p4: CGPoint = .init(x: myRect.midX, y: myRect.maxY)
let p5: CGPoint = .init(x: myRect.minX, y: myRect.maxY)
let p6: CGPoint = .init(x: myRect.maxX, y: myRect.maxY)
let pth = UIBezierPath()
pth.move(to: p1)
pth.addLine(to: p2)
pth.addLine(to: p3)
pth.move(to: p2)
pth.addLine(to: p4)
pth.move(to: p5)
pth.addLine(to: p6)
让我们将其添加为“普通”应用程序中的另一个
CAShapeLayer
:
let shapeNodeArrow = CAShapeLayer()
shapeNodeArrow.path = pth.cgPath
shapeNodeArrow.lineWidth = 3
shapeNodeArrow.strokeColor = UIColor.systemRed.cgColor
shapeNodeArrow.fillColor = UIColor.clear.cgColor
thisSKView.layer.addSublayer(shapeNodeArrow)
我们得到了这个(如预期的那样):
如果我们随后将该箭头贝塞尔曲线路径添加为场景套件应用程序中的另一个
SKShapeNode
:
let shapeNodeArrow = SKShapeNode(path: pth.cgPath)
shapeNodeArrow.lineWidth = 3
shapeNodeArrow.strokeColor = .systemRed
addChild(shapeNodeArrow)
我们得到这个结果:
现在很容易看到反转的 y 轴也反转了我们创建的路径。
再次简化您正在做的事情,以便更容易学习和理解正在发生的事情。它还有助于添加
print(...)
语句或调试断点来评估您的计算。
使用您发布的代码,如果我设置
trackOffset = 80.0
然后最后设置 print(trackRect)
,我会得到(在 iPhone 15 Pro 上):
(-116.5, -215.6, 233.0, 260.8)
这几乎肯定不是你想要的矩形原点。
每个示例的完整代码...
“普通”应用程序,带有
CAShapeLayer
:
class ShapeTestVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .init(red: 0.148, green: 0.148, blue: 0.148, alpha: 1.0)
let thisSKView = UIView()
thisSKView.frame = view.bounds
view.addSubview(thisSKView)
let sz = thisSKView.frame.size
let offset: CGFloat = 80.0
let x: CGFloat = offset
let y: CGFloat = offset
let w: CGFloat = sz.width - (offset * 2.0)
let h: CGFloat = sz.height * 0.4
let myRect: CGRect = .init(x: x, y: y, width: w, height: h)
let shapeNodeRect = CAShapeLayer()
shapeNodeRect.path = UIBezierPath(rect: myRect).cgPath
shapeNodeRect.lineWidth = 7
shapeNodeRect.strokeColor = UIColor.darkGray.cgColor
shapeNodeRect.fillColor = UIColor.clear.cgColor
thisSKView.layer.addSublayer(shapeNodeRect)
let shapeNodeOval = CAShapeLayer()
shapeNodeOval.path = UIBezierPath(ovalIn: myRect).cgPath
shapeNodeOval.lineWidth = 5
shapeNodeOval.strokeColor = UIColor.systemBlue.cgColor
shapeNodeOval.fillColor = UIColor.clear.cgColor
thisSKView.layer.addSublayer(shapeNodeOval)
let p1: CGPoint = .init(x: myRect.midX - 60.0, y: myRect.minY + 80.0)
let p2: CGPoint = .init(x: myRect.midX, y: myRect.minY)
let p3: CGPoint = .init(x: myRect.midX + 60.0, y: myRect.minY + 80.0)
let p4: CGPoint = .init(x: myRect.midX, y: myRect.maxY)
let p5: CGPoint = .init(x: myRect.minX, y: myRect.maxY)
let p6: CGPoint = .init(x: myRect.maxX, y: myRect.maxY)
let pth = UIBezierPath()
pth.move(to: p1)
pth.addLine(to: p2)
pth.addLine(to: p3)
pth.move(to: p2)
pth.addLine(to: p4)
pth.move(to: p5)
pth.addLine(to: p6)
let shapeNodeArrow = CAShapeLayer()
shapeNodeArrow.path = pth.cgPath
shapeNodeArrow.lineWidth = 3
shapeNodeArrow.strokeColor = UIColor.systemRed.cgColor
shapeNodeArrow.fillColor = UIColor.clear.cgColor
thisSKView.layer.addSublayer(shapeNodeArrow)
}
}
每个示例的完整代码...
场景套件应用程序与
SKShapeNode
:
class SimpleGameScene: SKScene {
override func didMove(to view: SKView) {
guard let thisSKView = self.view else { return }
let sz = thisSKView.frame.size
let offset: CGFloat = 80.0
let x: CGFloat = offset
let y: CGFloat = offset
let w: CGFloat = sz.width - (offset * 2.0)
let h: CGFloat = sz.height * 0.4
let myRect: CGRect = .init(x: x, y: y, width: w, height: h)
let shapeNodeRect = SKShapeNode(path: UIBezierPath(rect: myRect).cgPath)
shapeNodeRect.lineWidth = 7
shapeNodeRect.strokeColor = .darkGray
addChild(shapeNodeRect)
let shapeNodeOval = SKShapeNode(path: UIBezierPath(ovalIn: myRect).cgPath)
shapeNodeOval.lineWidth = 5
shapeNodeOval.strokeColor = .systemBlue
addChild(shapeNodeOval)
let p1: CGPoint = .init(x: myRect.midX - 60.0, y: myRect.minY + 80.0)
let p2: CGPoint = .init(x: myRect.midX, y: myRect.minY)
let p3: CGPoint = .init(x: myRect.midX + 60.0, y: myRect.minY + 80.0)
let p4: CGPoint = .init(x: myRect.midX, y: myRect.maxY)
let p5: CGPoint = .init(x: myRect.minX, y: myRect.maxY)
let p6: CGPoint = .init(x: myRect.maxX, y: myRect.maxY)
let pth = UIBezierPath()
pth.move(to: p1)
pth.addLine(to: p2)
pth.addLine(to: p3)
pth.move(to: p2)
pth.addLine(to: p4)
pth.move(to: p5)
pth.addLine(to: p6)
let shapeNodeArrow = SKShapeNode(path: pth.cgPath)
shapeNodeArrow.lineWidth = 3
shapeNodeArrow.strokeColor = .systemRed
addChild(shapeNodeArrow)
}
}