Swift/SpriteKit - Any way to pool/cache SKReferenceNodes?

258 views Asked by At

Is there a way to pool/cache SKReferenceNodes in SpriteKit using Swift?

I am creating a game using xCodes visual level editor. I am creating different .sks files with the visual level editor that I am than calling in code when I need to. I am calling them in code because I am using them to create random levels or obstacles so I don't need all of them added to the scene at once.

At the moment I am doing it like this

I create a convince init method for SKReferenceNodes to init them with URLs. I am doing this because there is some sort of bug calling SKReferenceNodes by file name directly (https://forums.developer.apple.com/thread/44090). Using such an extension makes makes the code a bit cleaner.

 extension SKReferenceNode {

convenience init(roomName: String) {
    let path: String
    if let validPath = NSBundle.mainBundle().pathForResource(roomName, ofType: "sks") {
        path = validPath
    } else {
        path = NSBundle.mainBundle().pathForResource("RoomTemplate", ofType: "sks")! // use empty roomTemplate as backup so force unwrap
    }

    self.init(URL: NSURL(fileURLWithPath: path))
}

}

and than in my scenes I can create them and add them like so (about every 10 seconds)

 let room = SKReferenceNode(roomName: "Room1") // will cause lag without GCD
 room.position = ...
 addChild(room)

This works ok but I am getting some lag/stutter when creating these. So I am using GCD to reduce this to basically no stutter. It works well but I am wondering if I can preload all .sks files first.

I tried using arrays to do this but I am getting crashes and it just doesn't seem to work (I also get a message already adding node that has a parent).

I was trying to preload them like so at app launch

 let allRooms = [SKReferenceNode]()

 for i in 0...3 {
     let room = SKReferenceNode(roomName: "Room\(i)")
     allRooms.append(room)
 }

and than use the array when I need too. This doesn't work however and I am getting a crash when trying to use code like this

 let room = allRooms[0]
 room.position = 
 addChild(room) // causes error/crash -> node already has parent

Has anyone done something similar? Is there another way I can pool/cache those reference nodes?. Am i missing something here?

2

There are 2 answers

0
crashoverride777 On BEST ANSWER

I just figured it out, I was just being an idiot.

Using arrays like I wanted to is fine, the problem I had which caused the crash was the following.

When the game scene first loads I am adding 3 rooms but when testing with the arrays I kept adding the same room

let room = allRooms[0]

instead of using a randomiser. This obviously meant I was adding the same instance multiple times, hence the crash.

With a randomiser, that doesn't repeat the same room, this does not happen.

Furthermore I make sure to remove the room from the scene when I no longer need it. I have a node in the rooms (roomRemover) which fires a method to remove/create a new room once it comes into contact with the player. This would be the code in DidBeginContact.

 guard let roomToRemove = secondBody?.node.parent?.parent?.parent?.parent else { return }
 // secondBody is the roomRemover node which is a child of the SKReferenceNode. 
 // If I want to remove the whole SKReferenceNode I need to add .parent 4 times. 
 // Not exactly sure why 4 times but it works

 for room in allRooms {
    if room == roomToRemove {
        room.removeFromParent()
    }
 }
 loadRandomRoom()

Hope this helps someone trying to do the same.

3
Alessandro Ornano On

Speaking about the SKReferenceNode preload, I think the policy to be followed is to load your object, find what kind they are and use the official preloading methods available in Sprite-Kit:

To avoid this kind of error you should create separate instances of the nodes.

Try to doing this:

let room = allRooms[0]
room.position = ...
room.removeFromParent()
addChild(room)