I'm very new to both Swift and iOS development.
I have been working on a game based off Apple's SpriteKit (with GameplayKit integration) template.
All was working fine until I renamed GameScene.swift and GameScene.sks to MapMainScene.swift and MapMainScene.sks. I made sure to change this code in GameViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
// Load 'GameScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = GKScene(fileNamed: "GameScene") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! GameScene? {
// Copy gameplay related content over to the scene
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
to:
override func viewDidLoad() {
super.viewDidLoad()
// Load 'MainMapScene.sks' as a GKScene. This provides gameplay related content
// including entities and graphs.
if let scene = GKScene(fileNamed: "MainMapScene") {
// Get the SKScene from the loaded GKScene
if let sceneNode = scene.rootNode as! MainMapScene? {
// Copy gameplay related content over to the scene
sceneNode.entities = scene.entities
sceneNode.graphs = scene.graphs
// Set the scale mode to scale to fit the window
sceneNode.scaleMode = .aspectFill
// Present the scene
if let view = self.view as! SKView? {
view.presentScene(sceneNode)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
}
}
I also changed the following in Gamescene.swift:
class GameScene: SKScene {
// Some code
}
to this in MainMapScene.swift:
class MainMapScene: SKScene {
// Some code
}
but the app crashes upon launch. The output of the console says:
2016-07-21 16:29:35.593592 MyGame[10656:1944648] [User Defaults] CFPrefsPlistSource<0x6080000e5e00> (Domain: kCFPreferencesAnyApplication, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null)) is waiting for writes to complete so it can determine if new data is available
2016-07-21 16:29:35.603729 MyGame[10656:1944648] [FenceWorkspace] creating a CA fence port (5d13)
2016-07-21 16:29:35.638 MyGame[10656:1944648] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (GameScene) for key (root); the class may be defined in source code or a library that is not linked'
*** First throw call stack:
So it looks like there is still a reference to GameScene somewhere in my project but I have done a project-wide search and there is no GameScene anywhere to be found.
I'm sure this is something really straightforwards. Do I need to change an outlet or something in the storyboard editor?? I can reproduce this problem by trying to change the name of GameScene in a new project too so I know it's not something specific to my project.
GameScene is actually referenced in one more place, and the error coming from NSKeyedArchiver should clue you in as to where: it's encoded in the .sks file you're loading. You can find it in the "Custom Class" tab of the .sks file in Xcode's editor.
This setting tells NSKeyedUnarchiver what class the encoded scene should be when you load the .sks file. This should be the last remaining reference to GameScene, so this should complete your renaming!
Related
Let me explain a little better what I mean since it's kinda tricky to understand.
I'm creating a prototype for a videogame. Every level inherits the main rules from a SKScene called SceneLogic:
class SceneLogic: SKScene, SKPhysicsContactDelegate {
// Set up the physics, the contacts, touches and so on...
}
class Level1: SceneLogic { }
class Level2: SceneLogic { }
Every level has its own .sks file which specifies the different icon to show in the HUD. In order to create a kind of "game engine" I thought to init every kind of graphics inside the SceneLogic class by lazy var and ignore them if the current level doesn't need it.
Let me explain with an example
class SceneLogic: SKScene, SKPhysicsContactDelegate {
// Text, available only for level 1
private lazy var textTopHUD = childNode(withName: "textTop") as! SKLabelNode
// Icon, available only for levels 3,4,5
private lazy var iconBottomHUD = childNode(withName: "iconBottom") as! SKSpriteNode
// Icon, available only for level 2
private lazy var iconLeftHUD = childNode(withName: "iconLeft") as! SKSpriteNode
func initGame(level: Int) {
switch mode {
case 1: // Level 1
textTopHUD.text = "Level 1"
case 2: // Level 2
iconLeftHUD.position = ....
}
}
}
The fact is: for level 1, iconBottomHUD is nil, for level 2 textTopHUD is nil... but the app doesn't crash since the var is lazy and it won't be called for some levels.
My question is: is it a good programming style? Is it safe to use lazy in this way?
The thing about lazy properties is that it defers the execution of the initialization code. So in your case it doesn't really matter since they are actually nil. So you defer the init of nothing basically. If i were you i would basically either make it as a computed propert as such:
private lazy var iconBottomHUD: SKSpriteNode = {
guard let node = childNode(withName: "iconBottom") as? SKSpriteNode else {
fatalError()
}
return node
}
Or make it as #JavierRivarola mentioned, make it protocol based.
I'm having this strange problem. In my game I load a Scene from an .sks file. When I load it on the main thread I have no issues whatsoever. Everything loads just fine. But when I load on the background thread, the app crashes due to memory issues. Here's my code...
DispatchQueue.global(qos: .background).async {
let nextScene = World(fileNamed: "GameScene")
DispatchQueue.main.async {
self.nextScene = nextScene
self.playerRunningState = .RUNNINGRIGHT
}
}
Does anyone have any idea why this would work on the main thread but not on a background thread.
FYI it crashes on the following line:
let nextScene = World(fileNamed: "GameScene")
SpriteKit is not thread safe when manipulating nodes as quoted in Apple's documentation: https://developer.apple.com/documentation/spritekit/sknode
Manipulations to nodes must occur in the main thread. All of the SpriteKit callbacks for SKViewDelegate, SKSceneDelegate and SKScene occur in the main thread and these are safe places to make manipulations to nodes. However, if you are performing other work in the background, you should adopt an approach similar to Listing 1.
All manipulations must happen on the main thread synchronously, and making a new node is a manipulation. If you wan to make manipulations to nodes off of the main thread then you must adopt this approach:
class ViewController: UIViewController {
let queue = DispatchQueue.global()
var makeNodeModifications = false
func backgroundComputation() {
queue.async {
// Perform background calculations but do not modify
// SpriteKit objects
// Set a flag for later modification within the
// SKScene or SKSceneDelegate callback of your choosing
self.makeNodeModifications = true
}
}
}
extension ViewController: SKSceneDelegate {
func update(_ currentTime: TimeInterval, for scene: SKScene) {
if makeNodeModifications {
makeNodeModifications = false
// Make node modifications
}
}
}
I work on a SpriteKit Game which is connected to another Device via MultipeerConnectivity. I put all the Connection stuff in an extra ConnectionManager.swift file to clean up the code a bit.
When receiving data, a function in the ConnectionManager.swift class is called to convert the data into Float and pass the value to a function in the GameScene.swift class.
GameViewController.swift:
class GameViewController: UIViewController {
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 = true
scene.scaleMode = .resizeFill
skView.presentScene(scene)
}
}
GameScene.swift:
class GameScene: SKScene {
var scoreLabel = SKLabelNode()
let ConnectionsManager = ConnectionManager()
func dummyText(Spieler: Int, Anweisung: Float) {
print("dummyText")
scoreLabel.text = String(Anweisung)
}
ConnectionManager.swift : Here is the function that should pass values to GameScene().dummyText()
class ConnectionManager: NSObject, MCSessionDelegate, MCNearbyServiceBrowserDelegate, MCNearbyServiceAdvertiserDelegate {
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
print("Etwas erhalten!")
NSLog("%#", "Etwas erhalten!")
// Trying with dummy Value
GameScene().dummyText(Spieler: 1, Anweisung: 200 )
}
}
By using GameScene() I assume that I create a new instance instead of using the GameScene which is already shown and which I want to manipulate. Unfortunately I don't know how to work with different classes correctly. The best resolution for me would be to call the ConnectionManager class as some kind of ChildClass of GameViewController, so I can call functions like super.dummyText().
Besides learning swift for a few weeks I am still a newbie. I searched for this problem for a long time and I found some results, but no resolution worked for me. That's why I am posting a new topic here. Thanks in advance for your help!
So I'm making some small projects for a major game that I've been developing, but without knowing why, everytime I try to run my Game a SIGABRT error prompts with the following message:
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '* -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (GameScene) for key (root); the class may be defined in source code or a library that is not linked'
I've added the following extension to SKNode
extension SKNode {
class func unarchiveFromFile(_ file: NSString) -> SKNode? {
if let path = Bundle.main().pathForResource(file as String, ofType: "sks") {
do {
let sceneData = try Data(contentsOf: URL(fileURLWithPath: path), options: NSData.ReadingOptions.dataReadingMappedIfSafe)
let archiver = NSKeyedUnarchiver(forReadingWith: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! GameScene
archiver.finishDecoding()
return scene
} catch {
print("ERROR1002")
return nil
}
} else {
print("ERROR001 - pathForResource not found")
return nil
}
}
}
I've used this extension in other projects and everything ran like a charm, but now on this one.
When my game lanches, my GameViewController.swift File is called and on my viewDidLoad method I do the following:
import UIKit
import SpriteKit
import GameplayKit
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = GameScene.unarchiveFromFile(currentSKSFile) as? GameScene {
// Configure the view.
let skView = self.view as! SKView
skView.showsFPS = false
skView.showsNodeCount = false
/* 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)
}
}
override func shouldAutorotate() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
if UIDevice.current().userInterfaceIdiom == .phone {
return .landscape
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
print("didReceiveMemoryWarning ERRO - GameViewController.swift")
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
I've debugged the code and it crashes on the following line in the extension class I've added:
let scene = archiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! GameScene
And it prompts the error described above.
I'm running Xcode 8 Beta + iOS 10, and no, it's not because of this, because I can run the exact same extension used in other projects on this kind of devices with Beta versions without any error.
Any hint on what might be causing this?
Regards - Ivan
Somehow cleaning my project solved my issues!
I have tried different stuff and the errors were there, so I've cleaned it and the error wasn't there anymore.
I don't know why that happened , but it's solved.
Thanks anyway :)
Is there a way I can display multiple scenes in the same SCNView at the same time? So let's say I have a ball in ball.dae and a pyramid in pyramid.dae. So these are separate scenes and I would like to show both at the same time.
Currently I only have one and I create it like this:
let scene = SCNScene(named: "world1.dae")!
If I just create another one below then it just displays the second one... :/
Thanks in advance!
Ok I already figured it out. The thing is to not add another scene but nodes from the 2nd scene to the first like this:
let scene = SCNScene(named: "world1.dae")!
let subScene = SCNScene(named: "pyramid.dae")!
let pyramid = subScene.rootNode.childNodeWithName("pyramid", recursively: true)!
scene.rootNode.addChildNode(pyramid)
I have made two SCNNode extension functions that clone nodes
from another scene:
extension SCNNode
{
func addNodeClonesWithNames(#fromScene: SCNScene, nodeNames: [String] )
{
for nodename in nodeNames
{
self.addClonedChildNode( fromScene.rootNode.childNodeWithName(nodename, recursively: true)! )
}
}
func addClonedChildNode(node: SCNNode)
{
self.addChildNode(node.clone() as! SCNNode)
}
}
in your view controller, loading two scenes here:
let scnMain = SCNScene(named: "world1.dae")!
let scnAssets = SCNScene(named: "props.dae")!
// then add some nodes from the assets scene to the main scene's
// root node by name:
scnMain.rootNode.addNodeClonesWithNames(fromScene: scnAssets,
nodeNames: ["btnStartEngine","btnStop","btnReverse","btnEject"])
//
Of course, this works not only on a root node, but on all SCNNodes.
I use cloning here so that the original stays intact for multiple usage.
I hope this is useful. So glad this is Swift.. Regards. Ted
In the latest version of Xcode 6.2 with iOS 8.2, I had to slightly change the code
func getNodeFromScene(colladaFile: String) -> SCNNode {
var subScene = SCNScene(named: "art.scnassets/\(colladaFile).dae")
let geometryNode = subScene?.rootNode.childNodeWithName(colladaFile, recursively: true)!
return geometryNode!
}
subScene to subScene?