I have a Swift SpriteKit scene and it doesn't happen every time but I did some testing on the restart game button by pressing menu and restart in quick successions repeatedly till it crashes. It also crashes randomly from other points that load the scene. My estimate is that theres about a 30% chance that it will crash on this line
class AppDelegate: UIResponder, UIApplicationDelegate {
with with the following error:
2015-03-07 09:52:14.347 DDgame[1457:433285] * Terminating app due
to uncaught exception 'NSGenericException', reason: '* Collection
<__NSArrayM: 0x170051e50> was mutated while being enumerated.'
*** First throw call stack: (0x18342259c 0x193b6c0e4 0x183421f50 0x187a57538 0x187a56c84 0x187a5661c 0x187a56510 0x10008f4c0
0x1000913b0 0x187a46bcc 0x187a43fd8 0x187a41038 0x187a6dfd8
0x100360a9c 0x187575280 0x187575118 0x1845718d0 0x1833c55e4
0x1833da200 0x1833da160 0x1833d80e0 0x1833050a4 0x18c49f5a4
0x187c36aa4 0x1000d1204 0x1000d1244 0x1941daa08) libc++abi.dylib:
terminating with uncaught exception of type NSException (lldb)
I used an catch all exception breakpoint and found out it is crashing in my update method on the gyro updates. Does anyone have a safer way of keeping gyro going safely with an update button?
Here is an extract of the touchesBegan method, I've removed the code sitting inside the other buttons just not to create too much fluff here: (not sure if you guys need the extras here?)
else if node.name == "replayButton" {
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
NSNotificationCenter.defaultCenter().removeObserver(self, name: "pauseGame", object: nil)
let transition = SKTransition.revealWithDirection(SKTransitionDirection.Down, duration: 1.0)
scene.scaleMode = SKSceneScaleMode.AspectFill
self.view?.presentScene(scene, transition: transition)
}
I have discovered that it is crashing on the gyro updates in the update method:
override func update(currentTime: NSTimeInterval) {
var timeSinceLast = currentTime - lastUpdateTimeInterval
lastUpdateTimeInterval = currentTime
if(!gameOver) {
//check for gameover
if (self.goldStash <= 0) {
gameOver = true
gameIsPaused = true
self.gameOverMethod()
}
//spawning logic
spawnEngine()
//gyro update
if (self.motionManager.gyroData != nil) {
self.updateGyroNodeWithData(self.childNodeWithName("GoldBack") as? SKSpriteNode, gyroIn: motionManager.gyroData)
self.updateGyroNodeWithDataReverse(self.childNodeWithName("GoldFront") as? SKSpriteNode, gyroIn: motionManager.gyroData)
self.updateGyroNode(self.childNodeWithName("CBack1") as? SKSpriteNode, gyroIn: motionManager.gyroData, minIn: cBack1position.x-10.0, maxIn: cBack1position.x+10.0, sensitivity: 0.8)
self.updateGyroNode(self.childNodeWithName("CBack2") as? SKSpriteNode, gyroIn: motionManager.gyroData, minIn: cBack2position.x-10.0, maxIn: cBack2position.x+10.0, sensitivity: 0.8)
self.updateGyroNode(self.childNodeWithName("CBack3") as? SKSpriteNode, gyroIn: motionManager.gyroData, minIn: cBack3position.x-10.0, maxIn: cBack3position.x+10.0, sensitivity: 0.8)
self.updateGyroNode(self.childNodeWithName("CBack4") as? SKSpriteNode, gyroIn: motionManager.gyroData, minIn: cBack4position.x-10.0, maxIn: cBack4position.x+10.0, sensitivity: 0.8)
}
}
if timeSinceLast > 1 {
timeSinceLast = kMinTimeInterval
}
updateWithTimeSinceLastUpdate(timeSinceLast)
}
func updateWithTimeSinceLastUpdate(timeSinceLast: NSTimeInterval) {
for dwarf in dwarves {
dwarf.updateWithTimeSinceLastUpdate(timeSinceLast)
}
}
I fixed the problem. It was in this piece of code:
self.childNodeWithName("GoldBack") as? SKSpriteNode
It kept enumerating the scene for the nodes while the scene was then destroyed. I tried putting in a boolean to prevent the gyro updates running while the menu was open but for some reason it kept crashing, the percentage dropped significantly but something wasn't working.
The best solution was to create variables of the nodes in the init instead and then run gyro updates on those in the update method.
var goldBack = SKSpriteNode?()
goldBack = self.childNodeWithName("GoldBack") as? SKSpriteNode
Then in the update method:
self.updateGyroNodeWithData(goldBack, gyroIn: motionManager.gyroData)
I didn't know about this before but, another thing that really helped me was adding 'All Exceptions' breakpoint in Xcode. (This is done by going to the breakpoint navigator, pressing the + on the bottom left and add exception breakpoint)
Related
I have created a HomeScene.sks and a HomeScene.swift. In the GameViewController I've changed the default "GameScene" to "HomeScene" like this"
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'HomeScene.sks'
if let scene = SKScene(fileNamed: "HomeScene") {
// 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
}
}
When I run it in the Simulator it shows the LaunchScreen but than it stops. In my xcode output screen I see:
2017-05-22 21:02:07.910 DiceWar[88707:2060079] -[DiceWar.HomeScene setNormalTexture:]: unrecognized selector sent to instance 0x7fdba6008af0
2017-05-22 21:02:07.915 DiceWar[88707:2060079] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[DiceWar.HomeScene setNormalTexture:]: unrecognized selector sent to instance 0x7fdba6008af0'
I Googled around but can't find anything useful. What are these lines telling me? Did I use a bad texture? I also tried to message out all the code in HomeScene.swift but the error still occurs.
I'm not sure if it's because English isn't my native language or if it's my logic but I really don't understand what xcode is trying to tell me.
Edit
I think I found it. Because there was something with a texture. I deleted a Color Sprite from the HomeScene.sks and now it does start. When I replaced the Color Sprite there wasn't a problem until I used this line:
settingsButton = self.childNode(withName: "settingsButton") as! SKSpriteNode
The strange thing is, I used the exact some line for another image (instead of settingsButton I used playButton) The playButton line doesn't seem to be a problem.
The error message is saying that your application logic is trying to call setNormalTexture(_:) on your class DiceWar.HomeScene, but that class does not know how to handle that method call.
From the documentation:
This action can only be executed by an SKSpriteNode object.
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.
I'm currently working with Swift in SpriteKit to make a small/basic mini game, and I'm running into the error displayed in the title above...
Most of the time, everything works fine, but every once in a while, I get this error (the majority of the times it happens, it's right when the game over function seen below gets called). The error always points to the following line:
func handle_tilt(acceleration_data: CGFloat)
{
if ((childNodeWithName("hero")) != nil){
childNodeWithName("hero")?.physicsBody?.applyForce(CGVectorMake(acceleration_data * 50, 0))
}
}
How can I fix this?
**the code for the accelerometer set up below is contained in the
if motionManager.accelerometerAvailable == true {
self.motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue()) {
(data, error) in
if (data.acceleration.y != 0) {
self.motionManager.accelerometerActive == true
self.handle_tilt(CGFloat(data.acceleration.y))
}
}
}
The function which, when called, triggers the error...
func game_over()
{
self.removeActionForKey("spawn_flying_squirrels")
self.removeActionForKey("spawn_flying_squirrels")
println("Removed the actions")
self.motionManager.stopAccelerometerUpdates()
println("removed motion manager")
if let recognizers = self.view?.gestureRecognizers {
for recognizer in recognizers {
self.view!.removeGestureRecognizer(recognizer as! UIGestureRecognizer)
}
}
println("removed the gesture recognizers")
//saves the score or doesn't (if it doesn't make the high score
saveScore(score)
for child in self.children as! [SKNode]
{
removeChildrenInArray([child])
}
println("removed the children")
let scene = GameScene(size: self.size)
scene.scaleMode = .AspectFill
NSLog("HE DEAD")
view?.presentScene(scene)
}
What I think is happening is that the game_over function deletes the "hero" node, and then the handle_tilt function tries unsuccessfully to access "hero" leading to a meltdown. I tried to prevent it with the if statement in the handle_tilt function, but it wasn't a complete success like I had hoped...
I am writing a game using SpriteKit with Swift and have run into a memory concern.
The layout of my game is such that the GameViewController (UIViewController) presents the first SKScene (levelChooserScene) in the viewDidLoad Screen. This scene does nothing more than display a bunch of buttons. When the user selects a button the scene then transitions to the correct scene using skView.presentScene, and when the level is complete, that scene then transitions back to the levelChooserScene and the game is ready for the user to select the next level.
The problem is that when the transition back to the levelChooserScene occurs the memory allocated for the game play scene is not deallocated, so after selecting only a few levels I start receiving memory errors.
Is my design correct in transitioning from SKScene to SKScene, or should I instead return to the GameViewController each time and then transition to the next SKScene from there?
I have found a few posts on here that say I should call skView.presentScene(nil) between scenes, but I am confused on how or where to implement that.
I simply want to transition from one SKScene to another and have the memory used from the outgoing scene to be returned to the system.
This is an example of how I have implemented the 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)
}
}
Well the thing that was causing my trouble was inserting particle emitters every half second for 5 seconds using SKAction.repeatActionForever() to call the emitter insert function.
This repeatAction apparently was not killed by transitioning to another scene, and was causing the memory for the whole scene to be retained. I switched to SKAction.repeatAction() instead and specify how many time it should fire. The scene now returns all of its memory when I transition to the new scene.
I am not sure I understand this behavior though.
SpriteKit it's not strongly documented when it comes to create complex games. I personally had a problem like this for days until I managed to figure it out.
Some objects retain the reference, so it doesn't deinit. (SKActions, Timers, etc)
Before presenting a new scene I call a prepare_deinit() function where I manually remove the strong references which are usually not deallocated by 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")
}
The last problem I encountered was that the new scene was presented much faster than my prepare_deinit() so I had to present the new scene a little later, giving the prepare_deinit() enough time to deallocate all objects.
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)]))
My app contains two scenes. Playscene.swift and gamescene.swift.
Transitioning from gamescene to playscene (where play takes place) works perfectly. However, once a gameover is reached, I have a "replay" button appear allowing the user to return back to gamescene.swift. Upon transitioning back it crashes with an error "Attempted to add a SKNode which already has a parent." Is there a correct way to transition back to a home screen or restart the game so I don't receive the error? Thank you for all your help!!
if self.nodeAtPoint(touchLocation) == self.replay {
let scene = GameScene(size: self.size)
let skView = self.view as SKView!
scene.size = skView.bounds.size
self.view?.presentScene(scene)
let transition = SKTransition.crossFadeWithDuration(1.0)
transition.pausesOutgoingScene = false
skView.presentScene(scene, transition: transition)
}
}
Gamescene.swift error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attemped to add a SKNode which already has a parent: <SKLabelNode> name:'(null)' text:'Highscore:' fontName:'Avenir-Black' position:{344, 549}'
*** First throw call stack:
I feel stupid. I had added a breakpoint and wasn't paying attention. its safe to say its time for bed.
You are trying to present the same scene twice :
self.view?.presentScene(scene)
skView.presentScene(scene, transition: transition)
Maybe you wan't to do like so :
if self.nodeAtPoint(touchLocation) == self.replay {
let skView = self.view as SKView!
let scene = GameScene(size: skView.bounds.size)
// Might also work with the following line instead :
// let scene = GameScene(size: self.frame.size)
let transition = SKTransition.crossFadeWithDuration(1.0)
transition.pausesOutgoingScene = false
self.view?.presentScene(scene, transition: transition)
}
You might be declaring that SKLabelNode outside of the GameScene class, making the SKLabelNode global, you should declare it inside the class so it dies when scene A transitions to scene B.