Losing reference to GameViewController from inside SpriteKit when moving between scenes - want to move between SKScene and UITableView - swift

I have a simple (multi scene) spritekit game and would like to implement and display a settings screen using a UITableViewContoller. I am close but keep losing my reference to the games view controller when i change scene, which means i can only segue to the UITableViewController on the first scene when my game starts. I'm hoping someone can help. I setup the UITableView in the storyboard and setup a segue to the tableview from my GameViewController in there.
I have the following set up in my GameViewController file:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene
if let scene = SKScene(fileNamed: "MenuScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
//I'VE ADDED THIS
if let gameScene = scene as? MenuScene {
gameScene.viewController = self
}
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
}
}
func showSettings() {
performSegue(withIdentifier: "openSettings", sender: self)
}
which gives me a reference to the GameViewController from my SKScene. I then load the UITableViewController from my SKScene in spritekit when a user touches an SKLabelNode. A simpler version of my MenuScene code looks something like this:
class MenuScene: SKScene {
var viewController: GameViewController?
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
let touch:UITouch = touches.first!
let positionInScene = touch.location(in: self)
let touchedNode = self.atPoint(positionInScene)
if let name = touchedNode.name
{
let transition:SKTransition = SKTransition.fade(withDuration: 0.75)
var scene:SKScene = SKScene()
if (name == "settingsBTN"){
viewController?.showSettings()
}
else if (name == "randomBTN"){
scene = StarScene(size: self.size)
scene.backgroundColor = SKColor.black
self.view?.presentScene(scene, transition: transition)
}
}
}
}//end of MenuScene
The table view is presented fine when i tap the settingsBTN when first starting the game. however, if i visit another scene in my game (e.g. by pressing randomBTN) and return back to the menu scene i can no longer segue to the Settings view as i have lost the reference to the viewcontroller (it shows as nil) so i can't call the showSettings method in the GameViewController that performs the segue (presumably because i set up the reference to the view controller in the view controller itself).
I can't figure out how to maintain the reference to my GameViewController so i can present the settings screen at any point. Please could someone help.

There are two simple methods to call a GameViewController function from other classes: through a delegate or by making the class instance static.
// DELEGATE
//
// GameViewController.swift
protocol GameView_Delegate
{
func show_ui()
}
class GameViewController: UIViewController, GameView_Delegate
{
override func viewDidLoad()
{
// ...
if let view = self.view as! SKView?
{
if let scene = SKScene(fileNamed: "MenuScene")
{
scene.scaleMode = .aspectFill
// pass GameViewController reference
scene.gv_delegate = self
}
view.presentScene(scene)
}
// ...
}
func show_ui()
{
// show ui
// ...
}
}
// MenuScene.swift
class MenuScene: SKScene
{
var gv_delegate : GameView_Delegate!
func settings()
{
// call function show_ui from GameViewController
gv_delegate.show_ui()
}
func game_scene()
{
let transition = SKTransition.fade(with: UIColor.white, duration: 1.0)
let next_scene = GameScene()
next_scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
next_scene.scaleMode = self.scaleMode
next_scene.size = self.size
// before moving to next scene, we must pass the delegate, so we
// don't lose the connection with the GameViewController
next_scene.gv_delegate = gv_delegate
self.view?.presentScene(next_scene, transition: transition)
}
}
// GameScene.swift
class GameScene: SKScene
{
var gv_delegate : GameView_Delegate!
func settings()
{
// call function show_ui from GameViewController
gv_delegate.show_ui()
}
func main_scene()
{
let transition = SKTransition.fade(with: UIColor.white, duration: 1.0)
let next_scene = MainScene()
next_scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
next_scene.scaleMode = self.scaleMode
next_scene.size = self.size
// before moving to next scene, we must pass the delegate, so we
// don't lose the connection with the GameViewController
next_scene.gv_delegate = gv_delegate
self.view?.presentScene(next_scene, transition: transition)
}
}
When you enter a new scene, for example GameScene, the old scene MainScene in our case is deallocated (all variables emptied, all references destroyed) and a new GameScene instance is created and displayed on screen. If afterwards you want to exit that scene and enter MainScene, a new instance of that class will be created with the GameViewController reference empty. We should reference it again or pass the reference before exiting the scene (as in my example).
// STATIC
//
// GameViewController.swift
class GameViewController: UIViewController
{
static var shared : GameViewController!
override func viewDidLoad()
{
super.viewDidLoad()
GameViewController.shared = self
// ...
}
func show_ui()
{
// show ui
// ...
}
}
// MenuScene.swift
class MenuScene: SKScene
{
func settings()
{
// call function show_ui from GameViewController
GameViewController.shared.show_ui()
}
}
Read pros/cons of each method and chose the one that suits your app the best. Also, avoid using UIKit inside SpriteKit.

Related

How do I delegate from one Scene another in SpriteKit?

I need some help setting up delegation in SpriteKit. the function in second scene is not called.
But I just cannot figure out what I am doing wrong.
Many thanks in advance
protocol receivingStringDelegate:class {
func didReceiveString(message:String)
}
class GameScene: SKScene {
weak var gamescene_transmissiondelegate: receivingStringDelegate?
private func sendString(message:String){
gamescene_transmissiondelegate?. didReceiveString(message)
}
}
class SecondScene: SKScene, receivingStringDelegate {
// here I get stuck, what to do?
var gameScene : GameScene = GameScene()
override func didMove(to view: SKView) {
gameScene.gamescene_transmissiondelegate = self
}
func didReceiveString(message:String) {
print ("hi there", message)
}
}
I woudld do it with scene properties vs a global class
class ConnectionScene: SKScene {
private var player1pos: CGPoint = CGPoint.zero
private var player2pos: CGPoint = CGPoint.zero
///some code in this scene sets player positions
func startMultiplayerGame() {
//this assumes your scene is setup in the editor not programmatically
if let gameScene = GameScene(fileNamed: "GameScene") {
gameScene.scaleMode = .aspectFill
gameScene.player1pos = player1pos
gameScene.player2pos = player2pos
self.view?.presentScene(multiplayerScene, transition: SKTransition.reveal(with: .down, duration: 1.0))
}
}
}
class GameScene: SKScene {
var player1pos: CGPoint = CGPoint.zero
var player2pos: CGPoint = CGPoint.zero
override func sceneDidLoad() {
//sceneDidLoad happens first
super.sceneDidLoad()
//do setup that doesn't require passed in variables in here
//this isn't set from connectScene yet so won't have the values
print("player1pos \(player1pos)")
print("player2pos \(player2pos)")
}
override func didMove(to view: SKView) {
//didMove happens last and already has the variables assigned
//now do setup that requires these variables
//this now has value from connectScene
print("player1pos \(player1pos)")
print("player2pos \(player2pos)")
}
}

SpriteKit presentScene not working with transition, only without

So I have my SKView as an IBOutlet and a delegate that allows scenes to change the current scene as follows:
// this code is inside the view controller
func changeScene(to sceneType: GameScene.Type) {
let sceneName = NSStringFromClass(sceneType).components(separatedBy: ".").last!
let scene = sceneType.init(fileNamed: sceneName)!
scene.scaleMode = .aspectFill
scene.dialogueDelegate = self.dialogueView
scene.sceneDelegate = self
self.hudScene.hudDelegate = scene
self.actionButtonScene.actionButtonDelegate = scene
self.gameView.presentScene(scene, transition: SKTransition.fade(withDuration: 2))
}
I am calling this function once when the game starts (and it works) and once when the player falls out of the scene:
// this code is inside the scene
override func update(_ currentTime: TimeInterval) {
if self.player.position.y < -self.size.height / 2 {
self.player.removeFromParent()
self.sceneDelegate?.changeScene(to: Self.self)
}
}
But the second time it does nothing. The code gets executed, but the scene is not presented. Removing the SKTransition seems to solve the problem, but why is the transition an issue here?

How do I use the same instance in a delegate protocol pattern?

I recently asked a question on how to use a protocol delegate pattern here: How do you use the Delegate Protocol Pattern with SpriteKit?
And while I got an answer that got me a long way, I don't get it working all the way, and I think it has to do with the fact that I don't (and can't I think) use the same instance when changing SKViews. Let me explain.
I have one UIViewController and two SKScenes. The UIViewController present the two scenes by rotating the device; landscape load SKScene 1 and portrait load SKScene 2 like this:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print("TRIGGERED")
if UIDevice.current.orientation.isLandscape {
print("Landscape")
presentView(name: "GameScene")
} else {
print("Portrait")
presentView(name: "GameScene2")
}
super.viewWillTransition(to: size, with: coordinator)
}
func presentView(name: String) {
if let view = self.view as! SKView? {
if let scene = SKScene(fileNamed: name) {
scene.scaleMode = .aspectFill
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
and in SKScene 1:
protocol MyProtocol {
func myProtocolFunc(someString: String)
}
class GameScene: SKScene{
var myDelegate: MyProtocol!
and in SKScene 2:
class GameScene2: MyProtocol {
private var label: SKLabelNode?
func myProtocolFunc(someString: String) {
label = SKLabelNode(fontNamed: "Chalkduster")
label!.text = someString
label!.position = CGPoint(x: 0, y: 0)
addChild(label!)
}
The SKlabel isn't updated with the delegate value however. An like I said I think this is because I create a new instance of scene when loading SKScene 2. And that instance don't have the delegate. I'm not sure how to work around this though since I need to have a new instance of SKScene to load the other scene.
It might be worth for you to read into the coordinator pattern https://www.hackingwithswift.com/articles/71/how-to-use-the-coordinator-pattern-in-ios-apps

Changing the initial game scene in Swift

I want to change the initial scene being presented to be another class other than the default GameScene class. From reading other questions, I understand I must change this part from the GameViewController:
if let scene = SKScene(fileNamed: "GameScene") {
print(scene)
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
So within the GameScene.swift file I am creating a new class:
class MainMenu : SKScene {
override func didMove(to view: SKView) {
print("At least it ran")
self.scene?.view?.presentScene(GameScene())
}
}
However, when I change the scene to:
if let scene = SKScene(fileNamed: "MainMenu")
When I run the project, it gets stuck, but when I run it with the string "GameScene" then it works perfectly. I am doing something wrong loading the MainMenu?
In my opinion you should separate your scenes into their own files...
Do you have an corresponding SKS file for MenuScene? You need to create one if you are trying to load it with fileNamed:
or -
Use this code to load a SKScene file that is created in code only and not in the Scene editor
if let skView = self.view as? SKView {
if skView.scene == nil {
let scene = MenuScene(size: skView.bounds.size)
scene.scaleMode = .aspectFill
skView.presentScene(scene)
}
}
and then in your MenuScene file you will need an init func
init(size: CGSize) {
super.init(size: size)
name = "menuScene"
}

SpriteKit Scene order

I'm creating a game using Sprite Kit. I have my GameScene and GameOverScenenow I want to create a MenuScene Where do I control the order of the MenuScene so that it shows up after LaunchScene
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile("MenuScene") as? GameScene {
// Configure the view.
let skView = self.view as! SKView
skView.ignoresSiblingOrder = true
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
Edit tried putting ("MenuScene") as the scene but when I run the game that scene won't show
Ok so I found out that I was one the right path. I just needed to change this line. Instead of this
if let scene = GameScene.unarchiveFromFile("MenuScene") as? GameScene {
add this
if let scene = MenuScene.unarchiveFromFile("MenuScene") as? MenuScene {