Scrolling background images twitches on first loop - SpriteKit Swift Xcode

时间:2017-08-04 13:03:01

标签: swift xcode sprite-kit

The post is now updated with my full code.

I made a small test project where I’m scrolling eight 2048x1536 background images. Each image is approx. 150kb in size. The project can be found here. The project is now updated and I've stripped down the code to minimal and added more code comments. The project files are now the same as the code here below.

The problem is that the scrolling of the background images twitches the first time the images are looping. After all pictures has looped once, the scrolling is smooth and stays smooth, even at a very high scrolling speed.

I’ve preloaded the images with this code here below, that I call from GameViewController.swift. The reason I put the eight images in eight different texture atlases is that if I put all images in one atlas Xcode gives this error when compiling: “Generate SpriteKit Texture Atlas Error Group”: “/TextureAtlas: cannot fit input texture into a maximum supported dimension of 2048 x 2048.”

Here is a picture from the project:

enter image description here

Here is my code:

GameViewController.swift:

import UIKit
import SpriteKit
import GameplayKit

class GameViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    // Preload images from SceneManager.swift
    SceneManager.sharedInstance.preloadAssets()
    self.startScene()

  }

  func startScene() {

    if let view = self.view as! SKView? {
      // Load the SKScene from 'GameScene.sks'
      let scene = GameScene(size:CGSize(width: 2048, height: 1536))
      // Set the scale mode to scale to fit the window
      scene.scaleMode = .aspectFill

      // Present the scene
      view.presentScene(scene)

      view.ignoresSiblingOrder = true
      view.showsFPS = true
      view.showsNodeCount = true
    }

  }

  override var shouldAutorotate: Bool {
    return true
  }

  override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    if UIDevice.current.userInterfaceIdiom == .phone {
      return .allButUpsideDown
    } else {
      return .all
    }
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Release any cached data, images, etc that aren't in use.
  }

  override var prefersStatusBarHidden: Bool {
    return true
  }
}

SceneManager.swift:

import Foundation
import SpriteKit

class SceneManager {

  static let sharedInstance = SceneManager()

  // Preload from atlas. All images cannot be used into one atlas.
  // If all images are put in one atlas, when building Xcode gives error:
  // “Generate SpriteKit Texture Atlas Error Group”: “/TextureAtlas: cannot fit input texture into a maximum supported dimension of 2048 x 2048.”
  var textureAtlas = [SKTextureAtlas]()

  func preloadAssets() {

    textureAtlas.append(SKTextureAtlas(named: "BGScrollImagesA"))
    textureAtlas.append(SKTextureAtlas(named: "BGScrollImagesB"))
    textureAtlas.append(SKTextureAtlas(named: "BGScrollImagesC"))
    textureAtlas.append(SKTextureAtlas(named: "BGScrollImagesD"))
    textureAtlas.append(SKTextureAtlas(named: "BGScrollImagesE"))
    textureAtlas.append(SKTextureAtlas(named: "BGScrollImagesF"))
    textureAtlas.append(SKTextureAtlas(named: "BGScrollImagesG"))
    textureAtlas.append(SKTextureAtlas(named: "BGScrollImagesH"))

    SKTextureAtlas.preloadTextureAtlases(textureAtlas, withCompletionHandler: { () -> Void in
      print("PRELOAD COMPLETED")
    })
  }
}

GameScene.swift:

//////////////////////////////////////////////////////////////////////
//                                                                  //
// Run this project on a physical device. Change the bundle         //
// identifier, if needed.                                           //
//                                                                  //
// The background images are preloaded from GameViewController      //
// and SceneManager.                                                //
//                                                                  //
// Tap screen to start the scrolling, tap again and the speed       //
// increases for every tap.                                         //
//                                                                  //
// This project makes the background images scroll but there        //
// is a twitch in the scrolling when the bg images are 'loaded'     //
// for the first time. When all the images has looped once, the     //
// scrolling is smooth and stays smooth, even at a very high        //
// scrolling speed (tap to increase speed).                         //
//                                                                  //
// How do I preload and scroll MANY large images so the twitch      //
// can be avoided?                                                  //
//                                                                  //
//////////////////////////////////////////////////////////////////////

import SpriteKit
import GameplayKit

class GameScene: SKScene {


  // When the scrolling bg image reaches this x-coordinat, it will be moved to the right side outside the screen
  let BG_X_RESET: CGFloat = -1030.0

  // The spritenodes (bg images) are stored in this array, this is used when we scroll the images
  var bgImagesArray = [SKSpriteNode]()

  // This is the scrolling speed of the bg images.
  var backgroundSpeed:CGFloat = -15.0

  // We use this in 'touchesBegan' to start scrolling the bg images
  var scrollAction: SKAction!

  override init(size: CGSize) {
    super.init(size: size)
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func didMove(to view: SKView) {

    // Change 'numberOfBgPieces' to the number of bg image we want to scroll.
    setupBackgroundPieces(numberOfBgPieces: 8, bgArray: &bgImagesArray)
  }

  // Make sprites of the bg images and position them correctly
  func setupBackgroundPieces(numberOfBgPieces: Int, bgArray: inout [SKSpriteNode]){
    for x in 1...numberOfBgPieces {
      let bgImageName = "bgImage\(x)"
      let bg = SKSpriteNode(imageNamed: bgImageName)
      bg.position = CGPoint(x: self.frame.minX + (CGFloat(x-1) * bg.size.width), y: self.size.height / 2)
      bg.zPosition = 10
      bgArray.append(bg)
      self.addChild(bg)
    }
  }

  // This function is called from the update loop
  // This moves the bg images to the right side outside the
  // screen so that they will scroll again. The bg image is moved when it reaches 'spriteResetXPos' which is x -1030.0
  func bgMovementPosition(piecesArray: [SKSpriteNode], spriteResetXPos: CGFloat){

    for x in (0..<piecesArray.count){

      if piecesArray[x].position.x <= spriteResetXPos {

        var index: Int!

        if x == 0 {
          index = piecesArray.count - 1
        } else {
          index = x - 1
        }

        let newPos = CGPoint(x: piecesArray[index].position.x + piecesArray[x].size.width, y: piecesArray[x].position.y)

        piecesArray[x].position = newPos
      }
    }
  }


  func touchDown(atPoint pos : CGPoint) {

  }

  func touchMoved(toPoint pos : CGPoint) {

  }

  func touchUp(atPoint pos : CGPoint) {

  }

  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches { self.touchDown(atPoint: t.location(in: self)) }
    // Start scrolling the background images when we tap screen.
    // Each time we tap screen, the scroll speed increases (I don't know if this
    // is the best way to increase the scroll speed but that is not relevant in this case
    // since the problem occurs after the first screen tap = start scrolling).
    scrollAction = SKAction.repeatForever(SKAction.moveBy(x: backgroundSpeed, y: 0, duration: 0.02))
    for x in bgImagesArray {
      x.run(scrollAction)
    }
  }

  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches { self.touchMoved(toPoint: t.location(in: self)) }
  }

  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches { self.touchUp(atPoint: t.location(in: self)) }
  }

  override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
    for t in touches { self.touchUp(atPoint: t.location(in: self)) }
  }

  override func update(_ currentTime: TimeInterval) {
    // Called before each frame is rendered
    bgMovementPosition(piecesArray: bgImagesArray, spriteResetXPos: BG_X_RESET)
  }
}

I also tried to remove all the eight atlases and put all the images in the Assets.xcassets folder and then preload the textures with these two functions from GameSceneController.swift:

func preloadAssets() {

    for x in 1…8 {
      let texture = SKTexture(imageNamed: “bgImage\(x)”)
      texture.preload {
        print("Texture preloaded")
      }
    }
}

and also this code, with .preload(completionHandler:

func preloadAssets() {

    for x in 1…8 {
      let texture = SKTexture(imageNamed: “bgImage\(x)”)
      texture.preload(completionHandler: { 
          print("Texture preloaded")
        })
    }
 }

The project I want to create is a real world game with several scrolling levels that consist of different background images for each level. So there will be more than eight 2048x1536 images for each level, depending of the length of the scrolling on each level. I’m thinking of a game similar to Jetpack Joyride.

I’ve spent days and countless hours on trying out different ways to make the scrolling work without twitches. I’ve tried the solutions in all Stack Overflow posts I’ve found.

The closest thing to get this to work is to split the 2048x1536 images into sixteen smaller 128x1536px pieces. But that results in a lot of nodes and the twitching still occurs until all images has looped, but the twitching occurs less frequent but it’s still there.

I’m using an iPad Air (1 gen) with iOS version 10.3.3 (14G60) for testing.

Xcode Version 8.3.3 (8E3004b)

Apple Swift version 3.1 (swiftlang-802.0.53 clang-802.0.42) Target: x86_64-apple-macosx10.9

My test project has a 10.0 deployment target.

The only thing I can think of is that is a preload issue, and that I'm not doing it the right way. The code and the scrolling works perfectly after all the images has looped around one time.

So my question is: “How can I make scrolling of full screen images the right way without the initial twitching, with Swift and SpriteKit?”.

0 个答案:

没有答案