Preload a Scene to prevent lag? - swift

Hello guys i have made the first Level of my game, but always when i go from the main menu screen to the first level the screen freezes for like 2 Seconds and the transition from the Main screen to the game is very delayed and laggy and it sometimes doesn't even show up. Is there a way to preload the Scene in the background to prevent the lag?

you can load the resources for the scene in a different thread. I do this in my game to get really snappy scene transitions despite the fact im loading tons of resources.
make a static function in your scene class to preload your scene
class func createResources(withCompletion: (scene: BaseScene) -> ()){
// load resources on other thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), {
let scene = YourScene()
// callback on main thread
dispatch_async(dispatch_get_main_queue(), {
// Call the completion handler back on the main queue.
withCompletion(scene: scene)
});
})
}
call it like this
YourScene.createResources(withCompletion: {
[weak self]
scene in
self!.skView.presentScene(scene)
})
So the way to use this is to build your scene in advance on the different thread. since its running on a different thread you shouldnt get that awkward pause.
for example. lets say the player reaches the goal of beating the level. before I was using this method the game would pause for a second before loading the next scene.
When the player beats the level now I still allow them to move around until the next scene has loaded and then the player will instantly shoot into the next level creating an instant transition.
you can see it here when the ship is hyperspacing between levels. there are a lot of resources loading but the transitions are seamless.
https://www.youtube.com/watch?v=u_bXA3woOmo

Swift 5 version of #hamobi answer
file: DispatchQueueExtensions.swift
import Foundation
extension DispatchQueue {
static func background(_ task: #escaping () -> Void) {
DispatchQueue.global(qos: .background).async {
task()
}
}
static func main(_ task: #escaping () -> Void) {
DispatchQueue.main.async {
task()
}
}
}
file: GameScene.swift
extension GameScene {
class func create(completion: #escaping (_ scene: GameScene) -> Void) {
DispatchQueue.background {
let scene = GameScene()
DispatchQueue.main {
completion(scene)
}
}
}
}
Usage:
GameScene.create(completion: { [weak self] scene in
let transition = SKTransition.doorway(withDuration: 1.0)
self?.view?.presentScene(scene, transition: transition)
})

Related

Remove SKAction and restore node state

Desired behavior is: when an action is removed from a node (with removeAction(forKey:) for instance) it stops to animate and all the changes caused by action are discarded, so the node returns back to pervious state. In other words, I want to achieve behavior similar to CAAnimation.
But when a SKAction is removed, the node remains changed. It's not good, because to restore it's state I need to know exactly what action was removed. And if I then change the action, I also will need to update the node state restoration.
Update:
The particular purpose is to show possible move in a match-3 game. When I show a move, pieces start pulsating (scale action, repeating forever). And when the user moves I want to stop showing the move, so I remove the action. As the result, pieces may remain downscaled. Later I would like to add more fancy and complicated animations, so I want to be able to edit it easily.
Thanks to the helpful comment and answer I came to my own solution. I think the state machine would be bit too heavy here. Instead I created a wrapper node, which main purpose is run the animation. It also has a state: isAimating property. But, first of all, it allows to keep startAnimating() and stopAnimating() methods close to each other, incapsulated, so it's more difficult to mess up.
class ShowMoveAnimNode: SKNode {
let animKey = "showMove"
var isAnimating: Bool = false {
didSet {
guard oldValue != isAnimating else { return }
if isAnimating {
startAnimating()
} else {
stopAnimating()
}
}
}
private func startAnimating() {
let shortPeriod = 0.2
let scaleDown = SKAction.scale(by: 0.75, duration: shortPeriod)
let seq = SKAction.sequence([scaleDown,
scaleDown.reversed(),
scaleDown,
scaleDown.reversed(),
SKAction.wait(forDuration: shortPeriod * 6)])
let repeated = SKAction.repeatForever(seq)
run(repeated, withKey: animKey)
}
private func stopAnimating() {
removeAction(forKey: animKey)
xScale = 1
yScale = 1
}
}
Usage: just add everything that should be animated to this node. Works well with simple animations, like: fade, scale and move.
As #Knight0fDragon suggested, you would be better off using the GKStateMachine functionality, I will give you an example.
First declare the states of your player/character in your scene
lazy var playerState: GKStateMachine = GKStateMachine(states: [
Idle(scene: self),
Run(scene: self)
])
Then you need to create a class for each of these states, in this example I will show you only the Idle class
import SpriteKit
import GameplayKit
class Idle: GKState {
weak var scene: GameScene?
init(scene: SKScene) {
self.scene = scene as? GameScene
super.init()
}
override func didEnter(from previousState: GKState?) {
//Here you can make changes to your character when it enters this state, for example, change his texture.
}
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass is Run.Type //This is pretty obvious by the method name, which states can the character go to from this state.
}
override func update(deltaTime seconds: TimeInterval) {
//Here is the update method for this state, lets say you have a button which controls your character velocity, then you can check if the player go over a certain velocity you make it go to the Run state.
if playerVelocity > 500 { //playerVelocity is just an example of a variable to check the player velocity.
scene?.playerState.enter(Run.self)
}
}
}
Now of course in your scene you need to do two things, first is initialize the character to a certain state or else it will remain stateless, so you can to this in the didMove method.
override func didMove(to view: SKView) {
playerState.enter(Idle.self)
}
And last but no least is make sure the scene update method calls the state update method.
override func update(_ currentTime: TimeInterval) {
playerState.update(deltaTime: currentTime)
}

Retain cycle trouble when transitioning between scenes

I have a game in which the main menu presents the game scene, and when the player loses, the game scene presents back the menu. When transitioning from the menu to the game scene, the menu's deinit is successfully called. My problem is that the opposite doesn't happen (when transitioning from the game scene to the menu scene, gameScene's deinit isn't called).
I suspect a strong reference cycle directly between the new main menu instance that I create and the game scene which creates the instance. Matter of facts, if I edit my gameScene's code as following, I do get the deinit's call, but my app crashes at runtime while trying to present the scene:
class GameScene: SKScene {
// Some stuff
// Now the function which is called when I want to present back the menu scene:
func lose() {
// Some stuff
// Delay is a helper function
delay(bySeconds: 2.0, closure: { [unowned self] in
for child in self.children {
child.removeFromParent()
}
unowned let menu = MainMenu(size: CGSize(width: 1152, height: 2048))
let reveal = SKTransition.fade(with: SKColor.black, duration: 1.0)
self.view?.presentScene(menu, transition: reveal)
})
}
}
At runtime I get the following error:
Attempted to retain deallocated object
Here's the definition of delay:
public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: #escaping () -> Void) {
let dispatchTime = DispatchTime.now() + seconds
dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}
public enum DispatchLevel {
case main, userInteractive, userInitiated, utility, background
var dispatchQueue: DispatchQueue {
switch self {
case .main: return DispatchQueue.main
case .userInteractive: return DispatchQueue.global(qos: .userInteractive)
case .userInitiated: return DispatchQueue.global(qos: .userInitiated)
case .utility: return DispatchQueue.global(qos: .utility)
case .background: return DispatchQueue.global(qos: .background)
}
}
}
And here's my MainMenu class:
class MainMenu: SKScene {
let gameScene = GameScene(size: CGSize(width: 1152, height: 2048))
let reveal = SKTransition.fade(with: SKColor.black, duration: 1.0)
// Some stuff
// override func didMove(to ...
// Now the function which is called when I want to present the game scene:
func presentGame() {
view?.presentScene(gameScene, transition: reveal)
}
}
I've tried different things but they didn't work out (for example: setting the new menu instance in the init method of gameScene).
If I remove the "unowned" definition of the menu constant in the GameScene class, the transition is successfully made but deinit isn't called.
Any suggestions?
I finally found what had been keeping my game scene from deinitializing itself.
It had nothing to do with the code that I provided with my question. The problem was in a strong "self" reference inside a closure which was responsible to interpret the accelerometer's input.
If you're interested in more informations, see here: Retain cycle suspected in closure

swift function to show the score in the Game Center

I am developing a game with five scenes (SKSecne) in swift. I am using the following function to show the score in the Game Center at the end of each scene. Currently I have to copy the function to all the scene files.
How can I modify the function so I can call it from all the scene files without duplicating it?
func showLeader() {
let viewControler = self.view?.window?.rootViewController
let gameCenter = GKGameCenterViewController()
gameCenter.gameCenterDelegate = self
viewControler?.presentViewController(gameCenter, animated: true, completion: nil) }
One solution is just create a subclass of SKScene and use it like parent for others five scenes.
class BasicScene: SKScene {
func showLeader() {}
}
class Scene1: BasicScene {
// call showLeader() when needed
}

Keeping the game paused after app become active?

It is my first post on this forum and I apologize in advance if I am doing something not in the right way ! :)
I am making an iphone game with Swift & SpriteKit and I am currently facing a problem. When my app is going to background it calls a function pause (cf. below) but it automatically unpause when the game resumes.
I have seen this very interesting post : Spritekit - Keep the game paused when didBecomeActive (and How to keep SpriteKit scene paused when app becomes active?) but I am stuck.
I don't know how to implement the new SKView class as my View is configured as shown in the below code...
This is how my application works :
class GameViewController: UIViewController {
var scene: GameScene!
override func viewDidLoad() {
super.viewDidLoad()
// Configure the View
let SkView = view as! SKView
SkView.multipleTouchEnabled = true
// Create and configure the scene
scene = GameScene(size: SkView.bounds.size)
scene.scaleMode = .AspectFill
// Present the scene
SkView.presentScene(scene)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("PauseWhenBackGround:"), name:"PauseWhenBackGround", object: nil)
}
func PauseWhenBackGround(notification : NSNotification) {
if scene.Pausing == false{
scene.Pause()
}
}
I've tried the following :
I added a new class which is :
class GameSceneView : SKView {
func CBApplicationDidBecomeActive() {
}
}
Then, I tried to set my view as let SkView = view as! GameSceneView but I got an error saying that I cannot cast the view to MyProjectName.GameSceneView()...
I also tried the following : let SkView! = GameSceneView() as GameSceneView! but I end up with a gray background scene...
Does anyone knows how I can implement the new SKView class to prevent the CBApplicationDidBecomeActive() bug from happening so that the game does not unpause when becoming active ?
Thank you very much in advance ! :)
I think a better way is instead of pausing the whole scene you could create a worldNode in your GameScene and add all the sprites that need to be paused to that worldNode. Its better because if you pause the scene you cannot add pause menu nodes or use touches began etc. It basically gives you more flexibility pausing a node rather than the whole scene.
First create the world node (make global property if needed)
let worldNode = SKNode()
addChild(worldNode)
Than add all the sprites you need paused to the worldNode
worldNode.addChild(sprite1)
worldNode.addChild(sprite2)
Create an enum for your different game states
enum GameState {
case Playing
case Paused
case GameOver
static var current = GameState.Playing
}
Than make a pause func in your game scene
func pause() {
GameState.current = .Paused
//self.physicsWorld.speed = 0 // in update
//worldNode.paused = true // in update
// show pause menu etc
}
And call it like you did above using NSNotification or even better using delegation.
I prefer this method way more than pausing the scene from the gameViewController and also pausing the whole scene.
Create a resume method
func resume() {
GameState.current = .Playing
self.physicsWorld.speed = 1
worldNode.paused = false
// remove pause menu etc
}
and finally add this to your update method
override func update(currentTime: CFTimeInterval) {
if GameState.current == .Paused {
self.physicsWorld.speed = 0
worldNode.paused = true
}
Spritekit sometimes tends to resume the game when the app becomes active again or when an alert such as for in app purchases is dismissed. To avoid this I always put the code to actually pause the game in the update method.
Hope this helps.

Sprite Kit UIActivityIndicator won't stop animating

I'm trying to pre-load an array of texture atlases during which a UIActivityIndicator is displayed. Once the textures are loaded I want to stop the activity indicator using the .stopAnimating() method. I've inserted breakpoints and found that the compiler does get to the .stopAnimating() method, but nothing happens...the indicator continues...
What am I doing wrong here?
class Menu: SKScene {
var activityInd: UIActivityIndicatorView!
override func didMoveToView(view: SKView) {
activityInd = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
activityInd.center = CGPointMake(self.frame.midX, self.frame.midY)
activityInd.startAnimating()
scene!.view?.addSubview(self.activityInd)
SKTextureAtlas.preloadTextureAtlases([saxAtlas, saxIdleAtlas, drumAtlas, drumIdleAtlas, pianoAtlas, pianoIdleAtlas, bassAtlas]) { () -> Void in
self.activityInd.stopAnimating()
}
}
Usually when you want to stop an activity indicator you just call removeFromSuperview() as it's no good to have a static activity indicator sitting there doing nothing, and that's all that stopAnimating() gives you.
You should also be calling this method on the main thread, since preloadTextureAtlases is a background task and just about anything prefixed with 'UI' needs to be run on the main thread.
SKTextureAtlas.preloadTextureAtlases([saxAtlas, saxIdleAtlas, drumAtlas, drumIdleAtlas, pianoAtlas, pianoIdleAtlas, bassAtlas]) { () -> Void in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.activityInd.stopAnimating()
}
}