2

I am trying to create a background fora game and have two background images. I can use two different SKSpriteNode(), SKTexture, SKAction.sequence..etc and some math to stitch them together but it comes out kind of choppy and also I cant seem to get the math just right. They are supposed to be able to be combined to make a long changing background but it always offsets.

Can someone tell me how to combine both textures into one so I dont have to break my head with the math. Or if someone spots some errors in my math can you please point them out? both background have the same width of 1200 pixels

override func didMoveToView(view: SKView) {
    /* Setup your scene here */

    var bgTexture = SKTexture(imageNamed: "bg_2_midground_1.png")
    var bgTexture2 = SKTexture(imageNamed: "bg_2_midground_2.png")

    var totalBG = bgTexture.size().width + bgTexture2.size().width

    var moveBG1 = SKAction.moveByX(-bgTexture.size().width, y: 0, duration: 12)
    //shift bg back to be able to repeat itself
    var replaceBG1 = SKAction.moveByX(bgTexture.size().width, y: 0, duration: 0.0) //move immediately back to replace self
    var moveBG1Forever = SKAction.repeatActionForever(SKAction.sequence([moveBG1,replaceBG1]))

    var moveBG2 = SKAction.moveByX(-bgTexture2.size().width, y: 0, duration: 12)
    //shift bg back to be able to repeat itself
    var replaceBG2 = SKAction.moveByX(bgTexture2.size().width, y: 0, duration: 0.0) //move immediately back to replace self
    var moveBG2Forever = SKAction.repeatActionForever(SKAction.sequence([moveBG2,replaceBG2]))

    for var i:CGFloat = 0; i<2; i++ {
        var bg = SKSpriteNode(texture: bgTexture)
        var bg2 = SKSpriteNode(texture: bgTexture2)

        bg.position = CGPointMake(bgTexture.size().width/2.0 + bgTexture.size().width * i + bgTexture2.size().width * i, CGRectGetMidY(self.frame))
        bg2.position = CGPointMake(bgTexture.size().width/2.0 + bgTexture2.size().width + bgTexture2.size().width * 2 * i, CGRectGetMidY(self.frame))

        bg.runAction(moveBG1Forever)
        bg2.runAction(moveBG2Forever)

        self.addChild(bg)
        self.addChild(bg2)

        println(bg.position)
        println(bgTexture.size().width)

        println(bg2.position)
        println(bgTexture2.size().width)
    }

}

CodeSmile
  • 64,284
  • 20
  • 132
  • 217
Jeremy Sh
  • 609
  • 7
  • 24
  • so what is it you need? You want the second background to come in after the first starts moving? and loop like that forever? – rakeshbs Jan 31 '15 at 03:52
  • exactly... OR combine both textures into one somehow so I can just loop one forever and itll look right. Right now some edges are out of place because my math is clearly wrong – Jeremy Sh Jan 31 '15 at 03:53

2 Answers2

2

Instead of using SKActions, you can use the update function to move all background nodes together, moving each node to the end as soon as each node goes out of the screen.

To make things more manageable, we can create a custom Background SKNode.

class BackgroundNode : SKNode
{
    override init() {
        super.init()

        var bgTexture1 = SKTexture(imageNamed: "b1.jpg")
        var bgTexture2 = SKTexture(imageNamed: "b2.png")


        let totalBG = bgTexture1.size().width + bgTexture2.size().width

        for index in 0..<2
        {
            var bg1 = SKSpriteNode(texture: bgTexture1)
            var bg2 = SKSpriteNode(texture: bgTexture2)
            bg1.anchorPoint = CGPointMake(0, 0)
            bg2.anchorPoint = CGPointMake(0, 0)

            let i = CGFloat(index)

            bg1.position = CGPointMake(i * bgTexture1.size().width + i * bgTexture2.size().width, 0)
            bg2.position = CGPointMake((i+1) * bgTexture1.size().width + i * bgTexture2.size().width, 0)

            self.addChild(bg1)
            self.addChild(bg2)
            lastNode = bg2
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    var speedOfBackground : CGFloat = 300.0
    var previousTime : CFTimeInterval = -1
    var lastNode : SKSpriteNode!

    func update(currentTime: CFTimeInterval) {

        if previousTime != -1
        {
            var outOfBoundsSprite : SKSpriteNode? = nil
            let deltaX = speedOfBackground * CGFloat(currentTime - previousTime)
            for sknode in self.children
            {
                if let sprite = sknode as? SKSpriteNode
                {
                    sprite.position = CGPointMake(sprite.position.x - deltaX, sprite.position.y)
                    if (sprite.position.x < -sprite.size.width)
                    {
                        outOfBoundsSprite = sprite
                    }
                }
            }
            if (outOfBoundsSprite != nil)
            {
                outOfBoundsSprite?.position.x = lastNode.position.x + lastNode.size.width
                lastNode = outOfBoundsSprite!
            }
        }
        previousTime = currentTime
    }

}

You can vary the speedOfBackground property to vary the speed of movement.

It can be used in the SKScene like this

class GameScene: SKScene ,SKPhysicsContactDelegate {

    var background : BackgroundNode!

    override func didMoveToView(view: SKView) {

        background = BackgroundNode()
        background.speedOfBackground = 500.0
        self.addChild(background)
    }


    override func update(currentTime: CFTimeInterval) {
        background.update(currentTime)
    }
}
rakeshbs
  • 24,392
  • 7
  • 73
  • 63
  • This is a good solution @rakeshbs except that if (sprite.position.x < -sprite.size.width) { sprite.position.x = lastNode.position.x + lastNode.size.width lastNode = sprite } will shift the position by too much since by the time sprite.position.x < - sprite.size().width there is an offset recorded in the value. The higher the speed the higher the offset and I am right where I started off – Jeremy Sh Jan 31 '15 at 14:53
  • How large are the ur background sprites? Did u try the above code? I am adding the node that goes out of screen after the last node and making it the last node everytime – rakeshbs Jan 31 '15 at 15:21
  • yes I used the exact code, but there is always a gap between the first sprites and the newly added ones. The gap increases/decreases when you increase/decrease the speed. I think it is due to lastNode.position.x not being an exact multiple of 1200 when it is passed into sprite.position.x = lastNode.position.x + lastNode.size.width – Jeremy Sh Jan 31 '15 at 16:15
0

@rakeshbs - Your answer was perfect, I did decide to expand it a bit. This will allow anyone to throw a Atlas in their project and be able to set up a scrolling background of any number of images. HOWEVER, watch out for loading to many assets at once, if you do that you need to remove them from the game or bad things might happen. =P

import SpriteKit

class BackgroundNode: SKNode {
    override init() {
        super.init()

        // create containers for all the Textures and Nodes
        var backgroundTextures = [SKTexture]()
        var backgroundSpriteNodes = [SKSpriteNode]()

        // load the TextureAtlas for the Background
        let backgroundAtlas : SKTextureAtlas = SKTextureAtlas(named: "BackgroundImages")
        let imagesCount:Int = backgroundAtlas.textureNames.count

        // fetch all of the image resources and store them as both textures and spriteNodes
        for var i = 1; i <= imagesCount; i++ {
            let imageTextureName = "background_\(i)"
            let imageTexture = backgroundAtlas.textureNamed(imageTextureName)

            let spriteNode = SKSpriteNode(texture: imageTexture)
            spriteNode.anchorPoint = CGPointMake(0, 0)

            backgroundTextures.append(imageTexture)
            backgroundSpriteNodes.append(spriteNode)
        }

        // for each image figure out what the x value of the location should be
        for var i = 0; i < imagesCount; i++ {
            var addtionalWidth:CGFloat = 0

            // add the x value of all the images before this image to come up with the latest x
            for var j = 0; j < i; j++ {
                addtionalWidth += backgroundTextures[j].size().width
            }

            // assign each SKNode a position
            backgroundSpriteNodes[i].position = CGPointMake(0 + addtionalWidth, 0)

            // add the Node to the scene
            self.addChild(backgroundSpriteNodes[i])
        }

        // keep track of where you are to reset scrolls images
        lastNode = backgroundSpriteNodes[imagesCount - 1]
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    var speedOfBackground : CGFloat = 300.0
    var previousTime : CFTimeInterval = -1
    var lastNode : SKSpriteNode!

    func update(currentTime: CFTimeInterval) {
        if previousTime != -1 {
            var outOfBoundsSprite : SKSpriteNode? = nil
            let deltaX = speedOfBackground * CGFloat(currentTime - previousTime)

            for sknode in self.children {
                if let sprite = sknode as? SKSpriteNode {
                    sprite.position = CGPointMake(sprite.position.x - deltaX, sprite.position.y)

                    if (sprite.position.x < -sprite.size.width) {
                        outOfBoundsSprite = sprite
                    }
                }
            }

            if (outOfBoundsSprite != nil) {
                outOfBoundsSprite?.position.x = lastNode.position.x + lastNode.size.width
                lastNode = outOfBoundsSprite!
            }
        }

        previousTime = currentTime
    }

}