I'm trying to figure out how to add more scenes in SpriteKit. If I use the line generated from SpriteKit in the GameViewController
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene
This works fine, however if I create a class that's also a of type SKScene and add it the the SKView.
let newscene = GameMenu()
skView.presentScene(newscene)
This will not work. It just displays a washed out gray color with only a fraction of the nodes but nothing actually presented. Is there something going wrong with the way SpriteKit uses unarchiveFromFile to create the scene. I'm also not even using the Storyboard or GameScene.sks. The entire game has been created programatically. One more Issue if I try to create another ViewController for a menu and use presentViewController. When it tries to run I also get an error that says "Could not cast value of type 'UIView' to 'SKView.'" I'm casting it with the same line similar to GameViewController.
let skView = self.view as! SKView
This is the line that it keeps breaking on. If anyone has any insight on how to fix this problem that would be greatly appreciated.
Here is how I normally make a scene:
Go to File -> New -> File...
Click Cocoa Class, click next
Name it whatever you want, make it a subclass of SKScene, choose swift
In the new class, make it look like this:
import SpriteKit
class GameScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
//Other init code
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
//Other functions like override func touchesBegan, update, etc. Whatever you want
}
If you want to display the scene first, GameViewController, in viewDidLoad you would type:
let scene = GameScene(size: skView.bounds.size)
skView.presentScene(scene)
(skView should be made for you already, but if its not , its: let skView = self.view as! SKView, and put that before all the let scene = GameScene... etc and stuff in viewDidLoad.
Then if you wanted to present the scene from a different SKScene, and not from the view controller, then do:
let scene = GameScene(size: self.frame.size)
self.view?.presentScene(scene)
If you're constructing a scene programmatically, don't try to unit hive it from a file (that probably stores a different scene). Use its initializer instead: e.g. GameMenu() or GameMenu(size: whateverSize) or a custom initializer you've defined.
Related
I was trying to solve this problem (TL;DR An overlaid SKScene using the overlaySKScene property in SCNView wasn't causing a redraw when children were added and removed from it) using view.setNeedsDisplay() to force a redraw since the SCNView wasn't doing it automatically.
The problem with using view.setNeedsDisplay() was that the CPU usage was spiking to 50% and I assumed it was because the entire SCNView was having to redraw its contents, which included a 3D SCNScene as well. My solution was to use view.setNeedsDisplay(_: CGRect) to minimise the region that needs to be redrawn. However, to my surprise, no matter what I put as the CGRect value the SCNView refused to render the SKScene contents that had been overlaid on it.
Steps to reproduce issue
Open SceneKit template
From the Main (Base) storyboard, set the "Scene" attribute on the SCNView to be "art.scnassets/ship.scn" or whatever the path is
Delete all boilerplate code and just leave
class CustomSKScene: SKScene {
override func didMove(to view: SKView) {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(userTapped(_:)))
view.addGestureRecognizer(tapGestureRecognizer)
}
#objc func userTapped(_ sender: UITapGestureRecognizer) {
let finger = convertPoint(fromView: sender.location(in: view))
let circle = SKShapeNode(circleOfRadius: 25)
circle.position = finger
addChild(circle)
}
}
class GameViewController: UIViewController {
private var gameView: SCNView { view as! SCNView }
override func viewDidLoad() {
gameView.overlaySKScene = CustomSKScene(size: gameView.bounds.size)
}
}
(This should still allow the ship scene to render when you run the app)
When you tap the screen, circles shouldn't show up. Fix this issue by adding view!.setNeedsDisplay() below the addChild function. Notice how CPU usage goes up to around 40-50% if you tap repeatedly after adding this fix.
Replace view!.setNeedsDisplay() with view!.setNeedsDisplay(view!.frame) (which should be equivalent).
At this point we are now back to square one. The circles are not showing up on screen again and confusion ensues. view.setNeedsDisplay() and view.setNeedsDisplay(view.frame) should be equivalent, yet, nothing is redrawn.
Does anyone know how to fix this problem? I feel it only happens when using the overlaySKScene property so maybe there is some caveat with its implementation that I am unaware of.
Some observations:
When you debug the view hierarchy, the overlaid SKScene doesn't show up anywhere, which is strange
sender.view === view returns true
(sender.view as! SCNScene).overlaySKScene === self also returns true
I want to write the iOS Game Example that uses SpritKit in Swift, which is provided with Xcode only in code. That means I don’t want to use the GameScene.sks, actions.sks and the main.storyboard. I know how to write it without storyboard, but I can’t get it working without the .sks files. Can you say what I must change or can you provide me with a full project?
You will first need to have a View Controller. You can adjust the properties how you would like them. Here is mine:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
// MARK: View Controller overrides
override func viewDidLoad() {
super.viewDidLoad()
view = SKView(frame: view.bounds)
if let view = self.view as! SKView? {
// Initialise the scene
let scene = GameScene(size: view.bounds.size) // <-- IMPORTANT: Initialise your first scene (as you have no .sks)
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
// Scene properties
view.showsPhysics = false
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
Then, you create a class for your first scene. Mine is called GameScene which was initialised in the view controller. Make sure this is a subclass of SKScene. It will look something like this:
import SpriteKit
class GameScene: SKScene {
/* All Scene logic (which you could extend to multiple files) */
}
If you have any questions, let me know :)
I am getting a couple of errors like this in a few of my programs and been trying to fix it. Example: When I put size instead of coder it will tell me its incorrect and change it back to coder.
func reset() {
self.removeAllChildren()
var viewSize = view?.bounds.size
backgrounMusicPlayer.stop()
replayButton.isHidden = true
let gameScene = GameScene(coder: viewSize) //where error is
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
gameScene.scaleMode = SKSceneScaleMode.aspectFill
self.scene!.view?.presentScene(gameScene, transition: transition)
}
The problem is in your initialization of the GameScene object ... as the error implies.
let gameScene = GameScene(coder: viewSize) //where error is
You are passing in a CGSize object, when this initializer expects an NSCoder object. I'm assuming that GameScene is a subclass of SKScene, you should be initializing with
let gameScene = GameScene(size: viewSize)
To avoid to write wrong syntax you could use the XCode autocompletion.
Control + SpaceBar triggers XCode’s autocomplete functionality, also when you want to know the available init methods about a generic class like SKScene you simply write:
Doing this you can immediatly see input properties and the required types.
About your case you have a class named GameScene that subclass SKScene and you try to make:
SKScene.init(coder: <#T##NSCoder#>)
Obviusly you cannot pass viewSize as the input parameter for this init method. You should use init(size: CGSize) method
Details:
Now we speak about the init(coder: NSCoder).
The procedure to save the state of your view to disk is known as serialization. The reverse is deserialization - restoring the state of the object from disk.
override func encode(with aCoder: NSCoder) {
// Serialize your object here
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
// Deserialize your object here
fatalError("init(coder:) has not been implemented")
}
These methods are needed in your custom class to speak with Interface Builder, for example objects to the storyboard, that serialize the state of that object on to disk and deserialize it when you use the storyboard but if you don't add any new properties to your subclass you can also avoid to write your own serialization and deserialization code for the subclass.
Assuming your GameScene class is a subclass of SKScene, it doesn't have an initializer with a coder label.
Just change GameScene(coder: viewSize) to GameScene(size: viewSize)
If this is a SpriteKit Project, the GameScene class does not need the size and only requires the name of the .sks file which represent the scene. (the size would be set as property on that file)
Try this, it would reset and represent the game scene again
if let scene = GameScene(fileNamed:"GAME_SCENE_FILE_NAME") {
scene.replayButton.isHidden = true
let transition = SKTransition.flipHorizontal(withDuration: 0.5)
scene.scaleMode = .aspectFill
self.view!.presentScene(scene, transition: transition)
}
I have this line in my GameViewController.swift. It was trying to make me "correct" my code to use "coder" instead of "size" and NScoder was the type.:
let scene = GameScene(size: view.bounds.size)
I commented out this part of my code in my GameScene.Swift. That fixed my problem.
/*
required init?(coder aDecoder: NSCoder) {
// theSelf = self
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder) //VL:4/1/2015 had to comment out for some reason
}
*/
Intuition helped me figure it out. I did a search for NSCoder in my project and found this. Which essentially was changing how the GameScene inits. This project started at least as far back as xcode 6, which is probably why.
This is my code for the view controller
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "GameScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
And this is my code for the GameScene
import SpriteKit
import GameplayKit
class GameScene: SKScene {
override func didMove(to view: SKView) {
print("gets called")
}
But for some reasons, in the debug area, it didn't print "gets called", which indicates that didMove didn't even get called. What's happening here? Did I miss anything?
The iOS 9 way
In your GameViewController try to directly present your GameScene instead of a generic SKScene.
if let scene = GameScene(fileNamed: "GameScene") {
...
Remember "fileNamed" is not the name of the .swift file, its the name of the .sks file which is used for the xCode level editor.
The new iOS 10 way
It seems Apple now prefers to pass a generic SKScene like you are trying.
if let scene = SKScene(fileNamed: "GameScene") { ... }
To make it work go to the relevant .sks file and go to the inspector on the right. Click the second last item (custom class) and enter the name of the .swift file into the custom class field.
Hope this helps.
This could happen because you don't have the related SKS (GameScene.sks in your case) file reference to your project, check if you have added it or removed/renamed due to mistake.
I finally sort that out.
My project name contains a period at the end. Like the name "XXXX.". After several experimentations, I discovered that I can simply solve the problem by removing the period.
My suggestion:
use
override func didMoveToView(view: SKView)
instead of
override func didMove(to view: SKView)
Maybe a naming inconsistency in the documentation/API...
I second what Tom Xue said as someone who spent way too long looking for the answer to this. I had a hyphen in my app name and that seems to have been causing the problem. When I renamed my project, the scene was presented as it should have been.
I'm using spritekit with swift, and I'm just trying to present a new scene...but something is wrong and throwing errors. I was pretty sure this was the right syntax, and this is really throwing me for a loop.
The line
let skView = self.view as SKView
is giving me the error "SKView? is not convertible to SKView
Any advice is appreciated!
*my current code is below:
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
for touch: AnyObject in touches{
let location = touch.locationInNode(self)
if(self.nodeAtPoint(location) == self.playButton)
{
var scene = PlayScene(size: self.size)
let skView = self.view as SKView
// Configure the view.
/* 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)
}
}
}
The problem is the view property on SKScene is an optional (SKView?) because an SKScene doesn't necessarily have a containing view; what's it contained in before its been presented? Nothing.
To solve your problem you need to check the scene has a view by unwrapping the view property, by using optional binding for example:
if let view = self.view {
let scene = PlayScene(size: self.size)
scene.scaleMode = .AspectFill
view.presentScene(scene)
}
You can be pretty certain your SKScene has been presented (and thus view isn't nil) if someone is pressing on it - therefore you could instead force unwrap view, like so:
let scene = PlayScene(size: self.size)
scene.scaleMode = .AspectFill
view!.presentScene(scene)
Also, it's not required that you configure the SKView again (skView.ignoresSiblingOrder = true) since this, presumably, was already done in GameViewController.
Edit:
The line let skView = self.view as! SKView is likely from GameViewController (a subclass of UIViewController). If you take a look at the documentation for UIViewController and SKScene you'll see they both have a view property:
class UIViewController: /* Superclass and Protocols */ {
var view: UIView!
// ...
}
class SKScene: SKEffectNode {
weak var view: SKView?
// ...
}
However their types differ which means the way you use them differs. In the case of SKScene see above about unwrapping. In the case of UIViewController, you need to cast to SKView with the line:
let skView = self.view as! SKView
because UIView doesn't have the methods necessary for presenting an SKScene. (For more information on casting I'd recommending you take a look at The Swift Programming Language: Type Casting)
Normally you wouldn't be allowed to cast from UIView to SKView (keep in mind that SKView is a subclass of UIView). Try the following in Playgrounds:
let view = UIView()
let skView = view as! SKView
You should get an error something along the lines of:
Could not cast value of type 'UIView' (0x1041d0eb0) to 'SKView'
(0x10da10718)
However you can cast from UIView to SKView in your GameViewController because the Custom Class of GameViewController's view has been set to SKView:
I hope that helps clear up any confusion you had.
try this :
let sKView = self.view?.scene?.view
sKView?.presentScene(scene)
Best regards,
Yassine