Transitioning to viewController from SKScene found nil when unwrapping optional value - ios

so i found some posts about transitioning from Skscene to uiviewcontroller and I got it to work. This segue and unwind segue is called everytime the user win the level of my game or loses.
this works for level1 but as soon as I win level2 I get
fatal error: unexpectedly found nil while unwrapping an Optional value
on the line where I call the game over function below
in my game scene I have :
class GameScene: SKScene, SKPhysicsContactDelegate {
//next level / try again segue
var viewController : GameViewController!
in the GameViewController i initialize this property
var currentLevel: Int!
override func viewDidLoad() {
super.viewDidLoad()
currentLevel = gameScene.currentLevel
if let scene = GameScene.level(currentLevel) {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
//initialize VC
scene.viewController = self
}
func gameOver() {
performSegueWithIdentifier("gameOver", sender: nil)
}
#IBAction func perpareForUnwind(unwindSegue: UIStoryboardSegue) {
}
and finally i call gameOver from my win() function in gameScene
func newGame() {
view!.presentScene(GameScene.level(currentLevel))
}
func win() {
if (currentLevel < 3) {
currentLevel++
//present win view - with option to restart, next level, or home
}
println(currentLevel)
runAction(SKAction.sequence([SKAction.waitForDuration(2),
SKAction.runBlock(newGame)]))
self.viewController.gameOver() // this is the error!
}
So this works from level1 to level2 but wont work from level2 to level3
Since viewDidLoad is only called once it is only initialized from lvl1 to lvl2 and then becomes nil. How can i make sure that it is initialized everytime. Shoud I put this set up code somewhere other than viewDidLoad?

From level 1 to level 2 your var viewController is not nil, because you initialize it to self on viewDidLoad.
But then, on your next level, viewController is nil, because your code
//initialize VC
scene.viewController = self
only gets executed on viewDidLoad in your GameViewController.
You should set the value of your var viewController in your scene init method or didMoveToView, so it gets set for every new scene.

Related

Swift, SpriteKit: Deallocate a Gamescene et reallocate a new one

I need to deallocate gamescene after the player have finished a level so that the memory is available to load another level of my game. If I don't do that, I have a crash of my app because of memory issues.
I have followed the indications given there:
Swift: Deallocate GameScene after transition to new scene?
But unfortunately, it doesn't work for me. I get an error "Could not cast value 'UIView' to 'SKView' in my GameViewController class. Here is my whole code:
import Foundation
import UIKit
import SpriteKit
class GameViewController: UIViewController {
var scene1: SKScene?
var scene2: SKScene?
var scene3: SKScene?
var skView: SKView?
func nextSceneAction1() {
scene2 = nil
scene3 = nil
skView! = self.view as! SKView
skView!.showsFPS = true
skView!.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView!.ignoresSiblingOrder = true
scene1 = TransitionSigns(size: skView!.frame.size)
scene1!.size.width = 2048
scene1!.size.height = 1536
/* Set the scale mode to scale to fit the window */
scene1!.scaleMode = .AspectFill
let transition = SKTransition.revealWithDirection(.Right, duration: 2)
scene1!.scaleMode = .AspectFill
skView!.presentScene(scene1!, transition: transition)
}
func nextSceneAction2() {
scene1 = nil
scene3 = nil
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
scene2 = TransitionSigns(size: skView.frame.size)
scene2!.size.width = 2048
scene2!.size.height = 1536
/* Set the scale mode to scale to fit the window */
scene2!.scaleMode = .AspectFill
let transition = SKTransition.revealWithDirection(.Right, duration: 2)
scene2!.scaleMode = .AspectFill
skView.presentScene(scene2!, transition: transition)
}
func nextSceneAction3() {
scene1 = nil
scene2 = nil
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
scene3 = TransitionSigns(size: skView.frame.size)
scene3!.size.width = 2048
scene3!.size.height = 1536
/* Set the scale mode to scale to fit the window */
scene3!.scaleMode = .AspectFill
let transition = SKTransition.revealWithDirection(.Right, duration: 2)
scene3!.scaleMode = .AspectFill
skView.presentScene(scene3!, transition: transition)
}
override func viewWillLayoutSubviews() {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
let scene = MainView(size: skView.frame.size)
scene.size.width = 2048
scene.size.height = 1536
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return UIInterfaceOrientationMask.AllButUpsideDown
} else {
return UIInterfaceOrientationMask.All
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidDisappear(animated: Bool) {
self.view.removeFromSuperview()
}
override func viewWillDisappear(animated: Bool){
self.view.removeFromSuperview()
}
}
I call the different functions nextSceneAction1(), nextSceneAction2(), and nextSceneAction3() from my other classes with:
let controller1 = GameViewController()
controller1.nextSceneAction1()
or
let controller2 = GameViewController()
controller2.nextSceneAction1()
When the new scene is loaded, I get this error: "Could not cast value of type 'UIView' to 'SKView'. Do you know what is wrong with this code?
By the way, I'm open with all other possibilities to clean old scene from memory before loading a new one so that there is no memory crash during the life of the app.
In general a SKScene automatically deallocates once you transition to a new scene, unless you have a memory leak, so usually you don't have to do anything in particular to free memory.
To see if your scene has deallocated you can add the deinit method
func deinit {
print("scene did deallocate")
}
If this method gets called when you change scenes you know everything correctly deallocated, if it doesn't get called you have a memory leak.
Also you code is faulty, you are creating a new GameViewController instance everytime you change scene instead of accessing the current instance.
let controller2 = GameViewController() // creates new instance of GameViewController
controller2.nextSceneAction1()
You normally only load your 1st scene from your GameViewController and than all other scene changes can be done directly in the SKScene you are in. Don't bother using the GameViewController for this.
In your current SKScene you can change to a new scene like this (e.g from GameScene to MenuScene)
class GameScene: SKScene {
...
func loadMenuScene() {
let menuScene = MenuScene(size: self.size) // use size of current scene
let transition = Some SKTransition
view?.presentScene(menuScene, withTransition: transition)
}
}
Hope this helps

Seque from a storyboard image tap into a SpriteKitScene

I'm creating a Swift project for a high school programming class. I can't seem to figure out this problem, and everyone else in my class doesn't seem to have any ideas.
To start, I created a new Swift project, and chose a game format.
I then used some basic code to make the first level for my game, a maze game where the maze moves around instead of the ball based on how the user tilts the device.
This is my GameScene.swift:
import SpriteKit
import CoreMotion
var accelupdateinterval = 0.1
var accelmultiplier = 15.0
class GameScene: SKScene {
let manager = CMMotionManager()
override func didMoveToView(view: SKView) {
manager.startAccelerometerUpdates()
manager.accelerometerUpdateInterval = accelupdateinterval
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()){
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!) * CGFloat(accelmultiplier), CGFloat((data?.acceleration.y)!) * CGFloat(accelmultiplier))
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
I want to have a main menu that the app opens into, which is mainMenu.storyboard:
I successfully have the app launching into the mainMenu.storyboard (and the level physics work well when I've tested the level1.sks), but I'm having trouble figuring out how to segue.
GOAL: I want people to be segued into the level1.sks (and the levels that I add later), when they tap the corresponding image in mainMenu.storyboard.
I can't use the method of adding a Storyboard Reference to segue it, as the Storyboard Reference won't let me choose level1.sks.
I'd also love to find out how to send users back to the main menu when the player icon touches the goal (the blue thing up near the top in this screenshot):
So to do this I think the best approach is to create another ViewController subclass, maybe named LauncherViewController, which will present your SKScene. Then in your storyboard add this viewController and have your menu segue to it on an image press.
Here is a start for the LauncherViewController
class LauncherViewController: UIViewController {
var gameScene: GameScene!
override func viewDidLoad() {
}
override func viewWillAppear() {
presentGameScene()
}
func presentGameScene(){
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
gameScene.size = skView.bounds.size
gameScene.scaleMode = .AspectFill
skView.presentScene(gameScene)
}
}
Where in your menu controller you have a prepareForSegue like this:
override func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
if segue.identifier == "yourSegue" {
let destinationViewController = segue.destinationViewController as! LauncherViewController
destinationViewController.gameScene = GameScene()
}
to have your LauncherViewController dismiss the gameScene when the user finishes the maze, use a delegate pattern. So in GameScene add a protocol above your class
protocol GameDelegate {
func gameFinished()
}
have your LauncherViewController conform to this delegate and set the gameScene's delegate variable to self (see below)
class LauncherViewController: UIViewController, GameDelegate {
var gameScene: GameScene!
override func viewDidLoad() {
gameScene.delegate = self
}
override func viewWillAppear() {
presentGameScene()
}
func presentGameScene(){
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
skView.ignoresSiblingOrder = true
gameScene.size = skView.bounds.size
gameScene.scaleMode = .AspectFill
skView.presentScene(gameScene)
}
func gameFinished(){
// this forces LauncherViewController to dismiss itself
dismissViewControllerAnimated(true, completion: nil)
}
}
add a variable in GameScene to hold the delegate (LauncherViewController) and add a function that calls the delegate function. You will also need to add the logic to know when the game is over as I haven't done that.
class GameScene: SKScene {
let manager = CMMotionManager()
var delegate: GameDelegate!
override func didMoveToView(view: SKView) {
manager.startAccelerometerUpdates()
manager.accelerometerUpdateInterval = accelupdateinterval
manager.startAccelerometerUpdatesToQueue(NSOperationQueue.mainQueue()){
(data, error) in
self.physicsWorld.gravity = CGVectorMake(CGFloat((data?.acceleration.x)!) * CGFloat(accelmultiplier), CGFloat((data?.acceleration.y)!) * CGFloat(accelmultiplier))
}
}
func gameOver(){
delegate.gameFinished()
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
// it's probably easiest to add the logic for a gameOver here
if gameIsOver {
gameOver()
}
}
}
There will probably be some mistakes in here as I wrote this on my phone so just comment below for anything you are unsure about or doesn't work.

Making a button not hidden in GameScene

Hello my question is simply how to unhide a button in GameScene. I have a segueToMainMenu button that is set up in storyboard.
This is how my GameViewController looks:
#IBOutlet weak var segueToMainMenu: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size: view.bounds.size)
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = false
scene.scaleMode = .AspectFill
skView.presentScene(GameScene(size: skView.bounds.size))
scene.viewController = self
self.segueToMainMenu.hidden = true
}
I set the button to hidden but now in my game I would like to unhide it when a lose func runs in GameScene since when the button is clicked it segues back to the main menu which is a separate view controller also created in the storyboard. Anything helps thank you.
You either can create a delegate for you scene which will call appropriate method on gameOver and do something like
// in the view controller
func gameSceneDidSendGameOver() {
self.segueToMainMenu.hidden = false
}
//in the scene
var gameOverDelegate : GameOverDelegate?
func gameOver() {
gamOverDelegate?.gameSceneDidSendGameOver()
}
Or do it a little bit of ugly way like:
func gameOver() {
let gvc = self.viewController as GameViewController
gvc.segueToMainMenu.hidden = false
}
Edit: Declaration of the delegate protocol.
protocol GameOverDelegate {
func gameSceneDidSendGameOver()
}
class GameViewController : ViewController, GameOverDelegate {
...
func gameSceneDidSendGameOver() {
self.segueToMainMenu.hidden = false
}
}

I keep getting this error "unexpectedly found nil while unwrapping an Optional value" Why?

Im adding these StartApp interstitial ads and I keep getting an error when I call this function thats in my GameScene. If I call it in my GameViewController it works perfectly and I get no errors but its not working in my GameScene. How would I fix this. Thanks!
//This is the fuction that gives the error.
self.viewController.startAppAd!.loadAdWithDelegate(viewController.self)
//GameViewController.swift
class GameViewController: UIViewController, STADelegateProtocol {
var startAppAd: STAStartAppAd?
override func viewDidLoad() {
super.viewDidLoad()
startAppAd = STAStartAppAd()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
}
// StartApp Ad loaded successfully
func didLoadAd(ad: STAAbstractAd) {
println("StartApp Ad had been loaded successfully")
startAppAd!.showAd()
}
}
//GameScene.swift
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
var touch: UITouch = touches.first as! UITouch
var location = touch.locationInNode(self)
var node = self.nodeAtPoint(location)
if node.name == "levelone" {
self.viewController.startAppAd!.loadAdWithDelegate(viewController.self)
}
GameViewController creates the GameScene during viewDidLoad but you are never assigning it to the GameScene for referencing
You say you have var viewController = GameViewController() in GameScene but thats not the same instance which created the scene in the first place.
You can add this to your viewDidLoad
scene.viewController = self to ensure that the instance with an non-nil startAppAd object is the one you are referencing.
You're force unwrapping startAppAd (using the ! operator) which tells the compiler “I know that this optional definitely has a value; please use it.”. However in your code startAppAd is presumedly nil. Therefore the crash.
I suggest you take a basic tour and read Apple's "Swift Programming Language"

Trying to pass UIImage from one ViewController to my GameScene in spriteKit

I thought it would be interesting to learn how to pass an image from an imagePicker to my GameScene to use as a SKSpriteNode(texture: SKTexture(image: UIImage)) but i keep getting an error after selecting my image and pressing a button to go into my gameScene
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "startSegue") {
var gameViewController = segue.destinationViewController as GameScene
gameViewController.imageToPass = pickedImage.image
}
anyone know what im doing wrong?
keep getting exc_breakpoint (code=exc_i386_bpt, subcode 0x0)
when initializing gameViewController as GameScene
I am trying to pass imageToPass to the GameScene.swift that is created when i create a new game project in Xcode
class GameScene: SKScene {
var imageToPass = UIImage()
var bird = SKSpriteNode()
override func didMoveToView(view: SKView) {
/* Setup your scene here */
var birdTexture = SKTexture(image: imageToPass)
bird = SKSpriteNode(texture: birdTexture)
bird.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(bird)
}
The destination view controller is a UIViewController or its subclass. Not an SKScene. So I don't think it can be converted to GameScene. GameScene is usually a property of a UIViewController that holds it. In my project the UIViewController is called GameViewController. GameViewController has a property called gameScene
So the code should be
if let gameViewController = segue.destinationViewController as? GameViewController
{
gameViewController.imageToPass = pickedImage.image
}
create a property in GameViewController called imageToPass. Set the imageToPass to GameScene.imageToPass in viewDidLoad.
class GameViewController: UIViewController {
var pickedImage : UIImage!
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.imageToPass = pickedImage
skView.presentScene(scene)
}
}
}
You can create a property observer on imageToPass to change it dynamically.
var bird : SKSpriteNode!
var imageToPass : UIImage!
{
didSet
{
createBird()
}
}
func createBird()
{
var birdTexture = SKTexture(image: imageToPass)
bird?.removeFromParent()
bird = SKSpriteNode(texture: birdTexture)
bird.position = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
self.addChild(bird)
}
In the default SpriteKit implementation, the View Controller created is a GameViewController. In Interface Builder, it has for its view an SKView. In viewDidLoad, the key lines are
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as SKView
/* more setup */
skView.presentScene(scene)
}
It looks like you've edited your question to reflect this. I'm not a SpriteKit expert, but if you get imageToPass to the GameViewController, then you should be able to pass it to your GameScene in viewDidLoad before it's presented. Good luck.
Note "GameScene.swift" is just the name of the text file that contains the code, so you wouldn't pass anything to it, the class is created in class GameScene: SKScene { /* class properties and methods */
Original Answer:
Are you sure the destinationViewController is a GameScene? Really sure? Because you're using as, you're force downcasting the segue view controller as a "GameScene".
Take advantage of Swift's safety features:
if var gameViewController = segue.destinationViewController as? GameScene {
gameViewController.imageToPass = pickedImage.image
}
or using optional chaining:
var gameViewController = segue.destinationViewController as? GameScene
gameViewController?.imageToPass = pickedImage.image
The pickedImage won't get through if not a GameScene, regardless, but you won't get a crash.
EDIT: By the way, it really should be GameSceneViewController so its clear everywhere it's a View Controller. If it's not a View Controller, that's a problem and has no chance to work.

Resources