John Love
John Love

Reputation: 9

How do I vertically align several SKSpriteNodes and keep them aligned when they move?

How do I vertically align several SKSpriteNodes and keep them aligned when they move?

EDITED 1/4/2025

After initial startup of game:

enter image description here

After start of motion:

enter image description here

Note that after moving, the locomotive is OK because it is following the UIBezierPath as it should. Ditto for the 3 train cars.

The PROBLEM is the lack of vertical alignment of the locomotive with the trailing train cars. The cars are positioned okay, but the locomotive is elevated??

My strong hunch is that this PROBLEM rests with my train’s animated code which I will present further down.

My code to start movement along the UIBezierPath (myTrack) is as follows:

func startFollowTrainPath() {
                           
    var trainAction = SKAction.follow(trainPath.cgPath,
                                       asOffset: false,
                                       orientToPath: true,
                                       speed: thisSpeed)
    trainAction = SKAction.repeatForever(trainAction)
    myTrain.run(trainAction)
    myTrain.isPaused = false

}

Similar code exists for the cars, e.g., func startFollowCoalcarPath()

// called once by `SKScene’s setupScene(...)`
func getGameNodes() {

    // courtesy of @DonMag
    guard let img = UIImage(named: "train.png"),
          let imgTop = img.rotated(byDegrees: -90.0),
          let imgBottom = imgTop.horizontallyMirrored() else {
        fatalError("Could not load \"train.png\" image!")
    }
    let trainInit = SKTexture(image: img)
    trainTop = SKTexture(image: imgTop)
    trainBottom = SKTexture(image: imgBottom)
    myTrain = SKSpriteNode(texture: trainInit)
    myTrain.name = "train"
    myTrain.zPosition = trainZPosition
    myTrain.zRotation = trainZRotation
    
    guard let img = UIImage(named: "coalcar.png"),
              let imgTop = img.rotated(byDegrees: -90.0),
              let imgBottom = imgTop.horizontallyMirrored() else {
        fatalError("Could not load \"coalcar.png\" image!")
    }
    let coalcarInit = SKTexture(image: img)
    coalcarTop = SKTexture(image: imgTop)
    coalcarBottom = SKTexture(image: imgBottom)
    myCoalcar = SKSpriteNode(texture: coalcarInit)
    myCoalcar.name = "coalcar"
    myCoalcar.zPosition = coalcarZPosition
    myCoalcar.zRotation = coalcarZRotation   // = 0.0
    myCoalcar.anchorPoint = CGPointMake(0.5, 1.0)

    // etc. for freightcar + baggagecar

}

My code for initially vertically aligning all components =

    override func sceneDidLoad() {
        
        super.sceneDidLoad()
                        
        addGamePieces()   // calls addChild for all SKSpriteNodes

        sizeGameNodes()
        positionGameNodes()

    }

    func sizeGameNodes() {
                
        sizeTracks()
        sizeTrainNodes()
        
    }

    func sizeTracks() {
        
        itsGameViewController.sizeRoom()
        
        tracksWidth  = roomWidth - 2*trackOffset
        tracksHeight = roomHeight/2 - trackOffset
        myTracks.size = CGSizeMake(tracksWidth, tracksHeight)
        
    }

    func sizeTrainNodes() {
        
#if os(iOS)
        if (roomWidth > roomHeight) { trainScale = 1.5 }
        else                        { trainScale = 1.2 }
#elseif os(tvOS)
        trainScale = 2.0
#endif
        
        trainWidth  = 96.0*trainScale   // original size = 96 x 110
        trainHeight = 110.0*trainScale
        myTrain.size = CGSizeMake(trainWidth, trainHeight)

    }

    func positionGameNodes() {
        
        positionTracks()
        positionTrainNodes()
        
    } 

    func positionTracks() {
    
        tracksPosX = roomPosX   // center horizontally
        tracksPosY = roomPosY - tracksHeight/2

        if myTracks.position == .zero {
            myTracks.position = CGPointMake(tracksPosX, tracksPosY)
        }
        
        topOfTracks = 0.0   // = tracksPosY + tracksHeight/2

    }

    func positionTrainNodes() {
        
        let theRect = CGRect(x: tracksPosX - tracksWidth/2,
                             y: tracksPosY - tracksHeight/2,
                             width: tracksWidth,
                             height: tracksHeight)
        
        // line up train vertically with its cars
        trainPosY = topOfTracks + trainHeight/2
        trainPosX = tracksPosX + 130.0   // and a tad to the right of center
        
        // when Swift creates an Object, its .position is initialized = .zero
        if myTrain.position == .zero {
            myTrain.position = CGPointMake(trainPosX, trainPosY)
        }

        // etc. for 3 train cars

    }

Whoops ... here's my GameScene's update(...) method:

    override func update(_ currentTime: TimeInterval) {
        
        super.update(currentTime)
        
        let t = myTrain.position
        if t.y < trainPath.bounds.midY && trainIsOnTopOfOval
        {
            trainIsOnTopOfOval = false

            itsGameViewController.stopAnimatedTrain()
            itsGameViewController.initAnimatedTrainHere("bottom")
            itsGameViewController.startAnimatedTrain()
            
            // set orientation of each car = that of myTrain
            myCoalcar.texture = coalcarBottom
            myFreightcar.texture = freightcarBottom
            myBaggagecar.texture = baggagecarBottom
        }
        else if t.y > trainPath.bounds.midY && !trainIsOnTopOfOval
        {
            trainIsOnTopOfOval = true

            itsGameViewController.stopAnimatedTrain()
            itsGameViewController.initAnimatedTrainHere("top")
            itsGameViewController.startAnimatedTrain()
            
            // set orientation of each car = that of myTrain
            myCoalcar.texture = coalcarTop
            myFreightcar.texture = freightcarTop
            myBaggagecar.texture = baggagecarTop
        }

   }

Finally, here is the animated code snippets which I guessed above were the cause of the stated problem:

I start movement of the animated train when I start a new game:

    func newGame() {

        initAnimatedTrainHere("top")

    }

    func initAnimatedTrainHere(_ toWhere: String) {
        
#if os(iOS)
        let trainImgFolder = "images_iOS/Railroad/animatedTrainFolder/"
#elseif os(tvOS)
        let trainImgFolder = "images_tvOS/Railroad/animatedTrainFolder/"
#endif
        animatedTrainImgArray =
        [trainImgFolder + "animatedTrain_0.png",
         trainImgFolder + "animatedTrain_1.png",
         trainImgFolder + "animatedTrain_2.png",
         trainImgFolder + "animatedTrain_3.png",
         // 4 .. 16
         trainImgFolder + "animatedTrain_17.png",
         trainImgFolder + "animatedTrain_18.png",
         trainImgFolder + "animatedTrain_19.png"]
        
        let trainArray = createImgArrayWithSrc(animatedTrainImgArray,
                                               whereTo: toWhere)
        
        // This just initializes the SKAction ...
        // run with repeatForever below actually starts the animation.
        animatedTrainAction = SKAction.animate(with: trainArray,
                                               timePerFrame: 0.05)
        
    } 

When I want to start movement of the train + train cars, I call

func startTrain() {

    myTrain.run(SKAction.repeatForever(animatedTrainAction),
                withKey: animatedTrainKey)

}

Since this request for help has been fairly long, I repeat:

The initial positions of the train + cars move as they should when startTrain() is called .. but

The PROBLEM is the lack of vertical alignment of the locomotive with the trailing train cars both in their initial start-up position and when they start moving. The cars are positioned okay, but the locomotive is elevated??

Upvotes: 0

Views: 20

Answers (0)

Related Questions