I have an oval UIBezierPath with a moving SKSpriteNode,
I stop its motion and record the stopped position. I then restart this motion and want it to restart where it initially stopped.
Works great if motion is not stopped. Movement is great around entire oval Path.
Also works great as long as this stop-restart sequence occurs along the top half of the oval UIBezierPath. However, I have problems along the bottom half of this Path -- it stops okay, but the restart position is not where it previously stopped.
My method to create this oval UIBezierePath is as follows:
func createTrainPath() {
trainRect = CGRect(x: tracksPosX - tracksWidth/2,
y: tracksPosY - tracksHeight/2,
width: tracksWidth,
height: tracksHeight)
// these methods come from @DonMag
trainPoints = generatePoints(inRect: trainRect,
withNumberOfPoints: nbrPathPoints)
trainPath = generatePathFromPoints(trainPoints!,
startingAtIDX: savedTrainIndex)
} // createTrainPath
My method to stop this motion is as follows:
func stopFollowTrainPath() {
guard (myTrain != nil) else { return }
myTrain.isPaused = true
savedTrainPosition = myTrain.position
// also from @DonMag
savedTrainIndex = closestIndexInPath(
trainPath,
toPoint: savedTrainPosition) ?? 0
} // stopFollowTrainPath
Finally, I call this to re-start this motion:
func startFollowTrainPath() {
var trainAction = SKAction.follow(trainPath.cgPath,
asOffset: false,
orientToPath: true,
speed: thisSpeed)
trainAction = SKAction.repeatForever(trainAction)
myTrain.run(trainAction, withKey: runTrainKey)
myTrain.isPaused = false
} // startFollowTrainPath
Again, great if motion is not stopped. Movement is great around entire oval Path.
Again, no problem for stopping and then restarting along top half of oval .. the ohoh occurs along bottom half.
Is there something I need to do within GameScene's update method that I am missing? For example, do I need to reconstruct my UIBezierPath? every time my node moves between the top half and the bottom half and therein account for the fact that the node is traveling in the opposite direction from the top half?
Post
Replies
Boosts
Views
Activity
Using Swift, how do I resize individual SKSpriteNodes for various iPad device sizes?
Currently, I use theScene.scaleMode = .resizeFill for scaling the entire SKScene and it works as advertised. Please note that .aspectFill does not solve the challenge described below.
My challenge is to resize individual SKSpriteNodes (that are components of the overall SKScene) based on size of the iOS device; for example, iPad Mini <--> iPad Pro
Right now, I hard code the sizes of these Nodes. But I realize this is not the optimum approach.
Again, hard coding is frowned upon. So I am looking for a more dynamic method that functions based on device size.
How do I set the static position of a SKSpriteNode so that it tilts toward the UIBezierPath as if it were following this Path?
When I first start my App, these Nodes are all aligned in a straight line
When I call:
var trainAction = SKAction.follow(trainPath.cgPath,
asOffset: false,
orientToPath: true,
speed: thisSpeed)
for moving the train, the train + each car will orient its tilt to hug the trainPath.
But I want the identical tilt to hug the trainPath for its initial static presentation.
How do I do that?
How do I correctly show a PDF document?
iPad and Xcode 15.4
Within my GameViewController, I have:
func presentScene(_ theScene: SKScene) {
theScene.scaleMode = .resizeFill
if let skView = self.view as? SKView {
skView.ignoresSiblingOrder = true
skView.showsFPS = true
skView.showsNodeCount = true
#if os(iOS)
let theTransition = SKTransition.doorway(withDuration: 2.0)
skView.presentScene(theScene, transition: theTransition)
#elseif os(tvOS)
skView.presentScene(theScene)
#endif
}
} // presentScene
I believe presentScene(theScene) goes to the sceneDidLoad() func of theScene which adds various SKSpriteNodes to the scene via a call to addChild(theNode).
So far so good ...
Until I have a SKScene wherein I wish to display a PDF.
I use this snippet to display this PDF with this call within the SKScene's sceneDisLoad():
displayPDF("ABOUT_OLD_WEST_LOCOMOTIVE")
func displayPDF(_ itsName:String) {
let pdfView = PDFView()
guard let path = Bundle.main.path(forResource: itsName, ofType: "pdf")
else { return }
print("path")
guard let pdfDocument = PDFDocument(url: URL(fileURLWithPath: path))
else { return }
print("pdfDocument")
pdfView.displayMode = .singlePageContinuous
pdfView.autoScales = true
pdfView.displayDirection = .vertical
pdfView.document = pdfDocument
} // displayPDF
The 2 print statements show, yet the SKScene does not display the PDF via pdfView.document = pdfDocument?
Anyone have a clue what errors I have committed?
Appreciate it.
Can you set isPaused = true of a SKSpriteNode and keep its SKEmitterNode still moving?
Why is Add Emitter to Node not happening immediately?
Within an extension to GameViewController I have:
func addEmitterToNode(_ particleName: String,
_ theNode:SKSpriteNode) {
if let emitter = SKEmitterNode(fileNamed: particleName) {
emitter.name = "emitter"
emitter.position = CGPoint(x: 0, y: 0) // = at center
theNode.addChild(emitter)
}
} // addEmitterToNode
Here is the func (extension to GameViewController) wherein I call the above. The problem is the explosion sound plays but the smoke particle emitter is not immediately added.
func increaseSpeed() {
if thisSpeed > maxSpeed {
pauseGame() // sets myTrain.isPaused = true
playSound(theSoundName: "explosion")
addEmitterToNode("smokeParticle.sks", myTrain)
trainHasCrashed = true
}
} // increaseSpeed
What I do not understand is that the following within GameScene's sceneDidLoad, the smoke particle emitter is added =
override func sceneDidLoad() {
if itsGameViewController.trainHasCrashed {
itsGameViewController.addEmitterToNode("smokeParticle.sks",
myTrain)
}
}
How to ensure current SKScene has fully loaded before engaging it with the GamePad Controller?
The problem is this = when stopping sound is involved when I do switch SKScenes, if I press the buttons of the GamePad Controller (which cycle thru these other SKScenes) too fast, the movement of the Game Pieces fails to resume when I return to the Game Scene after the above cycling.
This problem occurs only with the os(tvOS) version, but not with the iPad version. And the reason for this distinction is that each SKScene for the iPad has to fully load due to the fact that the button I press to switch SKScenes is at the top-left corner of the iPad -- so, by definition, by the time I have access to this button, the current SKScene has fully loaded.
By definition, there is no such button for os(tvOS).
Given this button’s absence, I believe I need the Swift version of jQuery’s
$(document).ready (function() {.
Any help will be appreciated to the rafters ...
How to ensure current SKScene has fully loaded before engaging it with the GamePad Controller?
MAJOR REWRITE FOR THE SAKE OF HOPEFULLY (?) INCREASED CLARITY
The problem is this = when stopping sound is involved when I do switch SKScenes, if I press the buttons of the GamePad Controller (which cycle thru these other SKScenes) too fast, the movement of the Game Pieces fails to resume when I return to the Game Scene after the above cycling.
This problem occurs only with the os(tvOS) version, but not with the iPad version. And the reason for this distinction is that each SKScene for the iPad has to fully load due to the fact that the button I press to switch SKScenes is at the top-left corner of the iPad -- so, by definition, by the time I have access to this button, the current SKScene has fully loaded.
By definition, there is no such button for os(iOS).
Given this button’s absence, I need the Swift version of jQuery’s
$(document).ready (function() {.
Any help will be appreciated to the rafters ...
Calling SKAction.follow(..) causes my SKSpriteNode to rotate 90 degrees CW and not stay horizontal as it follows my UIBezierPath?
I have this code (within my GameViewController Class) which implements the following of a SKSpriteNode along a UIBezierPath.
=====
Please note that a brilliant contributor solved the above challenge by creating a new Class, e.g., class NewClass: NSObject. Nevertheless, I need the solution to appear in an extension of my GameViewController
=====
func createTrainPath() {
trackRect = CGRect(x: tracksPosX - tracksWidth/2,
y: tracksPosY,
width: tracksWidth,
height: tracksHeight)
trainPath = UIBezierPath(ovalIn: trackRect)
} // createTrainPath
func startFollowTrainPath() {
var trainAction = SKAction.follow(
trainPath.cgPath,
asOffset: false,
orientToPath: true,
speed: theSpeed)
trainAction = SKAction.repeatForever(trainAction)
myTrain.run(trainAction, withKey: runTrainKey)
} // startFollowTrainPath
func stopFollowTrainPath() {
guard myTrain == nil else {
myTrain.removeAction(forKey: runTrainKey)
savedTrainPosition = getPositionFor(myTrain, orPath: trainPath)
return
}
} // stopFollowTrainPath
Here is a link to a super long conversation with a true genius at StackOverflow
https://stackoverflow.com/questions/78343630/what-algorithm-is-available-to-correlate-a-skspritenodes-position-with-the-uibe/78379892#78379892
He has done an extraordinary amount of work to make up for what I consider is the non-working of UIBezierPath’s orientToPath = true.
He’s done multiple hours work to make up for orientToPath = true not working.
What are we missing?
I know that this is not a coding question and I have posted on the regular Apple Forum and have been working with the 800 Tech Support folk for 4 months.
I am in a world of hurt.
Have ATV + Samsung TV + external soundbar.
All software versions are current
NO Cable box (direct input). 4 months ago I had Cable box = zero problems.
With Samsung on and ATV off, stereo = Dolby 5.1 sounds great.
With ATV also on, no stereo.
I can use Siri remote to activate stereo until I change to a new movie. ATV then reverts to mono = PCM
One Tech Support lady said to just not watch multiple movie.
?$@!*+^?
I have spoken with engineers at Samsung and Sonos (soundbar). Both say my cabling is correct = 1 cable from soundbar to HDMI Arc on TV + 1 cable from ATV to another HDMI port on TV.
Another Tech Support folk said to connect ATV to soundbar. Soundbars don’t have gobs of HDMI ports, the TV does.
?!$@*^+%#
I have spoken with Apple Tech Support with zero help for 3-4 months.
I think I have been a good soldier. But it is time for real results.
Please help
Detecting touching a SKSpriteNode within a touchesBegan event?
My experience to date has focused on using GamepadControllers with Apps, not a touch-activated iOS App.
Here are some short code snippets:
Note: the error I am trying to correct is noted in the very first snippet = touchesBegan within the comment <== shows "horse"
Yes, there is a "horse", but it is no where near the "creditsInfo" SKSpriteNode within my .sksfile.
Please note that this "creditsInfo" SKSpriteNode is programmatically generated by my addCreditsButton(..) and will be placed very near the top-left of my GameScene.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let ourScene = GameScene(fileNamed: "GameScene") {
if let touch:UITouch = touches.first {
let location = touch.location(in: view)
let node:SKNode = ourScene.atPoint(location)
print("node.name = \(node.name!)") // <== shows "horse"
if (node.name == "creditsInfo") {
showCredits()
}
}
} // if let ourScene
} // touchesBegan
The above touchesBegan function is an extension GameViewController which according to the docs is okay, namely, touchesBegan is a UIView method besides being a UIViewController method.
Within my primary showScene() function, I have:
if let ourScene = GameScene(fileNamed: "GameScene") {
#if os(iOS)
addCreditsButton(toScene: ourScene)
#endif
}
with:
func addCreditsButton(toScene: SKScene) {
if thisSceneName == "GameScene" {
itsCreditsNode.name = "creditsInfo"
itsCreditsNode.anchorPoint = CGPoint(x: 0.5, y: 0.5)
itsCreditsNode.size = CGSize(width: 2*creditsCircleRadius,
height: 2*creditsCircleRadius)
itsCreditsNode.zPosition = 3
creditsCirclePosY = roomHeight/2 - creditsCircleRadius - creditsCircleOffsetY
creditsCirclePosX = -roomWidth/2 + creditsCircleRadius + creditsCircleOffsetX
itsCreditsNode.position = CGPoint(x: creditsCirclePosX,
y: creditsCirclePosY)
toScene.addChild(itsCreditsNode)
} // if thisSceneName
} // addCreditsButton
To finish, I repeat what I stated at the very top:
The error I am trying to correct is noted in the very first snippet = touchesBegan within the comment <== shows "horse"
How to detect the location of a mouseDown event using Swift?
With the following code snippet, I get the error "Cannot find type 'NSEvent' in scope"?
import SpriteKit
func mouseDown(with event: NSEvent) {
if let ourScene = GameScene(fileNamed: "GameScene") {
let location = event.location(in: view)
let node:SKNode = ourScene.atPoint(location)
if (node.name == "creditsInfo") {
showCredits()
}
} // if let ourScene
} // mouseDown
Anybody have a floodlight to shine on this very basic error?
I submitted a TSI about 3 weeks ago.
Got an automated reply giving me my TSI ID. Unfortunately I cannot find that ID anywhere.
Can any of you detectives find that ID for me?
I guess I am a little curious as to when one of their super smart DTS programmers will contact me.
My Bad
John Love
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?