Problem = I have a locomotive trying to follow an oval-shaped UIBezierPath. The challenge is that when I use orientToPath:true in my call to SKAction.follow, the locomotive immediately rotates even though the locomotive is initially positioned on the straight portion of the oval before it reaches the rounded portion of the oval.
If I could prevent this initial rotation, then the rotation would not happen until it reached the rounded portion of the oval - as it should.
So, can you please provide some guidance as to how I could prevent this initial rotation?
Here is the code:
func createTrainPath() {
// this custom method ensures that the locomotive
// moves CW, versus CCW
trackPath = getBezierPath(theNode: myTrain, offset: 0.0)
} // createTrainPath
func startFollowTrainPath() {
let theSpeed = Double(5*thisSpeed)
var trainAction = SKAction.follow(
trackPath.cgPath,
asOffset: true,
orientToPath: true, // <==
speed: theSpeed)
trainAction = SKAction.repeatForever(trainAction)
myTrain.run(trainAction, withKey: runTrainKey)
} // startFollowTrainPath
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
How do I change just the starting point of an existing UIBezierPath? .. and leave all other points the same?
I have definitely seen many posts on how to do this .. honestly, I just do not understand many of them
My existing path has one .move(to:) and many .addLine(to:)
My goal is:
(1) replace the .move(to:) with .addLine(to:) and then
(2) change one of the .addLine(to:) with .move(to:)
All other CGPoints stay the same.
In order to get the new starting point, I set:
savedPathPoint = myPath.currentPoint
when I stop the following motion.
At some point, I want to resume this following motion with the new starting point = savedPathPoint via
myPath.move(to: savedPathPoint)
let myAction = SKAction.follow(
myPath.cgPath,
asOffset: false,
orientToPath: true,
duration: 0.1)
mySprite.run(myAction)
When I look at the above problem description, it really appears simple.
Nevertheless, its implementation alludes me.
Thanks bunches in advance for any hints whatsoever.
Why does orient Path = true cause the sprite node to rotate CW when the path is horizontal?
Here's just one example:
let trainAction = SKAction.follow(
trainPath.cgPath,
asOffset: false,
orientToPath: true,
duration: 0.1)
myTrain.run(trainAction.reversed())
I have myTrain.zRotation = 0.0
myTrain is a simple locomotive,trainPath = the railroad tracks
Docs state with orientToPath: true, that the Node will always orient itself such that it's facing the UIBezierPath.
To me, facing means that the wheels of the locomotive are facing the BezierPath (= trainPath)
Apparently I am wrong in this interpretation ... if so, what is the correct interpretation?
FYI, comparing the snapshot of my initial placement of the locomotive on the tracks using .position with what happens after I start moving the locomotive:
These snapshots indicate that facing means that the nose of the locomotive is facing the BezierPath (= trainPath).
If true, how do I eliminate that CW rotation?
How do I change a UIBezierPath.currentPoint to a SKSpriteNode.position?
Here are the appropriate code snippets:
func createTrainPath() {
let startX = -tracksWidth/2,
startY = tracksPosY
savedTrainPosition = CGPoint(x: startX, y: startY!)
trackRect = CGRect(x: savedTrainPosition.x,
y: savedTrainPosition.y,
width: tracksWidth,
height: tracksHeight)
trainPath = UIBezierPath(ovalIn: trackRect)
trainPath = trainPath.reversing() // makes myTrain move CW
} // createTrainPath
Followed by:
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
So far, so good (I think?) ...
Within other places in my code, I call:
return trainPath.currentPoint
I need to convert trainPath.currentPoint to myTrain.position ...
When I insert the appropriate print statements, I see for example:
myTrain.position = (0.0, -295.05999755859375)
trainPath.currentPoint = (392.0, -385.0)
which obviously disqualifies a simple = , as in:
myTrain.position = trainPath.currentPoint
Since this = is not correct, what is ?
After more investigation, my guess is that .currentPoint is in SKSpriteNode coordinates and .position is in SKScene coordinates.
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?
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
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?
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 ...
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)
}
}
Topic:
Community
SubTopic:
Apple Developers
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?
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.
Topic:
Graphics & Games
SubTopic:
SpriteKit
Apple TV
Samsung TV
SONOS Soundbar + S1 stereo speakers
Apple TV Preferences
Sound output = SONOS soundbar
Samsung Preferences
Sound Output = SONOS HDMI eArc soundbar
start with Apple TV movies and as I switch from movie to movie, sound comes out stereo as it should
however when I switch to Netflix or YouTube TV with stereo content, sound comes out mono .. until I manually change the ATV Preferences to stereo.
As ong as I stay with Netflix content, stereo output. But change to YouRube TV or ATV, it automatically changes to mono.
?????
Topic:
Community
SubTopic:
Apple Developers
Moving a SKSpriteNode image in response to a change in its position is not working.
Based on the code below, does anyone have an idea why?
N.B. I change the position of the image within my class GameScene.
Then, I call, e.g., this function:
func drawBall() {
let positionInSceneCoordinates = CGPoint(x: ballPosX, y: ballPosY)
myBall!.removeAction(forKey: "moveTO")
let moveTO = SKAction.move(to: positionInSceneCoordinates, duration: TimeInterval(1))
myBall!.run(moveTO, withKey: "moveTO)")
}
Selective placement of print statements prove that the ballPosX, ballPosY are changing as they should, but I am observing zero movement on the Xcode Simulator via the call to myBall!run(..).
I am at a loss here on how to proceed.
Thanks bunches!
GameScene’s didBegin is not called?
I have read a high volume of solutions here and many, many other Sites and I cannot figure out why?
What am I doing wrong?
Here are my relevant 2 Code Snippets:
Within GameViewController, this function is called every time I show the CGScene:
var noCollision: UInt32 = 00000000,
noContact: UInt32 = 00000000
func addGamePiecesToScene(toScene: SKScene) {
// thisSceneName is set before we're called
if thisSceneName == "GameScene" {
/*
BACKGROUND
*/
// GameScene.sks file loads this for us, but
// we still need myRoom to set the data below:
myRoom = SKSpriteNode(imageNamed: roomImg)
myRoom.name = "room"
myRoom.zPosition = 0
myRoom.size = CGSize(width: roomWidth, height: roomHeight)
myRoom.physicsBody = SKPhysicsBody(rectangleOf: myRoom.size)
myRoom.physicsBody?.categoryBitMask = roomCategory
myRoom.physicsBody?.collisionBitMask = noCollision
myRoom.physicsBody?.contactTestBitMask = noContact
/*
MAIN Game Pieces
*/
myPaddle = SKSpriteNode(imageNamed: paddleImg)
myPaddle.name = "paddle"
myPaddle.zPosition = 3
myPaddle.size = CGSize(width: paddleWidth, height: paddleHeight)
myPaddle.position = CGPoint(x: paddlePosX, y: paddlePosY) // = original or moved
myPaddle.physicsBody = SKPhysicsBody(rectangleOf: myPaddle.size)
myPaddle.physicsBody?.categoryBitMask = paddleCategory
myPaddle.physicsBody?.collisionBitMask = roomCategory | ballCategory
myPaddle.physicsBody?.contactTestBitMask = roomCategory | ballCategory
myPaddle.physicsBody!.affectedByGravity = false
myPaddle.physicsBody!.isDynamic = true
toScene.addChild(myPaddle)
//
myBall = SKSpriteNode(imageNamed: ballImg)
myBall.name = "ball"
myBall.zPosition = 4
myBall.size = CGSize(width: ballWidth, height: ballHeight)
myBall.position = CGPoint(x: ballPosX, y: ballPosY) // = original or moved
myBall.physicsBody = SKPhysicsBody(rectangleOf: myBall.size)
myBall.physicsBody?.categoryBitMask = ballCategory
myBall.physicsBody?.collisionBitMask = roomCategory
myBall.physicsBody?.contactTestBitMask = roomCategory
myBall.physicsBody!.affectedByGravity = false
myBall.physicsBody!.isDynamic = true
myBall.physicsBody!.usesPreciseCollisionDetection = true
toScene.addChild(myBall)
/*
EXTRA Game Pieces = bird, rock, trees + tower are loaded by the .sks File
*/
} // if thisSceneName == "GameScene"
} // addGamePiecesToScene
Within my GameScene is the cited didBegin function:
func didBegin(_ contact: SKPhysicsContact) {
print("didBegin") // never shows in Console
// Respond to an object hitting other objects based on their names
let bodyAName = contact.bodyA.node?.name
let bodyBName = contact.bodyB.node?.name
// N.B. – according to Apple docs,
// there is no guarantee which object is bodyA and which is bodyB
let paddleHitWall = ((bodyBName == myPaddle.name) && (bodyAName == myRoom.name)) ||
((bodyAName == myPaddle.name) && (bodyBName == myRoom.name))
let ballHitWall = ((bodyBName == myBall.name) && (bodyAName == myRoom.name)) ||
((bodyAName == myBall.name) && (bodyBName == myRoom.name))
let paddleHitBall = ((bodyBName == myPaddle.name) && (bodyAName == myBall.name)) ||
((bodyAName == myPaddle.name) && (bodyBName == myBall.name))
if paddleHitWall {
ouch()
movePaddle(dx: -dPaddleX, dy: -dPaddleY)
print("paddle hit Wall")
}
else if ballHitWall {
moveBall(dx: -dBallX, dy: -dBallY)
print("ball hit Wall")
}
else if paddleHitBall {
attaBoy()
moveBall(dx: -dBallX, dy: -dBallY)
print("paddle hit ball")
}
} // didBegin
What in the above 2 Code Snippets is wrong and, when corrected, didBegin is once again called?
App Review Process
(1) first attempt: rejected for a reason not supported by App Connect. For example, “You did not state that your App requires a Game Controller”. 100% refuted by App Connect’s description and promotional texts for all Apps in the App Store.
(2) second attempt = I get accused of a personal attack. Crocodile tears when in fact I am simply building a stream of logic that refutes their non-factual assertions.
This futile attempt to dissuade others is very often used when they can no longer refute the logical responses.
This attempt is quite often used by politicians.
(3) their last resort is to simply ignore my submission and forever keep my status = In review. Life in prison without parole.
(4) good question = do I put my legal team into gallop mode?
Why?
Because I and many others have reported very similar issues.
Someone needs to say “No more!”
(5) These issues all reduce to one = a deity complex that many Reviewers have. Synonymously they are saying “We have power and we will crush you”.
For this reason their rejection reasons become a moving target.
Most of the time this ploy works, but not now.
(6) I am a retired United States Air Force officer and we all saw this deity complex in full display in the late 1930’s …
Auschwitz occurred and World War II resulted.
I saw it up close and personal in Vietnam. I personally saw Quonset huts full of dismembered U.S. heroes.
And we see it full grown now here and overseas .. leaving the probability of World War III = high.
The clique of long ago App Developers will most likely result in slanderous statements against me.
But sometimes an adult must stand and say “ No more!”