SpriteKit : Finding unexpectedly nil while unwrapping, can't implement multiple menu scenes - ios

I am trying to present a menu with 2 buttons as initial scene: play, credits. When play is clicked, I want to present another menu scene with 4 buttons: tutorial, easy, hard, back.
The strategy is simply to create the buttons as SKSpriteNode objects and handle clicks in touchesBegan function.
In my menuScene.sks file I properly placed and named my nodes. Here, you can check the menuScene.swift file linked to this scene:
import SpriteKit
class menuScene: SKScene {
var playButton:SKSpriteNode!
var creditsButton:SKSpriteNode!
override func didMove(to view: SKView) {
playButton = self.childNode(withName: "playButton") as! SKSpriteNode
creditsButton = self.childNode(withName: "creditsButton") as! SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// transition is defined in Helper.swift
let touch = touches.first
if let location = touch?.location(in: self){
let nodesArray = self.nodes(at: location)
if nodesArray.first?.name == "playButton" {
let nextScene = difScene(size: self.size)
self.view?.presentScene(nextScene, transition: transition)
} else if nodesArray.first?.name == "creditsButton" {
let nextScene = creditsScene(size: self.size)
self.view?.presentScene(nextScene, transition: transition)
}
}
}
}
When I run, menuScene is presented without a problem. However when I touch the play button I get an error and it is from next scene: difScene.
Here you can find difScene.swift:
import SpriteKit
class difScene: SKScene {
var tutButton:SKSpriteNode!
var easyButton:SKSpriteNode!
var hardButton:SKSpriteNode!
var backButton:SKSpriteNode!
override func didMove(to view: SKView) {
tutButton = self.childNode(withName: "tutButton") as! SKSpriteNode // error
easyButton = self.childNode(withName: "easyButton") as! SKSpriteNode
hardButton = self.childNode(withName: "hardButton") as! SKSpriteNode
backButton = self.childNode(withName: "backButton") as! SKSpriteNode
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let transition = SKTransition.push(with: .down, duration: 0.2)
let touch = touches.first
if let location = touch?.location(in: self){
let nodesArray = self.nodes(at: location)
if nodesArray.first?.name == "tutButton" {
let nextScene = GameScene(size: self.size)
self.view?.presentScene(nextScene, transition: transition)
}
if nodesArray.first?.name == "easyButton" {
let nextScene = difScene(size: self.size)
self.view?.presentScene(nextScene, transition: transition)
}
if nodesArray.first?.name == "hardButton" {
let nextScene = difScene(size: self.size)
self.view?.presentScene(nextScene, transition: transition)
}
if nodesArray.first?.name == "backButton" {
let nextScene = menuScene(size: self.size)
self.view?.presentScene(nextScene, transition: transition)
}
}
}
}
In case it may be useful, here is my GameViewController.swift:
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GKScene(fileNamed: "menuScene") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! menuScene? {
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = false
view.showsNodeCount = false
}
}
}
}
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
}
}
The error I get is:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I know this is not a unique error. I have Google'd, watched tutorials and tried to derive a solution from other StackOverflow threads but failed to overcome this.

The error is on this line
tutButton = self.childNode(withName: "tutButton") as! SKSpriteNode // error
because there is no child node with the name "tutButton", so force casting it to an SKSpriteNode causes the error.
When the play button is pressed, you call this code:
let nextScene = difScene(size: self.size)
self.view?.presentScene(nextScene, transition: transition)
This initializes a new difScene and presents it, which in turn calls the line of code above which is crashing.
Do you have a corresponding sks file for the difScene where you add the four buttons you reference in didMove?
If so, you'll need to initialize the difScene with the sks file like you did for your menu scene:
let scene = GKScene(fileNamed: "difScene")
If not, you'll need to create those four button nodes and add them as children to your scene programatically.
Side note: In Swift, the convention is to name types starting with capital letters. Your menuScene should be MenuScene, and your difScene should be DifScene. This will make it much easier for others to read and understand your code.

Related

How to go back to the previous scene after tapping a colour sprite in swift?

So for a school project, I have been tasked with making a 2D game. The game is fine but I'm struggling with how to make a back button (In the middle of the page) so was wondering if there was specific code to make this work. I am using spriteKit so I'm trying to go back to the previous scene after clicking on a colour sprite.
I apologise if this is a stupid question but I am slightly new to Swift.
Kind Regards,
James
Here is an example of how you can create a button using a colored sprite. It shows how you can set up a button to receive touch events and how you can use those touch events to navigate between scenes.
In this example you can navigate forward to new scenes and backwards to previous scenes.
import SpriteKit
class Button: SKSpriteNode {
var tapped: (() -> Void)?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
tapped?()
}
}
class GameScene: SKScene {
var parentScene: SKScene?
var sceneCount = 1
override func didMove(to view: SKView) {
if parentScene != nil {
let backButton = addButton(color: .red, position: CGPoint(x: -200, y: 0))
backButton.tapped = {
if let previousScene = self.parentScene {
view.presentScene(previousScene)
}
}
}
let nextButton = addButton(color: .blue, position: CGPoint(x: 200, y: 0))
nextButton.tapped = {
if let nextScene = SKScene(fileNamed: "GameScene") as? GameScene {
nextScene.scaleMode = self.scaleMode
nextScene.parentScene = self
nextScene.sceneCount = self.sceneCount + 1
view.presentScene(nextScene)
}
}
let label = SKLabelNode(text: "Scene \(sceneCount)")
addChild(label)
}
func addButton(color: SKColor = .white, position: CGPoint = .zero) -> Button {
let button = Button(color: color, size: CGSize(width: 200, height: 200))
button.position = position
button.isUserInteractionEnabled = true
addChild(button)
return button
}
}
Too add a button the simplest way is to detect touches on your sprite(s) in the relevant SKScene.
enum NodeName: String {
case coloredSprite1
case coloredSprite2
}
class GameScene: SKScene {
let coloredSprite = SKSpriteNode(imageNamed: "YourImageName")
/// Scene setup
override func didMove(to view: SKView) {
// set up your colored sprite if necessary
// Give your sprites unique names to identify them
coloredSprite.name = NodeName.coloredSprite1.rawValue // always use enums for things like string identifiers so you avoid typos
}
/// Touches
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let touchedNode = atPoint(location)
// Way 1 by node (probably less preferable)
switch touchedNode {
case coloredSprite:
// do something (e.g call loadScene method)
// see below
default:
break
}
// Way 2 by node name (probably more preferable)
// name is an optional so we have to unwrap it when using it in the switch statement.
// The easiest way is by providing an alternative string, by using the nil coalescing operator (?? "NoNodeNameFound")
switch touchedNode.name ?? "NoNodeNameFound" {
case NodeName.coloredSprite1.rawValue:
// do something (e.g call loadScene method)
// see below
default:
break
}
}
}
// Also call touchesEnded, touchesMoved and touchesCancelled and do necessary stuff
}
For a more reusable solution you ideally want to create a button subclass. There is quite a few tutorials to google on how to do this.
To than transition between SKScenes you can create a loadScene method in each scene and than call them when necessary.
// Start Scene
class StartScene: SKScene {
...
func loadGameScene() {
// If you do everything in code
let gameScene = GameScene(size: self.size)
view?.presentScene(gameScene, transition: ...)
// If you use SpriteKit scene editor
guard let gameScene = SKScene(fileNamed: "GameScene") else { return } // fileNamed is the name you gave the .sks file
view?.presentScene(gameScene, transition: ...)
}
}
// Game scene
class GameScene: SKScene {
....
func loadStartScene() {
// If you do everything in code
let startScene = StartScene(size: self.size)
view?.presentScene(startScene, transition: ...)
// If you use SpriteKit scene editor
guard let startScene = SKScene(fileNamed: "StartScene") else { return } // fileNamed is the name you gave the .sks file
view?.presentScene(startScene, transition: ...)
}
}
Hope this helps

Segue between SKScene and UIViewController

I'm writing simple math game with swift 3 and I have a problem with segues. I have two GameViewController's, oneUIViewController and one NavigationViewController.
I want to make two menus, one with game modes and one with difficulties, I want that the difficulties menu be interactive and have gravity so I decided to make it in SkScene. Everything works fine except segue between SkScene with dark screen and difficulties menu when user lost the game. I write a protocol which works when I'm going from main menu to second menu but when I'm going from SkScene with game it doesn't. I was trying to make a manual segue but protocol doesn't want to execute.
MyGVViewController code:
import UIKit
import SpriteKit
import GameplayKit
enum skad1 {
case game,menu
}
var skad = skad1.gra
class MyGVViewController: UIViewController, GameViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "Menugame") {
let gameScene = scene as! MySKScene
gameScene.gameViewControllerDelegate = self
gameScene.scaleMode = .aspectFill
gameScene.size = view.bounds.size
// Present the scene
view.presentScene(gameScene)
}
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
}
func goback() {
print("goback executed")
let view = self.view as! SKView?
view?.presentScene(nil)
navigationController?.popViewController(animated: true)
}
func goback2() {
print("goback2 executed")
let view = self.view as! SKView?
view?.presentScene(nil)
self.performSegue(withIdentifier: "comeback", sender: nil)
}
}
My SkScene with difficulties menu code:
import GameKit
import SpriteKit
import CoreMotion
protocol GameViewControllerDelegate: class {
func goback()
func goback2()
}
class MySKScene: SKScene {
//Variables
var motionManager: CMMotionManager!
var ball = SKSpriteNode()
var ball1 = SKSpriteNode()
var ball2 = SKSpriteNode()
var ball3 = SKSpriteNode()
var ball4 = SKSpriteNode()
weak var gameViewControllerDelegate: GameViewControllerDelegate?
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
if ball.contains(location){
showgame(zakres: 200)
}else if ball1.contains(location){
showgame(zakres: 100)
}else if ball2.contains(location) {
showgame(zakres: 50)
}else if ball3.contains(location) {
showgame(zakres: 150)
}else if ball4.contains(location) {
if skad == .menu{
print("should execut goback")
self.gameViewControllerDelegate?.goback()
}else{
print("should execut goback2")
self.gameViewControllerDelegate?.goback2()
}
}
}
}
func showgame(zakres:Int) {
if let view = self.view {
range = zakres
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
let gameScene = scene as! GameScene
gameScene.scaleMode = .aspectFill
// Present the scene
gameScene.size = view.bounds.size
view.presentScene(gameScene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override func didMove(to view: SKView) {
ball = self.childNode(withName: "ball") as! SKSpriteNode
ball1 = self.childNode(withName: "ball1") as! SKSpriteNode
ball2 = self.childNode(withName: "ball2") as! SKSpriteNode
ball3 = self.childNode(withName: "ball3") as! SKSpriteNode
ball4 = self.childNode(withName: "ball4") as! SKSpriteNode
motionManager = CMMotionManager()
motionManager.startAccelerometerUpdates()
let border = SKPhysicsBody(edgeLoopFrom: self.frame)
border.friction = 0
border.restitution = 0
self.physicsBody = border
}
override func update(_ currentTime: TimeInterval) {
if let accelerometerData = motionManager.accelerometerData {
physicsWorld.gravity = CGVector(dx: accelerometerData.acceleration.x * 20, dy: accelerometerData.acceleration.y * 35)
}
}
}
PS.
I'm reading what I wrote and I have to correct it, in difficulties menu I have a button which execute protocol that hide SKScene and show main menu. It works only when I'm going from main menu but when segue is from game when user lost, protocol isn't executed

How to create a main menu for a spritekit game made with swift in xcode?

I created a really simple game in xcode using spritekit and swift. I have finished coding the actual game itself. Now I want to create a main menu so that when the game opens, there will be a menu with buttons either going into settings or starting the game. I have no idea how I can do this. Should I use storyboard? And if so, how can i implement it in xcode. Thanks everyone :)
Swift 3.0
import SpriteKit
class MenuScene: SKScene {
var playButton = SKSpriteNode()
let playButtonTex = SKTexture(imageNamed: "play")
override func didMove(to view: SKView) {
playButton = SKSpriteNode(texture: playButtonTex)
playButton.position = CGPoint(x: frame.midX, y: frame.midY)
self.addChild(playButton)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let pos = touch.location(in: self)
let node = self.atPoint(pos)
if node == playButton {
if let view = view {
let transition:SKTransition = SKTransition.fade(withDuration: 1)
let scene:SKScene = GameScene(size: self.size)
self.view?.presentScene(scene, transition: transition)
}
}
}
}
}
Create two new files. One sprite kit scene and one cocoa touch file. Name them the same so for example MenuScene. To create the files you click on the folder in the sidebar of your xcode project. Then to have the MenuScene show when you run your app go into the GameViewController file. There is a line under viewDidLoad. if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene { change this into if let scene = MenuScene.unarchiveFromFile("MenuScene") as? Menu Scene { Then in your MenuScene you have to have a button that leads you to your GameScene
import SpriteKit
class MenuScene: SKScene {
var playButton = SKSpriteNode()
let playButtonTex = SKTexture(imageNamed: "play")
override func didMoveToView(view: SKView) {
playButton = SKSpriteNode(texture: playButtonTex)
playButton.position = CGPointMake(frame.MidX, frame.midY)
self.addChild(playButton)
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
if let touch = touches.first as? UITouch {
let pos = touch.locationInNode(self)
let node = self.nodeAtPoint(pos)
if node == playButton {
if let view = view {
let scene = GameScene.unarchiveFromFile("GameScene") as! GameScene
scene.scaleMode = SKSceneScaleMode.AspectFill
view.presentScene(scene)
}
}
}
}
Ask If you need further explanation.

SpriteKit Transition Won't Work

I'm trying to add a transition to my SpriteKit scene, but it won't work. My second scene is called "myScene." Here's my code:
func didBeginContact(contact: SKPhysicsContact) {
let collision:UInt32 = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask)
if collision == (playerCategory | crateCategory) {
NSLog("Game Over")
self.scene?.view?.paused = true
var myscene = myScene(size: self.size)
var transition = SKTransition.doorsCloseHorizontalWithDuration(0.5)
myscene.scaleMode = SKSceneScaleMode.AspectFill
self.scene!.view?.presentScene(myscene, transition: transition)
}
if (contact.bodyA.categoryBitMask == bubbleCategory) {
let node = contact.bodyB.node
//Other remove routine
node?.removeAllActions()
node?.removeFromParent()
} else if (contact.bodyB.categoryBitMask == bubbleCategory) {
let node = contact.bodyB.node
//Other remove routine
node?.removeAllActions()
node?.removeFromParent()
}
}
What's the problem here?
This will present the scene and it works fine for me every time. This will not return nil for the scene so it will transition smoothly. You can also set ignoreSiblingOrder to whatever you want with this line: skView?.ignoresSiblingOrder = true //or false
Here is my GameScene:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
self.backgroundColor = UIColor.redColor()
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
println("touch occured in gamescene")
let scene:SKScene = myScene()
let skView = self.view as SKView?
skView!.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
scene.size = skView!.bounds.size
skView!.presentScene(scene)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}

Need help instantiating a controller and storyboard scene from controller SWIFT?

I have a controller that is using a Singleton to change SKScenes in a single storyboard scene.
class GameViewController: UIViewController, GKGameCenterControllerDelegate, GADBannerViewDelegate, GADInterstitialDelegate, ADBannerViewDelegate, FBLoginViewDelegate, CLLocationManagerDelegate, MapViewControllerDelegate {
var sharedInstance = Singleton.sharedInstance
var skView:SKView!
var scene:GameScene! = GameScene.unarchiveFromFile("GameScene") as? GameScene
var searchedTypes = ["bakery", "bar", "cafe", "grocery_or_supermarket", "restaurant"]
let locationManager = CLLocationManager()
var adHelper: AdHelper = AdHelper()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
// Configure the view.
skView = self.view as SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* 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, transition: SKTransition.crossFadeWithDuration(kSceneTransitionSpeed))
}
// LOAD MAP SCENE
func showMap(){
let viewController:UIViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("MapController") as UIViewController
self.presentViewController(viewController, animated: false, completion: nil)
}
}
In my game scene I have a button that I am calling to this controller to change to the other scene in the storyboard because I don't have a layout button to use an IBAction
import SpriteKit
class GameScene: SKScene {
var sharedInstance = Singleton.sharedInstance
let playButton = Button(imageNamed:"playButton");
let highscoresButton = Button(imageNamed:"highscoresButton");
let creditsButton = Button(imageNamed:"creditsButton");
let moreGamesButton = Button(imageNamed: "moreGamesButton")
override func didMoveToView(view: SKView) {
// background
let background = SKSpriteNode(imageNamed: "background")
background.position = CGPointMake(self.size.width/2, self.size.height/2)
self.addChild(background)
// main image
let main = SKSpriteNode(imageNamed: "main")
main.position = CGPointMake(self.size.width/2, self.size.height/2)
self.addChild(main)
// buttons
moreGamesButton.position = CGPointMake(self.size.width/2 + playButton.size.width, main.position.y + main.size.height/2 + playButton.size.height)
self.sharedInstance.addChildFadeIn(moreGamesButton, target: self)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self);
if self.nodeAtPoint(location) == self.moreGamesButton {
var gameController: GameViewController=GameViewController()
gameController.showMap()
}
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
When I click this I don't get a defined error just hits a break and it the thread I get "Swift dynamic cast failed".
Can I not call the controller like that within my scene or is is it a problem in instantiating the other controller?

Resources