如何集成UIDevice旋转并在旋转后创建新的UIBezierPath?
我的挑战是成功集成
UIDevice
旋转并在每次 UIBezierPath
旋转时创建一个 new
UIDevice
。
编辑: 根据 DonMag 的建议更改为
viewDidLayoutSubviews
。这是有道理的,因为我希望在调整所有 SKSpriteNode 的大小并重新定位后,生成一个新的 UIBezierPath 旋转后。
哦,我多么希望这能奏效..但它确实奏效了没有
作为序言,我在之间来回跳动
NotificationCenter.default.addObserver(self,
selector: #selector(rotated),
name: UIDevice.orientationDidChangeNotification,
object: nil)
在我的
viewDidLoad()
内与一起调用
@objc func rotated() {
}
和
override func viewDidLayoutSubviews() {
// please see code below
}
当我实现
viewDidLayoutSubviews()
时,与 rotated()
相比,我的成功要好得多。所以让我提供 viewDidLayoutSubviews()
的详细代码。
我得出的结论是,每次旋转
UIDevice
时,都需要生成 new UIBezierPath
,因为我的各种 positions
的 sizes
和 SKSprieNodes
会发生变化。
我绝对不是说我必须在每次旋转时创建一个新的
UIBezierPath
..只是说我认为我必须这样做。
代码开始
// declared at the top of my `GameViewController`:
var myTrain: SKSpriteNode!
var savedTrainPosition: CGPoint?
var trackOffset = 60.0
var trackRect: CGRect!
var trainPath: UIBezierPath!
我的
UIBezierPath
创作和SKAction.follow
代码如下:
// called with my setTrackPaths() – see way below
func createTrainPath() {
// savedTrainPosition initially set within setTrackPaths().
// We no longer keep tabs on this Position because
// UIBezierPath's built-in .currentPoint does that for us.
trackRect = CGRect(x: savedTrainPosition!.x,
y: savedTrainPosition!.y,
width: tracksWidth,
height: tracksHeight)
trainPath = UIBezierPath(ovalIn: trackRect)
trainPath = trainPath.reversing() // makes myTrain move CW
} // createTrainPath
func startFollowTrainPath() {
let theSpeed = Double(5*thisSpeed)
var trainAction = SKAction.follow(
trainPath.cgPath,
asOffset: false,
orientToPath: true,
speed: theSpeed)
trainAction = SKAction.repeatForever(trainAction)
createPivotNodeFor(myTrain)
myTrain.run(trainAction, withKey: runTrainKey)
} // startFollowTrainPath
func stopFollowTrainPath() {
guard myTrain == nil else {
myTrain.removeAction(forKey: runTrainKey)
savedTrainPosition = myTrain.position
return
}
} // stopFollowTrainPath
这是详细的
viewWillLayoutSubviews
我之前承诺过的:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if (thisSceneName == "GameScene") {
// code to pause moving game pieces
setGamePieceParms() // for GamePieces, e.g., trainWidth
setTrackPaths() // for trainPath
reSizeAndPositionNodes()
// code to resume moving game pieces
} // if (thisSceneName == "GameScene")
} // viewDidLayoutSubviews
func setGamePieceParms() {
if (thisSceneName == "GameScene") {
roomScale = 1.0
let roomRect = UIScreen.main.bounds
roomWidth = roomRect.width
roomHeight = roomRect.height
roomPosX = 0.0
roomPosY = 0.0
tracksScale = 1.0
tracksWidth = roomWidth - 4*trackOffset // inset from screen edge
#if os(iOS)
if UIDevice.current.orientation.isLandscape {
tracksHeight = 0.30*roomHeight
}
else {
tracksHeight = 0.38*roomHeight
}
#endif
// center horizontally
tracksPosX = roomPosX
// flush with bottom of UIScreen
let temp = roomPosY - roomHeight/2
tracksPosY = temp + trackOffset + tracksHeight/2
trainScale = 2.8
trainWidth = 96.0*trainScale // original size = 96 x 110
trainHeight = 110.0*trainScale
trainPosX = roomPosX
#if os(iOS)
if UIDevice.current.orientation.isLandscape {
trainPosY = temp + trackOffset + tracksHeight + 0.30*trainHeight
}
else {
trainPosY = temp + trackOffset + tracksHeight + 0.20*trainHeight
}
#endif
} // setGamePieceParms
// a work in progress
func setTrackPaths() {
if (thisSceneName == "GameScene") {
if (savedTrainPosition == nil) {
savedTrainPosition = CGPoint(x: tracksPosX - tracksWidth/2, y: tracksPosY)
}
else {
savedTrainPosition = CGPoint(x: tracksPosX - tracksWidth/2, y: tracksPosY)
}
createTrainPath()
} // if (thisSceneName == "GameScene")
} // setTrackPaths
func reSizeAndPositionNodes() {
myTracks.size = CGSize(width: tracksWidth, height: tracksHeight)
myTracks.position = CGPoint(x: tracksPosX, y: tracksPosY)
// more Nodes here ..
}
代码结束
我的理论表明,当我在每次
setTrackPaths()
旋转时调用 UIDevice
时,应该调用 createTrainPath()
。
就
UIBezierPath
而言,视觉上没有发生任何有意义的事情..直到我打电话给 startFollowTrainPath()
。
底线
就在那时,我确信已经创建了一个新的
UIBezierPath
,因为当我旋转createTrainPath()
时调用UIDevice
时,它应该是这样的。new UIBezierPath
不是新的,而是旧的。
UIBezierPath
来适应调整大小和重新定位的
SKSpriteNode
?
使用
scene.scaleMode = .resizeFill
时,我们可以在
override func didChangeSize(_ oldSize: CGSize)
类中实现SKScene
。当场景大小发生变化时(例如设备旋转时),将调用此函数。因此,对于一个非常简单的示例,如下所示:
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
var scene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
scene = GameScene(size: view.frame.size)
scene.scaleMode = .resizeFill
if let skView = view as? SKView {
skView.presentScene(scene)
}
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .all
}
override var prefersStatusBarHidden: Bool {
return true
}
}
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var spOval: SKShapeNode!
var myTrain: SKSpriteNode!
var trainPath: UIBezierPath!
var currentSize: CGSize = .zero
override func didMove(to view: SKView) {
// ellipse frame will be set in updateFraming
spOval = SKShapeNode(ellipseIn: .zero)
spOval.lineWidth = 5
spOval.strokeColor = .lightGray
addChild(spOval)
myTrain = SKSpriteNode(imageNamed: "arrow2")
addChild(myTrain)
updateFraming()
startAnim()
}
override func didChangeSize(_ oldSize: CGSize) {
if let v = self.view {
// this can be called multiple times on device rotation,
// so we only want to update the framing and animation
// if the size has changed
if currentSize != v.frame.size {
currentSize = v.frame.size
updateFraming()
startAnim()
}
}
}
func updateFraming() {
// self.view is optional, so safely unwrap
guard let thisSKView = self.view else { return }
let sz = thisSKView.frame.size
// make the ellipse width equal to view width minus 120-points on each side
let w: CGFloat = sz.width - 240.0
// if view is wider than tall (landscape)
// set ellipse height to 30% of view height
// else (portrait)
// set ellipse height to 38% of view height
let h: CGFloat = sz.width > sz.height ? sz.height * 0.3 : sz.height * 0.38
// center horizontally
let x: CGFloat = (sz.width - w) * 0.5
// put bottom of ellipse 40-points from bottom of view
let y: CGFloat = 40.0
let r: CGRect = .init(x: x, y: y, width: w, height: h)
// create the "path to follow"
trainPath = UIBezierPath(ovalIn: r).reversing()
// update the visible oval
spOval.path = trainPath.cgPath
}
func startAnim() {
var trainAction = SKAction.follow(
trainPath.cgPath,
asOffset: false,
orientToPath: true,
speed: 200.0)
trainAction = SKAction.repeatForever(trainAction)
myTrain.run(trainAction, withKey: "myKey")
}
}