Seque from a storyboard image tap into a SpriteKitScene - ios

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.

Related

Calling a func of GameViewController from GameScene

I want to call a func of GameViewController from the GameScene.
-> If the game Ends I want to call GameViewController().GameOver()
I tried now a lot of different things like this one: LINK (I tried every answer more than once, still not working)
But doesn't matter what I tried it doesn't even call the func.
Hope anyone can help me with this.
CODE:
GameViewController:
class GameViewController: UIViewController {
#IBOutlet weak var Button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = GameScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
if UIDevice().userInterfaceIdiom == .phone {
scene.scaleMode = .aspectFill
}else{
scene.scaleMode = .aspectFit
}
// Present the scene
view.presentScene(scene)
skView = view
}
view.ignoresSiblingOrder = true
view.showsFPS = false
view.showsNodeCount = false
}
...
#IBAction func Button(_ sender: Any) {
animation()
if let gameScene = skView.scene as? GameScene { // check to see if the current scene is the game scene
gameScene.start()
}
}
func animation(){
UIButton.animate(withDuration: 1, animations: {
self.Button?.alpha = 0
})
}
func GameOver(){
UIButton.animate(withDuration: 1, animations: {
self.Button?.alpha = 1
})
}
}
GameScene:
class GameScene: SKScene, SKPhysicsContactDelegate {
...
func torpedoDidCollideWithAlien (torpedoNode:SKSpriteNode, alienNode:SKSpriteNode) {
GameViewController().GameOver()
removeAllActions()
removeAllChildren()
}
}
Option 1:
You should pass GameViewController instance when you are calling GameScene from GameViewController at the first time, Then just save this instance in some var gameVC: GameViewController! and initialize it like you do in prepare for segue method.
Then you'll be able to call gameVC.GameOver()
Option 2:
Put this in GameScene when you want to call GameOver():
if let controller = self.view?.window?.rootViewController as? GameViewController {
controller.GameOver()
}

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

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
}
}

Assembly crash report when casting UIView to SKView

I am making a simple game in SpriteKit, and I am trying to make a main menu using UIKit. I created a View controller for the main menu and a separate one for the game. Whenever the play button is pressed, it switches to the Game view controller, where that then presents the first scene. However, whenever it seems that whenever it attempts to cast the game view controller's UIView to and SKView to be able to present the scene, something fails, and crash report is assembly, which I have no idea how to debug. I believe it is instantiating the game view controller twice, because when I put a println() in the viewWillLayoutSubviews() method, it prints twice. Another post on this said the type was already of SKView during the cast, causing it to fail, in which case that might mean when it triggers twice, it casts once, then tries to cast again and fails. Thanks in advance!
Here is the swift code
import UIKit
import SpriteKit
class MenuViewController: UIViewController {
let background = UIImageView(image: UIImage(named: "background.png"))
let titleImg = UIImageView(image: UIImage(named: "title.png"))
override func viewDidLoad() {
background.frame = self.view.frame
titleImg.frame = CGRectMake(0, 0, self.view.frame.width, 100)
self.view.addSubview(background)
self.view.addSubview(titleImg)
let bp = ButtonPositions(controller: self)
let play = button(bp.playY, named: "playButton.png")
let leader = button(bp.leaderY, named: "leaderButton.png")
let rate = button(bp.rateY, named: "rateButton.png")
play.addTarget(self, action: "playPressed", forControlEvents: UIControlEvents.TouchDown)
leader.addTarget(self, action: "leaderPressed", forControlEvents: UIControlEvents.TouchDown)
rate.addTarget(self, action: "ratePressed", forControlEvents: UIControlEvents.TouchDown)
self.view.addSubview(rate)
self.view.addSubview(leader)
self.view.addSubview(play)
}
func button(y:CGFloat, named name:String!) -> UIButton {
let bp = ButtonPositions(controller: self)
let img = UIImage(named: name)
let b = UIButton(frame: CGRectMake(bp.x, y, self.view.frame.width / 1.5, img.size.height))
b.setImage(img, forState: UIControlState.Normal)
return b
}
func playPressed() {
println("playPressed") //only prints once
changeVC()
}
func changeVC() {
self.presentViewController(GameViewController(), animated: true, completion: nil) //maybe initializing the GameViewController twice for some reason?
}
func leaderPressed() {
}
func ratePressed() {
}
func center() -> CGPoint {
return CGPoint(x: CGRectGetMidX(self.view.frame), y: CGRectGetMidY(self.view.frame))
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
class GameViewController: UIViewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//prepareGame()
println("test") //this prints twice, even through the button press event is only triggered once
}
func prepareGame() {
let skView = self.view as SKView //crashes here when called
let scene = GameScene(size: self.view.bounds.size)
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return Int(UIInterfaceOrientationMask.AllButUpsideDown.toRaw())
} else {
return Int(UIInterfaceOrientationMask.All.toRaw())
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func prefersStatusBarHidden() -> Bool {
return false
}
}
I had the same issue. I created my project using the default Swift Game project for iOS which by default created the project for SceneKit, since I want to use SpriteKit and tried casting my new scene to SKView it produced EXC_BAD_ACCESS at the line were I tried to cast. To fix my problem I had to change the Class in the main storyboard to SKView since it was defaulted to SCNView.
Steps I followed to fix:
Select the main storyboard (in my case it was Main.storyboard)
Select the view from the hierarchy under View Controller
Select the Identity inspector tab (Utilities panel on the right)
Under the Custom Class section change the class from SCNView to SKView
After doing this the issue went away.
This is not the most optimal way, but a temporary workaround was to create a new SKView and add it to the view controller's view. I was trying to avoid this because right at start of the scene the fps is around 40, and i see no reason why it shouldn't be 60 when there is nothing on the screen.
EDIT: adding a counter since the method was being called twice also helped with the fps
private var count = 0
private func prepareGame() {
count++
if count == 2 {
let skView = SKView(frame: self.view.frame)
self.view.addSubview(skView)
let scene = GameScene(size: skView.bounds.size)
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}

Deallocate SKScene after modal transition

I have been creating a game in sprite kit using swift and have encountered a problem. I have two view controllers, each with one scene and one transitions to the other modally. This all works perfectly first time round, but then when i return to the first view controller and then go to the second again, i have using double the memory. This gives me the impression that nothing is being deallocated, but the objects are rather reallocated every time I transition to the scene. I ran the app in instruments and got the same result. In the below image i moved from one scene to the next, and then back to the first one again and yet it appears to reallocate the first scene and yet not clear any memory. As the dealloc method is unused now, i don't see how i can fix this. I will post the code to the first view controller below so you can have a look at it. Thanks a lot.
import UIKit
import SpriteKit
class SelectionViewController: UIViewController {
var selectionScene:SelectionScene?
var currentRocketName = ""
#IBOutlet var playButton: UIButton
override func viewDidLoad() {
super.viewDidLoad()
if let selectionScene = SelectionScene.unarchiveFromFile("SelectionScene") as? SelectionScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.multipleTouchEnabled = false
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
selectionScene.scaleMode = .ResizeFill
selectionScene.viewController = self
skView.presentScene(selectionScene)
NSNotificationCenter.defaultCenter().addObserver(selectionScene, selector: "spinnerChanged", name: "spinnerValueChanged", object: nil)
NSNotificationCenter.defaultCenter().addObserver(selectionScene, selector: "productBought", name: "ProductBought", object: nil);
NSNotificationCenter.defaultCenter().addObserver(selectionScene, selector: "manageErrorInPurchase", name: "ErrorOccured", object: nil)
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func viewDidAppear(animated: Bool) {
}
#IBAction func playButtonPressed(sender: UIButton) {
self.performSegueWithIdentifier("moveToGame", sender: nil)
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return Int(UIInterfaceOrientationMask.AllButUpsideDown.toRaw())
} else {
return Int(UIInterfaceOrientationMask.All.toRaw())
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override func viewDidUnload() {
NSNotificationCenter.defaultCenter().removeObserver(selectionScene)
}
override func viewDidDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(selectionScene)
}
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "moveToGame" {
let destController = segue.destinationViewController as GameViewController
destController.rocketTexture = SKTexture(imageNamed: self.currentRocketName)
}
}
}
Both selectionScene and currentRocketName are passed to the viewController as soon as they are loaded into the view
I'm not familiar with Swift yet, so I'll give you examples in Objective-C.
Create an IBOutlet for skView. When you are going to present another ViewController, remove skView from it's superview and nil it out:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Need to deallocate GameScene (if game is not paused)
[self.skView removeFromSuperview];
self.skView = nil;
....
}
Don't forget to add skView back to the ViewController's view, when ViewController is getting loaded:
if (!self.skView.window) {
[self.view addSubview:self.skView];
}
To easily check if SKScene was deallocated or not, add this method to it:
- (void)dealloc {
NSLog(#"GAME SCENE DEALLOCATED");
}
I had a similar issue.
Turns out I had created a strong reference by having an SKScene instance as a delegate in another class. After declaring each property of SKScene type or UIView type as weak my issue was resolved:
weak var skScene:SKScene!

Resources