How to integrate UIDevice rotation and creating a new UIBezierPath after rotation?
My challenge here is to successfully integrate UIDevice rotation and creating a new UIBezierPath every time the UIDevice is rotated.
(Please accept my apologies for this Post’s length .. but I can’t seem to avoid it)
As a preamble, I have bounced back and forth between
NotificationCenter.default.addObserver(self,
selector: #selector(rotated),
name: UIDevice.orientationDidChangeNotification,
object: nil)
called within my viewDidLoad() together with
@objc func rotated() {
}
and
override func viewWillLayoutSubviews() {
// please see code below
}
My success was much better when I implemented viewWillLayoutSubviews(), versus rotated() .. so let me provide detailed code just for viewWillLayoutSubviews().
I have concluded that every time I rotate the UIDevice, a new UIBezierPath needs to be generated because positions and sizes of my various SKSprieNodes change.
I am definitely not saying that I have to create a new UIBezierPath with every rotation .. just saying I think I have to.
Start of Code
// declared at the top of my `GameViewController`:
var myTrain: SKSpriteNode!
var savedTrainPosition: CGPoint?
var trackOffset = 60.0
var trackRect: CGRect!
var trainPath: UIBezierPath!
My UIBezierPath creation and SKAction.follow code is as follows:
// called with my setTrackPaths() – see way below
func createTrainPath() {
// savedTrainPosition initially set within setTrackPaths()
// and later reset when stopping + resuming moving myTrain
// via stopFollowTrainPath()
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
Here is the detailed viewWillLayoutSubviews I promised earlier:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
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")
} // viewWillLayoutSubviews
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 ..
}
End of Code
My theory says when I call setTrackPaths() with every UIDevice rotation, createTrainPath() is called.
Nothing happens of significance visually as far as the UIBezierPath is concerned .. until I call startFollowTrainPath().
Bottom Line
It is then that I see for sure that a new UIBezierPath has not been created as it should have been when I called createTrainPath() when I rotated the UIDevice.
The new UIBezierPath is not new, but the old one.
If you’ve made it this far through my long code, the question is what do I need to do to make a new UIBezierPath that fits the resized and repositioned SKSpriteNode?
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Someone somewhere in Apple Development needs to do something. This just cannot be allowed to continue. This has been reported by many others before me .. but nothing changes.
What their Reviewers do borders on Assault which is a Crime.
How much longer?
The very capable folk such as Quinn are tarnished by working along side some of the Reviewers. I truly feel sorry for Quinn.
Submitted my Monster Paddle Pong App that operates on Apple TV and iPad with a Game Controller.
SO the Tester uploads my App to their iPad and starts poking their finger all over the screen .. and NOTHING happens.
Maybe their reading skills aren’t ample because that Game Controller requirement is delineated in sentence #1.
Promotional Text:
Monster Paddle Pong is FUN! to play on a iPad + Apple TV with a Extended Game Controller. Match wits with your skill to react quickly. See the Description for more info.
Description Text:
Monster Paddle Pong is FUN! to play on a iPad or Apple TV with a Game Controller. It mischievously matches wits with your skill to react quickly.
It's 100% free, so give it a GO!
Pressing the Right Shoulder Button starts the Game and causes the Dinossaur Ball to start moving .. and unless the Ball is moving the Monster Paddle will not move.
As a matter of fact, until this Right Shoulder Button is first pressed, only the Left Shoulder Button works (described below).
This makes sense because after all what’s so hard in hitting a stationary target? Right?
Use your Game Controller’s A, B, X, Y buttons & the Joysticks to move the Monster Paddle and hit the moving Dinossaur Ball.
If you’re successful then your Score in the upper right corner advances. If instead your Monster Paddle hits one of the 4 Walls, your score decreases by 1.
All this is graphically depicted in the About Scene which can be accessed by pressing your Controller’s Left Shoulder Button once.
BTW, press this Left Shoulder a 2nd time, and you will see my Credits Scene.
I don’t want to get too detailed here because a big part of FUN! is the thrill of Discovering.
So, feel free to DISCOVER, most notably pressing all the buttons on your Controller to see the MAGIC! each of them makes happen.
Too Complex to Compile?
Ventura 13.4.1
New error after update to Xcode 14.3.1 from 14.3.0
// ... <= -(roomHeight/2 - kFudge) too complex to compile ??
if (playerPosY - playerHeight/2) <= (kFudge - roomHeight/2)
{
// ... this variation works, the previous one doesn't
}
kFudge and the other parms are declared Double! within AppDelegate.
I don't understand what is suddenly wrong after Xcode update.
Using Swift, how do I continuously test for a GamePad being on/connected?
When you build a Browser-based Canvas + Javascript Game, you do the above via:
function listenForGamepadConnected() {
window.addEventListener("gamepadconnected", (event) => {
// this is the BIGEE that is always testing
});
} // listenForGamepadConnected
And it definitely works!
I cannot find its equivalent in the Xcode/Swift world?
I do see the following that’s built into a boilerplate Game that you initially create using Xcode - but it seems that it just looks once, versus continuously:
func ObserveForGameControllers() {
NotificationCenter.default.addObserver(
self,
selector: #selector(connectControllers),
name: NSNotification.Name.GCControllerDidConnect,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(disconnectControllers),
name: NSNotification.Name.GCControllerDidDisconnect,
object: nil)
} // ObserveForGameControllers
@objc func connectControllers() {
// Unpause the Game if it is currently paused
self.isPaused = false
// Used to register the Nimbus Controllers to a specific Player Number
var indexNumber = 0
// Run through each controller currently connected to the system
for controller in GCController.controllers() {
// Check to see whether it is an extended Game Controller (Such as a Nimbus)
if controller.extendedGamepad != nil {
print("CONNECTED - Extended Gamepad #\(indexNumber)")
controller.playerIndex = GCControllerPlayerIndex.init(rawValue: indexNumber)!
indexNumber += 1
setupControllerControls(controller: controller)
}
else {
print("CONNECTED - but, NOT an Extended Gamepad #\(indexNumber)")
}
}
} // connectControllers
@objc func disconnectControllers() {
print("DIS-CONNECTED")
// Pause the Game if a controller is disconnected ~ This is mandated by Apple
self.isPaused = true
} // disconnectControllers
I try to call it in a Timer loop, but still does not work:
@objc func testForGamepadIsConnected() {
ObserveForGameControllers
var gamepadOn = !self.isPaused // does not work
} // testForGamepadIsConnected
func startTestForGamepad() {
guard (gamepadTimer != nil) else {
gamepadTimer = Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector:#selector(testForGamepadIsConnected),
userInfo: nil,
repeats: true)
return
}
} // startTestForGamepad
func stopTestForGamepad() {
guard (gamepadTimer == nil) else {
gamepadTimer!.invalidate()
// .invalidate() removes Timer() from gamepadTimer, so reinitialize it.
gamepadTimer = Timer()
return
}
} // stopTestForGamepad
Scoured the Google world, but I’ve come up empty.
Would appreciate some genius out there providing what I’m missing.
Thanks loads.