How do I call a function from another scene? - swift

My game unlocks a level every-time they beat a level. So the problem im having is when the user beats level one I want it to show a button of level 2 thats in my GameScene. When I call the function unlockLevelTwo() in my Level1.swift file it doesn't show up in my GameScene.swift file. What am I doing wrong?
//GameScene.Swift
func unlockLevelTwo() {
let fadeIn = SKAction.fadeInWithDuration(0.5)
levelTwo.position = CGPointMake(self.size.width / 2.0, self.size.height / 2.2)
levelTwo.zPosition = 20
levelTwo.setScale(0.8)
levelTwo.alpha = 0
levelTwo.runAction(fadeIn)
levelTwo.name = "leveltwo"
addChild(levelTwo)
}
//Level1.swift
if firstBody.categoryBitMask == HeroCategory && sixthBody.categoryBitMask == GoldKeyCategory{
//calling this function from my gameScene to unlock levelTwo button.
var gameScene = GameScene()
gameScene.unlockLevelTwo()
}

This function:
if firstBody.categoryBitMask == HeroCategory && sixthBody.categoryBitMask == GoldKeyCategory{
//calling this function from my gameScene to unlock levelTwo button.
var gameScene = GameScene()
gameScene.unlockLevelTwo()
}
is creating a new GameScene object and adding the childNode "levelTwo" to that gamescene. Instead, you need to call unlockLevelTwo() on the actual GameScene that is currently presented to the user.
I imagine somewhere in Level1.Swift there is a reference to the current GameScene (the one the user is interacting with)? Call the function on that one.
EDIT:
In essence, what you must do is keep a reference to your original GameScene object somewhere in your code, which I will henceforth refer to it as MyScene. That way, when in Level1.swift, you can reference MyScene and add buttons, levels, whatever you like to it without creating a new one like you did here:
var gameScene = GameScene()
gameScene.unlockLevelTwo()
Instead, you would just call
MyScene.unlockLevelTwo().
If Level1 is a view of some sort, or an object that gets created, in the init function you could pass in your GameScene object and set it like so
class Level1 : (Type) {
var myScene: GameScene!
init(myScene: GameScene) {
self.myScene = myScene)
}
}
Something like that, hope it helps!

Related

Use multiple classes to control a single SKScene [duplicate]

I'm trying to learn how to make a GameManager type class, and making individual classes for each of my GameScenes... probably the wrong thing to do, but for the sake of this question, please accept this as the way to do things.
My GameManager looks like this, having a reference to each of the scenes, that's static:
import SpriteKit
class GM {
static let scene2 = SecondScene()
static let scene3 = ThirdScene()
static let home = SKScene(fileNamed: "GameScene")
}
How do I create a SKScene programmatically, without size info, since they're in a subclass of SKScene and don't have any idea what the view size is, and I don't want them to need worry about this:
I'm doing this, but getting a EXC_BAD_Access at convenience override init()
class SecondScene: SKScene {
override init(size: CGSize){
super.init(size: size)
}
convenience override init(){
self.init()
self.backgroundColor = SKColor.red
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
}
}
As I mentioned your question is a bit vague but lets do some examples of what a GameManager class can be.
Before I start lets differentiate between calling this
let scene = StartScene(size: ...)
and this
let scene = SKScene(fileNamed: "StartScene")
The 1st method, with size, is when you create your scenes all in code and you are not using the xCode visual level editor.
The 2nd method is when you are using the Xcode level editor, so you would need to create a StartScene.sks file. Its that .sks file that it looks for in fileNamed.
Now for some game manager example, lets first imagine we have 3 SKScenes.
class StartScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameScene: SKScene {
override func didMove(to view: SKView) { ... }
}
class GameOverScene: SKScene {
override func didMove(to view: SKView) { ... }
}
Lets say you want to transition from StartScene to GameScene, you would add this code in your StartScene at the correct spot e.g when the play button is pressed. Thats the simplest way to move from one SKScene to the next, directly from the SKScene itself.
// Code only, no xCode level editor
let gameScene = GameScene(size: CGSize(...))
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
// With xCode level editor (returns an optional so needs if let
// This will need the GameScene.sks file with the correct custom class set up in the inspector
// Returns optional
if let gameScene = SKScene(fileNamed: "GameScene") {
let transition = SKTransition...
gameScene.scaleMode = .aspectFill
view?.presentScene(gameScene, transition: transition)
}
Now for some actual examples of GameManagers, Im sure you know about some of them already.
EXAMPLE 1
Lets say we want a scene loading manager. You approach with static methods will not work because a new instance of SKScene needs be created when you transition to one, otherwise stuff like enemies etc will not reset. Your approach with static methods means you would use the same instance every time and that is no good.
I personally use a protocol extension for this.
Create a new .swift file and call it SceneLoaderManager or something and add this code
enum SceneIdentifier: String {
case start = "StartScene"
case game = "GameScene"
case gameOver = "GameOverScene"
}
private let sceneSize = CGSize(width: ..., height: ...)
protocol SceneManager { }
extension SceneManager where Self: SKScene {
// No xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
let scene: SKScene
switch identifier {
case .start:
scene = StartScene(size: sceneSize)
case .game:
scene = GameScene(size: sceneSize)
case .gameOver:
scene = GameOverScene(size: sceneSize)
}
let transition = SKTransition...\
scene.scaleMode = .aspectFill
view?.presentScene(scene, transition: transition)
}
// With xCode level editor
func loadScene(withIdentifier identifier: SceneIdentifier) {
guard let scene = SKScene(fileNamed: identifier.rawValue) else { return }
scene.scaleMode = .aspectFill
let transition = SKTransition...
view?.presentScene(scene, transition: transition)
}
}
Now in the 3 scenes conform to the protocol
class StartScene: SKScene, SceneManager { ... }
and call the load method like so, using 1 of the 3 enum cases as the scene identifier.
loadScene(withIdentifier: .game)
EXAMPLE 2
Lets make a game manager class for game data using the Singleton approach.
class GameData {
static let shared = GameData()
private init() { } // Private singleton init
var highscore = 0
func updateHighscore(forScore score: Int) {
guard score > highscore else { return }
highscore = score
save()
}
func save() {
// Some code to save the highscore property e.g UserDefaults or by archiving the whole GameData class
}
}
Now anywhere in your project you can say
GameData.shared.updateHighscore(forScore: SOMESCORE)
You tend to use Singleton for things where you only need 1 instance of the class. A good usage example for Singleton classes would be things such as helper classes for Game Center, InAppPurchases, GameData etc
EXAMPLE 3
Generic helper for storing some values you might need across all scenes. This uses static method approach similar to what you were trying to do. I like to use this for things such as game settings, to have them in a nice centralised spot.
class GameHelper {
static let enemySpawnTime: TimeInterval = 5
static let enemyBossHealth = 5
static let playerSpeed = ...
}
Use them like so in your scenes
... = GameHelper.playerSpeed
EXAMPLE 4
A class to manage SKSpriteNodes e.g enemies
class Enemy: SKSpriteNode {
var health = 5
init(imageNamed: String) {
let texture = SKTexture(imageNamed: imageNamed)
super.init(texture: texture, color: SKColor.clear, size: texture.size())
}
func reduceHealth(by amount: Int) {
health -= amount
}
}
Than in your scene you can create enemies using this helper class and call the methods and properties on it. This way you can add 10 enemies easily and individually manage their health etc. e.g
let enemy1 = Enemy(imageNamed: "Enemy1")
let enemy2 = Enemy(imageNamed: "Enemy2")
enemy1.reduceHealth(by: 3)
enemy2.reduceHealth(by: 1)
Its a massive answer but I hope this helps.

swift function to show the score in the Game Center

I am developing a game with five scenes (SKSecne) in swift. I am using the following function to show the score in the Game Center at the end of each scene. Currently I have to copy the function to all the scene files.
How can I modify the function so I can call it from all the scene files without duplicating it?
func showLeader() {
let viewControler = self.view?.window?.rootViewController
let gameCenter = GKGameCenterViewController()
gameCenter.gameCenterDelegate = self
viewControler?.presentViewController(gameCenter, animated: true, completion: nil) }
One solution is just create a subclass of SKScene and use it like parent for others five scenes.
class BasicScene: SKScene {
func showLeader() {}
}
class Scene1: BasicScene {
// call showLeader() when needed
}

Keeping the game paused after app become active?

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.

How can I make images that act as buttons in Swift?

I have the code for one button on my scene, but I tried using that same code to make another button in the same scene, and it didn't work. Here's the code I have so far:
for touch:AnyObject in touches {
let location = touch.locationInNode(self)
if playGameButton.containsPoint(location) {
let newScene = GameplayScene(size: self.size)
newScene.scaleMode = scaleMode
self.view?.presentScene(newScene)
}
}
Again, that's only one button. When I use that same bit of code for another image, it doesn't work.
A better way to approach this problem would be to use an SKSpriteNode object as your button and set its name property to easily distinguish between multiple buttons. For example, if you have an SKSpriteNode object button1:
button1.name = "first"
Then in your touch method to check which was touched, you can use the name property:
SKNode node = self.nodeAtPoint(location)
if node.name == "first" {
// do something
}

SpriteKit scene transition good practices

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)]))