Should I Remove All Skspritenodes and Labels When I Switch from One Node to Another

Should I remove all SKSpriteNodes and Labels when I switch from one node to another

When you perform the transition, the scene and its nodes will be released from memory, unless you have a strong reference cycle. Also, you should know that SpriteKit has its own cache system for the SKTextures, so not all memory will freed.

The lag could be caused by a lot of thing, some possibilities:

  1. If you instantiate the new scene on touchesEnded (or your custom button callback closure), the lag could be caused because you're doing too much work on the initialization. This can be solved by, for example, preloading the scene with a closure that run in background and, when you have to run the transition, you already have everything loaded. An example follows:
    Sample Image

  2. Maybe you're using assets that are too large and because they take longer to be loaded, you have the lag. You could solve this by, for example, converting images that don't need an alpha channel to .jpeg.
    Another solution would be to preload assets. A code example follows.
    Sample Image

SpriteKit transition between scenes without resetting game

You have lots of options to keep persistent state across your game scenes. I've listed two approaches I've used.

Option A: Maintain a Reference to the Scene

When a scene is swapped out for a new one, the scene is often fully removed from memory. If you hold onto a reference for the scene object somewhere else, and present that reference, no data will be lost.

To maintain a reference over time (and present the scene again when needed), I recommend a scene presenter class with a static instance such as the following:

class SceneCoordinator {
static var shared = SceneCoordinator()

var gameScene : GameScene?
var shopScene : ShopScene?
}

when you initialize a GameScene, also register it with your with SceneCoordinator.shared.gameScene = self. Then, when transitioning away from another scene, you can present the instance you stored in the coordinator class.

didMoveToView() will still be called on the scene when it is presented again. You could move all your initializing code to a separate function, create a new instance var such as var isInitialized = false, and only initialize your content if it is false (and set it to true after your initialize).

The issue with this approach is that scene objects are expensive, which means you could build up a large overhead by not allowing scenes to be released.

Option B: Model Struct

The better way (which would also allow for easier reinit of a scene after your app closes) would be to create a data model of your game state, and provide a function to create a GameScene from your model object.

This method is more consistent with the Model-View-Controller design pattern, and allows your scenes and data to be a lot more lightweight.

Such as:

struct GameModel {
var coins : Int
}

class GameScene : SKScene {
var state : GameModel

convenience init(size: CGSize, state: GameModel) {
self.state = state
// set up scene content from state
}

// lots of fun game scene logic

func playerCollectedCoin() {
state.coins += 1
}

func moveToShopScene() {
// init a new shop scene with the state of this scene
let shop = ShopScene(size: self.view!.bounds.size, state: self.state)
(self.view as! SKView).presentScene(scene)
}
}

class ShopScene : SKScene {
var state : GameModel

convenience init(size: CGSize, state: GameModel) {
self.state = state
// set up scene content from state
}

// lots of fun shop scene logic

func playerSpentCoins(amount: Int) {
state.coins -= amount
}

func moveToGameScene() {
// init a new game scene with the updated state of this scene
let game = GameScene(size: self.view!.size, state: self.state)
(self.view as! SKView).presentScene(game)
}
}

How to refer to multiple SKNodes at once in SpriteKit?

You can’t. You need to place them into a collection (array, dictionary, set, etc) and then find a way to traverse through them with something like foreach, map, compactMap (formally flatMap), reduced, etc. in your case, I would use reduce.

let caps = [Cap1,Cap2,Cap3,Cap4,Cap5,Cap6,Cap7,Cap8,Cap9,Cap10]
let result = caps.reduce(false,{value,cap in return value || cap.position.y < illustration2.position.y})
if result {
// Transitioning to game over

let transition = SKTransition.crossFade(withDuration: 0)
let gameScene = GameOver(size: self.size)
self.view?.presentScene(gameScene, transition: transition)
}

What this is doing, is going through each of your nodes, and evaluating the current equation with the previous result.

So we start at a false, then it will check if cap y > illustration y. If true, then our value becomes true, and this value carries over to the next cap. It repeats this until you run out of caps, and then returns the final value, which will be a false or a true.

SKSpriteNode subclass: method to remove all joints

I spent a little more time in investigating this. This is what I have come up with:

I decided to add an property to my SKSpriteNode subclass and manage the joints myself

    var joints: [SKPhysicsJointLimit]
override init(){
...
self.joints = []
...
}

Everytime I add an joint to the scene's SKPHysicsWorld I also add it to the joints array of the SKNNode itself. Whilst iterating the SKPHysicsBody's joints-Array failed (see question) at the point I wanted to cast it to SKPhysicsJoint, removing items from the physics world works as intended when iterating the array of SKPhysicsJointLimit items:

func freeJoints(world: SKPhysicsWorld){
for item in self.joints{
println("removing item from physics world \(item)")
world.removeJoint(item)
}
self.joints.removeAll(keepCapacity: false)
}
}

This seems not to be the most elegant way to do the job, since there already is a framework managed array that promises to be same thing. But I was unable to utilize it and this works for now.

Sprite Kit: create node only once for all scenes

You can't create a node that persists between scenes. Once you present a new scene, you would need to add the nodes to this new scene.

For this reason, I do not use SKScenes the way Apple describes in the documentation because of this issue. Not only is it cumbersome to have to add the nodes to the new scene each time but also extremely inefficient for nodes like background nodes that should always be present.

So what I did is create 2 scenes, one for the game scene and one for the menu (GUI).

For the menu scene I subclass SKNodes for my interface and then use SKActions on these nodes to present and dismiss them on the screen so it feels like the user is transitioning between scenes. This gives you total customization because you can present multiple nodes, you can keep nodes on the screen permanently etc.

By subclassing the SKNodes you can organize your code just as you did for the scenes. Each node will represent a "scene" in your App. Then you just need to write a method to present and dismiss these nodes.


I've added some sample code below to show one implementation of using SKNodes as "Scenes." The sample code has a base class called SceneNode which we subclass (just as you would subclass an SKScene). In this implementation, I use the GameScene to handle all transitions between scene nodes*. I also keep track of the current scene node so that I can update its layout in case the scene changes size (such as rotation or window resize on OS X**). Your game might not need this, but it's a great way to dynamically layout your nodes. Anything that you want to add to the background or keep around, simply add it to the GameScene. Anything that you want to add to a scene, simply subclass a SceneNode, transition to it and your good to go.

*You could easily present scene nodes directly from other scene nodes instead of going through the GameScene. However I have found that using the GameScene to handle transitions between nodes works very well, especially when you have many scenes with complex transitions.

**There is a bug on OS X, resizing the window does not call the scene's didChangeSize. You need to manually call it.

class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size:self.view.bounds.size)
scene.scaleMode = .ResizeFill
(self.view as! SKView).presentScene(scene)
}
}

class GameScene: SKScene {
var currentSceneNode: SceneNode!

override func didMoveToView(view: SKView) {
self.backgroundColor = SKColor.whiteColor()
transitionToScene(.Menu)
}
override func didChangeSize(oldSize: CGSize) {
currentSceneNode?.layout()
}
func transitionToScene(sceneType: SceneTransition) {
switch sceneType {
case .Menu:
currentSceneNode?.dismissWithAnimation(.Right)
currentSceneNode = MenuSceneNode(gameScene: self)
currentSceneNode.presentWithAnimation(.Right)

case .Scores:
currentSceneNode?.dismissWithAnimation(.Left)
currentSceneNode = ScoresSceneNode(gameScene: self)
currentSceneNode.presentWithAnimation(.Left)

default: fatalError("Unknown scene transition.")
}
}
}

class SceneNode: SKNode {
weak var gameScene: GameScene!

init(gameScene: GameScene) {
self.gameScene = gameScene
super.init()
}
func layout() {}
func presentWithAnimation(animation:Animation) {
layout()
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: invert*gameScene.size.width, y: 0)
gameScene.addChild(self)
let action = SKAction.moveTo(CGPoint(x: 0, y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(action)
}
func dismissWithAnimation(animation:Animation) {
let invert: CGFloat = animation == .Left ? 1 : -1
self.position = CGPoint(x: 0, y: 0)
let action = SKAction.moveTo(CGPoint(x: invert*(-gameScene.size.width), y: 0), duration: 0.3)
action.timingMode = SKActionTimingMode.EaseInEaseOut
self.runAction(action, completion: {self.removeFromParent()})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

class MenuSceneNode: SceneNode {
var label: SKLabelNode
var container: SKSpriteNode

override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Menu Scene")
label.horizontalAlignmentMode = .Center
label.verticalAlignmentMode = .Center
container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.userInteractionEnabled = true
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.gameScene.transitionToScene(.Scores)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

class ScoresSceneNode: SceneNode {
var label: SKLabelNode
var container: SKSpriteNode

override func layout() {
container.position = CGPoint(x: gameScene.size.width/2.0, y: gameScene.size.height/2.0)
}
override init(gameScene: GameScene) {
label = SKLabelNode(text: "Scores Scene")
label.horizontalAlignmentMode = .Center
label.verticalAlignmentMode = .Center
container = SKSpriteNode(color: UIColor.blackColor(), size: CGSize(width: 200, height: 200))
container.addChild(label)
super.init(gameScene: gameScene)
self.addChild(container)
self.userInteractionEnabled = true
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
self.gameScene.transitionToScene(.Menu)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

enum SceneTransition{
case Menu, Scores
}
enum Animation {
case Left, Right, None
}

Sample Image

updating an SKLabel to show the right integer

In your Achieve struct, add a property called label:

var label: SKLabelNode?

And change the aAmount property to look something like this:

var aAmount: Int = 0 {
didSet {
label?.text = "\(aAmount) / \(aRequired)"
}
}

Now when you generate the labels, replace this:

_ = generateLabels(CGPointMake(0, -285), page:page1ScrollView, 
text: "\(Ach1.aAmount) / \(Ach1.aRequired)")

with this

var label = _ = generateLabels(CGPointMake(0, -285), page:page1ScrollView, 
text: "\(Ach1.aAmount) / \(Ach1.aRequired)")
Ach1.label = label

Now when you change the aAmount property of Ach1, the label's text should change as well.

Suggestions:

Judging from this and your last question about sprite node images not updating, I think your programming skills are not mature enough. In the long term, you should learn the basics first. Try playing around with UIKit and read more others' code. See how other people do things. You will definitely learn a lot. In the short term though, you shouldn't make each of all these properties binds to a node. That will make Achieve too dependent. What you should do is make Achieve a subclass of SKSpriteNode and add those other sprite nodes and label nodes as sub nodes of the Achieve node.

Update an SKLabelNode to have a different text field later on by a condition basis. Swift SpriteKit

This is somewhat guessing since you haven't shown enough to be sure, but every time you call lookForThings you're creating a new label and adding it to the scene. Any previous labels added aren't being affected, so likely you're building up a stack of labels, most of which are showing the "Start searching" message. They're all at the same place so you're not noticing them as distinct.

Probably you want one label that should appear when the player is in the relevant region and be removed when not. Something like this:

class GameScene: SKScene {
var searchingLabel: SKLabelNode?

func lookForThings() {
if player.position.x > -240 && player.position.x <= 100 {
// Show the searching message
guard searchingLabel == nil else { return } // Already shown
let label = SKLabelNode()
addChild(label)
label.text = ... // Set up the label as desired
searchingLabel = label
} else {
// Don't show the message
guard let label = searchingLabel else { return } // Already removed
label.removeFromParent()
searchingLabel = nil
}
}
}

How can i remove a SKSpriteNode from parent, when the background is the same color as the sprite?

I suggest you generalize the code that creates the squares and backgrounds to simplify adding more colors to your game. Here's an example of how to do that:

Define a type that identifies the type of sprite node to create

typedef NS_ENUM (NSInteger, SpriteType) {
SpriteTypeBackground,
SpriteTypeSquare
};

This method adds a square and a background to the scene each with a randomly selected color

- (void) addSquareAndBackground {
_background = [self createSpriteWithType:SpriteTypeBackground];
_square = [self createSpriteWithType:SpriteTypeSquare];
}

This removes the square and background from the scene

- (void) removeSquareAndBackground {
[_background removeFromParent];
[_square removeFromParent];
}

This creates either a square or a background sprite based on the specified type

-(SKSpriteNode *) createSpriteWithType:(SpriteType)type {
// Select a color randomly
NSString *colorName = [self randomColorName];
SKSpriteNode *sprite;
if (type == SpriteTypeBackground) {
NSString *name = [NSString stringWithFormat:@"%@Outline",colorName];
sprite = [SKSpriteNode spriteNodeWithImageNamed:name];
sprite.name = name;
sprite.size = CGSizeMake(320, 480);
}
else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:colorName];
sprite.name = [NSString stringWithFormat:@"%@Sprite",colorName];
sprite.size = CGSizeMake(50, 50);
}
sprite.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[self addChild:sprite];

return sprite;
}

Randomly select a color name

// Set the total number of colors here
#define kNumberOfColors 2

- (NSString *) randomColorName {
NSString *colorName;
switch (arc4random_uniform(kNumberOfColors)) {
case 0:
colorName = @"blue";
break;
case 1:
colorName = @"pink";
break;
// Add more colors here
default:
break;
}
return colorName;
}

Add this to your touchesBegan method to test for a color match

    if (node == _square) {
// Extract the color name from the node name
NSArray *squareNameParts = [node.name componentsSeparatedByCharactersInSet:[NSCharacterSet uppercaseLetterCharacterSet]];
// Extract the color name from the node name
NSArray *backgroundNameParts = [_background.name componentsSeparatedByCharactersInSet:[NSCharacterSet uppercaseLetterCharacterSet]];
// Compare if the colors match
if ([backgroundNameParts[0] isEqualToString: squareNameParts[0]]) {
NSLog(@"Score");
}
}

All that's left is to create an SKAction that calls addSquareAndBackground, waits for one second, and then calls removeSquareAndBackground. Lather, rinse, repeat!

EDIT: Add this above your @implementation MyScene statement:

@interface MyScene()

@property SKSpriteNode *background;
@property SKSpriteNode *square;

@end

Removing SKSpriteNode when touched

Because you are writing the same variable. try:

func spawnBadGuy(){

let localBadGuy = SKSpriteNode(imageNamed: "redBall")
localBadGuy.name = "badguy"
localBadGuy.position = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
localBadGuy.setScale(0)
let scaleUp = SKAction.scaleTo(0.15, duration: 2)

let moveToSide = SKAction.moveTo(CGPoint(x: CGFloat.random(min: 0 + 50, max: self.size.width - 50 ), y: CGFloat.random(min: 0 + 50, max: self.size.height - 50 )), duration: 2)
localBadGuy.runAction(moveToSide)
localBadGuy.runAction(scaleUp)
self.addChild(localBadGuy)
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */

for touch in touches {
let location = touch.locationInNode(self)
let touchedNode = self.nodeAtPoint(location)

if touchedNode.name == "badguy"{

touchedNode.removeFromParent()
}
}


Related Topics



Leave a reply



Submit