SpriteKit Scene referenced with SKReferenceNode does not load custom class - ios

I have a GameScene.sks a custom class GameScene.swift which is connected via the Custom Class Inspector (on the right in Xcode). This is working fine.
Now in the GameScene.swift where I want to reference another scene that I want to reuse in many scenes in the future (oversimplified code):
class GameScene: SKScene {
// ...
override func didMove(to view: SKView) {
//...
guard let gameMenuPath = Bundle.main.path(
forResource: "GameMenu", ofType: "sks") else { return }
let url = NSURL(fileURLWithPath: gameMenuPath) as URL
let gameMenu = SKReferenceNode(url: url)
addChild(gameMenu)
//...
where I am loading a reusable GameMenu.sks.
Everything works fine till here. Now the custom class issue for the GameMenu.sks (the scene to be reused). For the GameMenu.sks I also have a GameMenu.swift which is referenced in the custom class inspector as custom class. So exactly as for the GameScene. The problem is that it seems like the GameMenu.swift is not found/loaded. I get no error - so it looks like I misspelled the custom class - but I checkt it many times and also cleared the Xcode cache.
Any ideas how to set custom class for a scene (.sks) that is referenced in another scene?
I would think this is something that is done for reusable scene parts suche as the GameMenu in my case that is visible in every GameScene.
Thank you for any hints.

Is it necessary to add GameMenu in GameScene via code? You can also simply use the inspector. In your GameScene.sks drop the 'Reference' (SKReferenceNode) object. Select it from the hierarchy and select GameMenu as in the 'reference' dropdown.

SKReferenceNodes behave as nodes, not as scenes, which may cause confusion since you are creating a scene file.
This means none of the functions that a view calls on a scene will be called.
If you need some of these functions to call, you need to do it manually.
Look at this example to get your didMoveToView to fire:
override func didMove(to view: SKView) {
let gameMenu = SKReferenceNode(fileNamed:"GameMenu") as? SKScene // (or your custom class)
addChild(gameMenu)
gameMenu.didMove(to:view)
}

Related

SKScene not loaded when add GKComponent to GameScene.sks

I am currently creating a 2D game with Swift SpriteKit and GameplayKit.
I have a GameScene.sks file and a GameScene.swift file that is of type SKScene.
Now I wanted to add a Component of type GKComponent. I created my component and added it to one of my SKSpriteNodes in GameScene.sks
However after adding the Component to the SpriteNode in the Scene Editors Inspector the Scene is no longer loaded and the app shows only a grey screen.
Is this a known issue in XCode?
Is there a way of bypassing this issue?
I have not uploaded any example code. The issue is reproducable by any new XCode Project (ios / Game) that supports SpriteKit and GameplayKit.
Just add a class that is derived from GKComponent and add the class to the given helloLabel in the Scene Editor.
This is how the GameScene is loaded:
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GKScene(fileNamed: "GameScene") {
if let sceneNode = scene.rootNode as! GameScene? {
// This gets only executed when I remove the Component from the SKSpriteNode in the Scene Editor.
}
}
...

Nothing Shows up on scene in SpriteKit

I wanted to create a game in Xcode 7.2. I wanted to create a info screen which will have some labels inside. But nothing shows up on the newly created scene. Whatever I put on the new scene, like images, sprites, labels. Nothing appears on the screen when I run it. But the code I wrote to create the label works perfectly fine in the "GameScene" which the game initially contains.
The code that I put in the InfoScene is the following:
import SpriteKit
class InfoScene: SKScene {
override func didMoveToView(view: SKView) {
let thanksLabel = SKLabelNode(fontNamed:"Arial")
thanksLabel.text = "Thank you for Playing!"
thanksLabel.fontSize = 45
thanksLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))
self.addChild(thanksLabel)
}
}
Nothing seems wrong to me. If I put the above code in the GameScene which the template originally has, them the label will appear. Also, if I add a sprite on the scene, it doesn't show up as well. This really confuses me.
I think you've simply forgot to put your code to an instance method like:
Swift 2.x:
override func didMoveToView(view: SKView) {
// put your stuff here
}
Swift 3.x:
override func didMove(to view: SKView){
// put your stuff here
}
didMove is called immediately after a scene is presented by a view. According to the actual documentation you might use this method to create the scene’s contents.
Update: (after your corrections and comment):
I think InfoScene is not launched for some reason.
Make sure your InfoScene is launched using breakpoints inside didMoveToView or simply add this line between your lines:
print("∙ \(type(of: self))")
Are you sure you are using your InfoScene class? How are you making this instance? Through an SKS file? if so, you need to change the Custom class field to point to your InfoScene, by default it will go to SKScene

Swift: How to handle view controllers for my game

i have a general question about view controllers and how to handle them in a clean way when i develop a SpriteKit based game.
What i did so far:
Use storyboard only for defining view controllers
SKScene's are presented in each view controller (Home, LevelSelection, Game) by presentScene
in each view controller i call performSegueWithIdentifier with the identifier i defined in the storyboard between the view controllers
all the content i show programmatically using SKSpritenode etc. on the SKScene's
on the storyboard i only have view controllers with segue relations and identifiers defined
all the stuff i do in viewDidDisappear is because it seems to be the only way to get my SKScene deinited correctly
My problems are:
everytime i segue to another view, my memory raises, because the view controller is re-initialized, the old one keeps staying in the stack
it is not clear for me how to handle the segue's between the view controllers, on some tutorial pages i see people using the navigation controller, others are using strong references of some view controllers and using the singleton pattern for the view controller in order to decide either to init the view controller or just show it
my view controllers are not deiniting, i understand my home view can't because it is the initial one, but since ios is reiniting it anyways, why then not unloading it?
What is the correct way for a Swift based game using SpriteKit to handle the view controller? Below you can see my initial view controller (Home) showing an SKScene with a simple play button which calls the play() function to segue to the levelselection
import UIKit
import SpriteKit
class Home : UIViewController {
private var scene : HomeScene!
override func viewDidLoad() {
print(self)
super.viewDidLoad()
self.scene = HomeScene(size: view.bounds.size)
self.scene.scaleMode = .ResizeFill
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(play), name: Constants.Events.Home.play, object: nil)
skView.presentScene(self.scene)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
let v = view as! SKView
self.scene.dispose()
v.presentScene(nil)
NSNotificationCenter.defaultCenter().removeObserver(self)
self.scene = nil
self.view = nil
print("home did disappear")
}
func play() {
self.performSegueWithIdentifier("home_to_levelselection", sender: nil)
}
deinit {
print("Home_VC deinit")
}
}
Your way seems very complicated to essentially present 3 scenes. Its not what you are supposed to do for SpriteKit games, you only really need 1 view controller (GameViewController).
Load your first scene from GameViewController (e.g HomeScene) and nothing else.
Create your playButton and other UI directly in HomeScene. Use SpriteKit APIs for your UI (SKLabelNodes, SKNodes, SKSpriteNodes etc).
You should never really use UIKit (UIButtons, UILabels) in SpriteKit. There are some exceptions to this, like maybe using UICollectionViews for massive level select menus, but basic UI should be done with SpriteKit APIs.
There is plenty tutorials to google on how to create sprite kit buttons, how to use SKLabelNodes etc. Xcode has a SpriteKit level editor so you can do all that visually similar to storyboards.
Than from HomeScene transition to the LevelSelect Scene and than to the GameScene and vice versa. Its super easy to do.
/// Home Scene
class HomeScene: SKScene {
...
func loadLevelSelectScene() {
// Way 1
// code only, no XCode/SpriteKit visual level editor used
let scene = LevelSelectScene(size: self.size) // same size as current scene
// Way 2
// with xCode/SpriteKit visual level editor
// fileNamed is the LevelSelectScene.sks you need to create that goes with your LevelSelectScene class.
guard let scene = LevelSelectScene(fileNamed: "LevelSelectScene") else { return }
let transition = SKTransition.SomeTransitionYouLike
view?.presentScene(scene, withTransition: transition)
}
}
/// Level Select Scene
class LevelSelectScene: SKScene {
....
func loadGameScene() {
// Way 1
// code only, no XCode/SpriteKit visual level editor used
let scene = GameScene(size: self.size) // same size as current scene
// Way 2
// with xCode/SpriteKit visual level editor
// fileNamed is the GameScene.sks you need to create that goes with your GameScene class.
guard let scene = GameScene(fileNamed: "GameScene") else { return }
let transition = SKTransition.SomeTransitionYouLike
view?.presentScene(scene, withTransition: transition)
}
}
/// Game Scene
class GameScene: SKScene {
....
}
I strongly recommend you scratch your storyboard and ViewController approach, and just use different SKScenes and 1 GameViewController.
Hope this helps
Go to the segues and use Show Detail Segues anywhere that you don't want the previous view controller to be kept in the stack. Keep in mind that you will have to reinitialize everything appropriately in those view controllers whenever you do return to them.
If you pay attention, viewDidAppear loads every time that you see the view appear, while with your current setup, viewDidLoad would only be called initially and if you returned to the viewController, only viewDidAppear would be called.
When you use a segue to transition out of a viewController, prepareForSegue is called, but deinit() is only called when you use a show detail segue (or custom segue with those specific properties about it), because the view, like you said, is loaded into memory so it can be retrieved easier.

How to create more SKScene in addition to GameScene

I tried to create a new subclass of SKScene "MainScene" like the one apple created the GameScene.
I want to create more scene in addition to my "GameScene" but its not working.
Below is my subclass code.
MainScene :
import SpriteKit
#if !os(iOS)
import AppKit
#endif
class MainScene : SKScene {
override func didMoveToView(view: SKView) {
backgroundColor = UIColor.blueColor()
}
}
MainSceneViewController :
import UIKit
import SpriteKit
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = MainScene(fileNamed:"MainScene") {
// 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
skView.showsPhysics = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
Error : "Could not cast value of type 'UIView' (0x1097f2b20) to 'SKView' (0x108a4cad0)."
When creating a new scene, you need to actually create a new scene file, not just the code behind file.
To create new scene files, go to file, New, File, then in your sections go to iOS, SpriteKit Scene. This will create the sks file you need.
edit:
In the case you are getting the error Could not cast value of type 'UIView' to 'SKView', this is due to the fact that in your story board, your ViewControllers main view does not have a custom class of SKView. In your storyboard file, open up your view controller, click the view for this controller, make sure the right panel is visible, and select the identity inspector tab (It is probably the 3rd from left, looks like a flag or an envelope with a window). Under Custom Class, look for the class text box, and put in SKView
To create more SKScene in addition to GameScene
Go to New File
Select Coca Touch Class
Click on Next
SubClass : Type Manually SkScene
create
import SpriteKit

How do you call a function defined in another class?

I've created a SpriteKit project and defined a function in the GameScene.swift file. Said function creates a SKSpriteNode and has no problem running when called within it's class.
func generateShip() {
self.removeAllChildren()
let SKSprite1 = SKSpriteNode(imageNamed: "Spaceship")
SKSprite1.size = CGSize(width: CGFloat(arc4random_uniform(UInt32(200))), height: CGFloat(arc4random_uniform(UInt32(200))))
SKSprite1.position = CGPoint(x: self.size.width / 2, y: self.size.height / 2)
self.addChild(SKSprite1)
}
//if I called it here with touchesBegan, it worked.
Now, in my ViewController, I created a simple button and connected it to its swift file. I did a few things before the following code, but this is the part I'm having trouble with.
let gameScene = GameScene()
#IBAction func newShip(sender: AnyObject) {
gameScene.generateShip() // am I doing this wrong?
}
When I run the app, and click the button, nothing happens. What do I need to change/ do to make function.... well ... function?
Links: Full Code , Iphone
The problem is in this code:
let gameScene = GameScene()
override func viewDidLoad() {
super.viewDidLoad()
let skView = self.view as! SKView
skView.presentScene(GameScene(fileNamed: "GameScene"))
}
Your gameScene property isn't the same as the scene you presented. Those are two separate objects. You need to do two things.
Change your gameScene declaration to be like the new one you created in your presenting code. Like so:
let gameScene = GameScene(fileNamed: "GameScene")
In viewDidLoad, present that GameScene object instead of making a new one, like this:
skView.presentScene(gameScene)
This way you create one GameScene object, present it, and call generateShip() on the same object.
In theory Swift should be able to link all the files automatically that are inside of your project, so your code should be able to work on its own. However, the only issue that might arise is because you are now relying on the StoryBoard as well, so you have to make sure thats linked correctly.
Make sure that its linked correctly.
Check out this question for more information on linking with Swift.
EDIT: Also, look at your code. You are calling the function for the space ship on the class itself and not on an instance of it, so thats why its probably not working. You need to first initialize a gameScene and then use that scene to call a method. I think it might be a lot more work so my advice would be to keep everything inside of your gameScene class and create a spriteNode button inside of there.
Your code seems fine to me.
If there was any 'access' problem with code from an unknown class / module etc. you'd get a compiler warning about it. This seems not to be the case.
My guess: your button is not wired up correctly!
Doublecheck that the #IBAction func newShip(sender: AnyObject) really gets called when you press the button: set a breakpoint inside of the newShip function.
Edit: And if it does stop there: step into the function using the debugger.
You are incorrectly using two different instances of GameScene in your view controller! One is added to the view hierarchy, and the OTHER instance is called when the button is tapped.
This can not be seen from the code in the original question, but from the code you provided in a comment: screenshot of full code
In your view controller your are first creating a GameScene with:
let gameScene = GameScene()
but then you are creating and adding a second instance of GameScene to the view hierarchy with:
skView.presentScene(GameScene(fileNamed:"GameScene"))
and your button action calls the generateShip() method on the first object stored in gameScene, that has not been presented by the SKView.
So the solution is to change the above code to:
let gameScene = GameScene(fileNamed:"GameScene"))
and later:
skView.presentScene(gameScene)

Resources