Swift Spritekit Arc for Dummies

Swift SpriteKit ARC for dummies

Thank you for all the answers, especially appzYourLift and your detailed reply.

I was digging into my spriteKit project the whole day, also experimenting with playground a lot and my game is totally leak free now with no strong reference cycles to be found.

It actually turned out a lot of the leaks/persistant classes I was seeing in instruments where simply there because something else didnt deallocate.

Its seems that repeat action forever was causing my leaks. All I had to do is loop through all the nodes in my scene and remove their actions.

This question helped me with this

iOS 7 Sprite Kit freeing up memory

UPDATE: I recently revisited this topic again because I felt like having to manually remove actions on nodes should not be necessary and could become cumbersome. After more research I found the precise issue for the (my) memory leaks.

Having a "SKAction repeat forever" like this apparently causes the leak.

let action1 = SKAction.wait(forDuration: 2)
let action2 = SKAction.run(someMethod)
let sequence = SKAction.sequence([action1, action2])
run(SKAction.repeatForever(sequence))

So you need to change it to this to not cause a leak

 let action1 = SKAction.wait(forDuration: 2)
let action2 = SKAction.run { [weak self] in
self?.someMethod()
}
let sequence = SKAction.sequence([action1, action2])
run(SKAction.repeatForever(sequence))

This is coming directly from an Apple bug report I made.

Swift / SpriteKit - Can't break strong reference cycle

There wasn't an easy way to do this. Check all your properties that reference eachother and use weak or unowned. The bigger issue in spritekit is checking for closures. When you're running delayed closures (in SKActions) and things like that it's important to use [weak self] within the closure so it doesnt keep the object alive. It's easier to add more weaks and unowned than necessary until the strong reference cycle is broken and then work your way backwards deleting unnecessary ones.

Reuse same sprite node within multiple scenes in sprite kit using Swift

There is a few ways to do this.

You could subclass your other scenes to be subclass of the scene with the loadNode function which gives those scenes access to that function.

I asked a question about this last year

Swift multiple level scenes

Another way that might be a bit easier if you are not comfortable with scene subclassing is to just create a subclass of the node itself.

So you create a class

enum EnemyType {
case Normal
case Special
}

class NodeA1: SKSpriteNode {

init(imageNamed: String, enemyType: EnemyType) {
let texture = SKTexture(imageNamed: imageNamed)

if enemyType == .Normal {
super.init(texture: texture, color: SKColor.clearColor(), size: texture.size())
else {
// other init
}

self.zPosition = 1
self.name = ""
// add physics body, other properties or methods for the node

}
}

Than in your SKScenes you can add the node in the init method like so

  nodeA1 = NodeA1(imageNamed: "ImageName", enemyType: .Normal)
nodeA1.position = ....
addChild(nodeA1)

this way ever scene where you add the node will use the subclass and therefore include all the properties, set up etc for that node. Another benefit with subclassing is that you could loop through all your nodes using

self.enumerateChildNodesWithName...

and than call custom methods on all nodes.

If you want to subclass your scenes than you would create your baseScene

 class BaseScene: SKScene {

// set up all shared stuff in didMoveToView
// have your node function here
// touches began
// physics word and contact collision
// all other stuff that needs to be shared between all level scenes

}

Than your subsequent level scenes would look something like this

  class Level1Scene: BaseScene {

override func didMoveToView(view: SKView) {
super.didMoveToView(view) // This lines imports all stuff in BaseScene didMoveToView

// do level 1 specific setUps.
// you can call any function or property from BaseScene, e.g the loadNode function.
}

You than load you level scenes as usual, e.g you transition to level 1 scene and it will automatically use/have access to all the superclass methods and sprites (BaseScene).
So you never call baseScene directly, its gets called automatically.

This applies for other methods in baseScene too, so say you have a Update method in BaseScene.

 override func update(currentTime: CFTimeInterval) {.... }

This will work across all your level scenes which are subclasses of BaseScene.

But what happens if you need to add some specific stuff to the update method only relevant in 1 level scene and not all level scenes?
It would be the same process, you create a new update func in the LevelScene and call super.

 override func update(currentTime: CFTimeInterval) {
super.update(currentTime) // this calls the baseScene Update method

/// specific stuff for that level only
}

Super simply means the super class of the currentScene, which is BaseScene if the scene is a subclass of it.

Is this helping?

3D Touch and SpriteKit

Yes, you can integrate 3D Touch and SpriteKit.

Using the default SpriteKit game project template (Swift), set your GameScene to be a UIViewControllerPreviewingDelegate and implement the protocol's methods, like this:

public class GameScene: SKScene, UIViewControllerPreviewingDelegate {
public func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
return UIViewController()
}

//Pop
public func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
return
}
}

In GameViewController.swift's viewDidLoad, add the following:

if let scene = GameScene(fileNamed:"GameScene") {
if self.traitCollection.forceTouchCapability == .Available {
registerForPreviewingWithDelegate(scene, sourceView: scene.view!)
}
}

In the delegate method, I am returning an empty view controller. You can create a class-level variable and instantiate this VC with appropriate peek-type views. Again, a very simple example. If you want to add 3D Touch from an AppIcon interaction, you will need to modify Info.plist and add capabilities from the AppDelegate. There is a good video on this topic. Good luck!

Using acceptsMouseMovedEvents for SpriteKit mouse actions with Storyboards and Swift

You can configure an NSTrackingArea object to track the movement of the mouse as well as when the cursor enters or exits a view. To create an NSTrackingArea object, you specify a region of a view where you want mouse events to be tracked, the owner that will receive the mouse event messages, and when the tracking will occur (e.g., in the key window). The following is an example of how to add a tracking area to a view. Add to your SKScene subclass, such as GameScene.swift.

Swift 3 and 4

override func didMove(to view: SKView) {
// Create a tracking area object with self as the owner (i.e., the recipient of mouse-tracking messages
let trackingArea = NSTrackingArea(rect: view.frame, options: [.activeInKeyWindow, .mouseMoved], owner: self, userInfo: nil)
// Add the tracking area to the view
view.addTrackingArea(trackingArea)
}

// This method will be called when the mouse moves in the view
override func mouseMoved(with theEvent: NSEvent) {
let location = theEvent.location(in: self)
print(location)
}

Swift 2

override func didMoveToView(view: SKView) {
// Create a tracking area object with self as the owner (i.e., the recipient of mouse-tracking messages
let trackingArea = NSTrackingArea(rect: view.frame, options: NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseMoved, owner: self, userInfo: nil)
// Add the tracking area to the view
view.addTrackingArea(trackingArea)
}

// This method will be called when the mouse moves in the view
override func mouseMoved(theEvent: NSEvent) {
let location = theEvent.locationInNode(self)
println(location)
}

SpriteKit can't change SKPhysicsJointLimit maxLength

0x141E provided information on how to do this, making the connection I could not make. Basically create a new joint after removing the first one. I have a global variable for my joint, and this worked nicely for me. Here is the code that I'm now using in the touchesBegan

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.running) {
// Create the new maxLength
float newMaxLength = self.limitJoint.maxLength + 50.0;
[self.scene.physicsWorld removeJoint:self.limitJoint];

self.limitJoint = [SKPhysicsJointLimit jointWithBodyA:self.node1.physicsBody bodyB:self.node2.physicsBody anchorA:self.node1.position anchorB:self.node2.position];
[self.limitJoint setMaxLength:newMaxLength];

[self.scene.physicsWorld addJoint:self.limitJoint];
NSLog(@"New length: %f", self.limitJoint.maxLength);
} else {
self.running = YES;
self.node1.physicsBody.dynamic = YES;
}
}

Sprite Kit - Create arch path for SKSpriteNode given a swipe gesture

Ended up using the physics based approach. I went through the iOS Sprite Kit programming guide which helped quite a bit (go figure).

https://developer.apple.com/LIBRARY/IOS/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Introduction/Introduction.html

Per @LearnCocos2D and @doctorBroctor I came up with the solution that seems to be working well enough. I updated my github project if anyone stumbles upon this post and is looking for some code.

Thanks everyone!

PS - I wasn't sure how to mark this since I used pieces of two responses/answers. So I created this answer with explanation and up-voted both responses. If there's a better and more fair way to mark this question I'm all ears.



Related Topics



Leave a reply



Submit