Хорошие практики перехода на сцену SpriteKit

Я пишу игру, используя SpriteKit со Swift, и столкнулся с проблемой памяти.

Макет моей игры таков, что GameViewController (UIViewController) представляет первый SKScene (levelChooserScene) на экране viewDidLoad. Эта сцена не более чем отображает кучу кнопок. Когда пользователь выбирает кнопку, сцена затем переходит на правильную сцену с помощью skView.presentScene, а когда уровень завершается, эта сцена затем возвращается к levelChooserScene, и игра готова для выбора пользователем следующего уровня.

Проблема в том, что когда происходит переход обратно к levelChooserScene, память, выделенная для игровой сцены, не освобождается, поэтому после выбора только нескольких уровней я начинаю получать ошибки памяти.

Верен ли мой дизайн при переходе от SKScene к SKScene, или я должен вместо этого каждый раз возвращаться к GameViewController и затем оттуда переходить к следующему SKScene?

Я нашел несколько постов, в которых говорится, что я должен вызывать skView.presentScene(nil) между сценами, но я не понимаю, как и где это реализовать.

Я просто хочу перейти от одной SKScene к другой, чтобы память, использованная в исходящей сцене, была возвращена в систему.

Это пример того, как я реализовал SKScene:

class Level3: SKScene
{
   var explodingRockTimer = NSTimer()
   var blowingUpTheRocks = SKAction()

   override func didMoveToView(view: SKView)
   {
       NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "dismissTheScene:", userInfo: nil, repeats: false)
       var wait = SKAction.waitForDuration(0.5)
       var run = SKAction.runBlock{
           // your code here ...
           self.explodeSomeRocks()
       }
       let runIt = SKAction.sequence([wait,run])
       self.runAction(SKAction.repeatActionForever(runIt), withKey: "blowingUpRocks")

       var dismissalWait = SKAction.waitForDuration(5.0)
       var dismissalRun = SKAction.runBlock{
           self.removeActionForKey("blowingUpRocks")
           self.dismissTheScene()

       }
       self.runAction(SKAction.sequence([dismissalWait,dismissalRun]))
   }

   func explodeSomeRocks()
   {
       println("Timer fired")
   }

   //MARK: - Dismiss back to the level selector
   func dismissTheScene()
   {
       let skView = self.view as SKView?
       var nextScene = SKScene()

       nextScene = LevelChooserScene()
       nextScene.size = skView!.bounds.size
       nextScene.scaleMode = .AspectFill
       var sceneTransition = SKTransition.fadeWithColor(UIColor.blackColor(), duration: 1.5) //WithDuration(2.0)
       //var sceneTransition = SKTransition.pushWithDirection(SKTransitionDirection.Down, duration: 0.75) //WithDuration(2.0)
       //var sceneTransition = SKTransition.crossFadeWithDuration(1.0)
       //var sceneTransition = SKTransition.doorwayWithDuration(1.0)
       sceneTransition.pausesOutgoingScene = true

       skView!.presentScene(nextScene, transition: sceneTransition)
   }
}

2 ответа

Решение

Ну, единственное, что вызывало у меня проблемы, это вставка эмиттеров частиц каждые полсекунды в течение 5 секунд с использованием SKAction.repeatActionForever() для вызова функции вставки эмиттера.

Это повторяющееся действие, по-видимому, не было убито при переходе на другую сцену и вызывало сохранение памяти для всей сцены. Вместо этого я переключился на SKAction.repeatAction() и указал, сколько раз он должен срабатывать. Сцена теперь возвращает всю свою память, когда я перехожу на новую сцену.

Я не уверен, что понимаю это поведение, хотя.

SpriteKit не является строго задокументированным, когда дело доходит до создания сложных игр. У меня лично была такая проблема в течение нескольких дней, пока мне не удалось ее решить.

Некоторые объекты сохраняют ссылку, поэтому она не деиницируется. (SKActions, Timers, и т. Д.)

Прежде чем представить новую сцену, я называю prepare_deinit() функция, где я вручную удаляю сильные ссылки, которые обычно не освобождаются с помощью swift.

func prepare_deinit()
{
    game_timer.invalidate() // for Timer()
    removeAction(forKey: "blowingUpRocks") // for SKAction in your case

    // I usually add the specific actions to an object and then remove 
    object.removeAllActions()

    // If you create your own object/class that doesn't deinit, remove all object 
    //actions and the object itself
    custom_object.removeAllActions()
    custom_object.removeFromParent()

}

deinit
{
   print("GameScene deinited")
}

Последняя проблема, с которой я столкнулся, заключалась в том, что новая сцена была представлена ​​намного быстрее, чем моя prepare_deinit() поэтому мне пришлось представить новую сцену чуть позже, предоставив prepare_deinit() достаточно времени для освобождения всех объектов.

let new_scene =
{
   let transition = SKTransition.flipVertical(withDuration: 1.0)
   let next_scene = FinishScene(fileNamed: "FinishScene")
   next_scene?.scaleMode = self.scaleMode
   next_scene?.name = "finish"
   self.view?.presentScene(next_scene!, transition: transition)
}

run(SKAction.sequence([SKAction.run(prepare_deinit), SKAction.wait(forDuration: 0.25), SKAction.run(exit_to_finish)]))
Другие вопросы по тегам