Merging or flattening background nodes to improve game performance? - swift

I want to be able to improve my game's performance by somehow "flattening" my background nodes.
This youtube video demonstrates how I build up my background graphics.
But, I have my background graphics set up like this.
I use two textures like stamps and repeat then.
In this case... one mountain texture with a snowy top...
One mountain texture without the snow.
I alter the zPosition of these stamps for a "layering" effect.
I then use "fills" which are just SKSpriteNodes of solid grey to layer over parts that need a grey fill.
E.g. Before fill nodes are added
A colour screen with alpha is then added on top to give the mountain a faded looked.
E.g. Before
E.g. After
The game sometimes freezes up... but it doesn't freeze when I remove these graphics.
Is there a way to improve performance by merging or flattening my background graphics nodes based on zPosition as a scene is loading up?
How would I do this?
Would this improve performance?
Or what would be the best way to improve performance in my case?
I think this question is similar...
Merge all SKSpriteNode children into a single SKSpriteNode
But, how do I do this in Swift and take zPosition and alpha into account so I don't lose the layering effects?

Somethings you may want to do:
Separate your foreground and background into separate SKS files, and load them
into your main SKS file via SKReferenceNode
Convert these SKS files into textures instead of SKReferenceNodes via code using view!.texture(from: node)
Ensure your atlas is not being broken up in a way that adds to the draw count
Design your code in a way that not all nodes are on the scene, the more nodes the slower your code becomes.
If you have SKPhysicsBody's, try to use as few as possible. This can be done my merging multiple bodies together with SKPhysicBody(bodies:) or creating 1 body with a polygon that goes around multple objects.
Also, if your bodies are not moving via physics (Not by SKActions) then make sure that isDynamic is set to false
If you are running SKActions, make sure you use as few of these as possible. For example, if you have 4 different nodes travelling left at 10 points per second, you can put these 4 nodes into a parent node, and run the action on the parent node.

Ok so here is an example of what you have now.. all your nodes here like this:
How to execute the bitblit easily is to move all these under a new empty node called "background"
Now head to your swift file, and set up an placeholder variable for the new node we will create in code
class GameScene: SKScene {
var blitBackground = SKSpriteNode()
override func didMove(to view: SKView) {
}
}
Now , add this handy function that you can use in all of your projects if desired:
func blit(from node: SKNode) -> SKSpriteNode {
return SKSpriteNode(texture: SKView().texture(from: node))
}
and here is the method we will use to initialize our bitblit background, and remove the laggy one from the game:
func blitTheBackground() { // use in didMove
let laggyBackground = childNode(withName: "background")!
blitBackground = blit(from: laggyBackground)
laggyBackground.removeFromParent()
addChild(blitBackground)
}
Here is the completed scene:
class GameScene: SKScene {
var blitBackground = SKSpriteNode()
func blit(from node: SKNode) -> SKSpriteNode {
return SKSpriteNode(texture: SKView().texture(from: node))
}
func blitTheBackground() {
let laggyBackground = childNode(withName: "background")!
blitBackground = blit(from: laggyBackground)
laggyBackground.removeFromParent()
addChild(blitBackground)
}
override func didMove(to view: SKView) {
blitTheBackground()
}
}
and it reduced the node count from 10 to 4... hopefully, for you it will help with your draw count too!!
NOTE, this method is EXTREMELY effective with shapenodes!!
If you need to do this in a loop, be sure to place it in an autorelease pool to be sure that the memory is freed up after it is used. Creating textures from the view uses a lot of memory, so you can expect to get errors if you are not careful with it.
for i in 0..<5
{
autoreleasepool{
let laggyBackground = childNode(withName: "background\(i)")!
blitBackground = blit(from: laggyBackground)
laggyBackground.removeFromParent()
addChild(blitBackground)
}
}

Related

Multiple SKSpriteNodes as one Node in Swift

I'm trying to create a simplified Dr. Mario like game where I have a pill shaped object, each half with their own color.
The pills can be stacked on top of each other and I need to find out via collision detection if two similar colors have collided.
What I tried
I created an SKSpriteNode subclass where I generated half of the pill with its color and spawned two in my scene. I positioned them near each other and created a fixed joint between them. The problem is that I couldn't rotate them as if they were a single Node (there's an open question regardin this issue)
I created two SKSpriteNode subclasses. The first one is just like the one above, returning half a pill, but instead of getting called from the scene, these get called from the second subclass, which returns the pill as a single node into the scene. In this scenario, the rotation works, but I can't figure out how to create a joint between them and add that joint to the physicsWorld from outside the scene.
Either way, neither of my solutions seem okay. How would you go about creating the pill object?
I would just create an SKNode subclass with two SKSpriteNode references to the left and the right half of the pill and add these two sprite nodes as children.
Something like this.
class Pill: SKNode {
var left: SKSpriteNode?
var right: SKSpriteNode?
func setup() {
left = SKSpriteNode()
/* Setup left half of the pil */
addChild(left!)
right = SKSpriteNode()
/* Setup right half of the pil */
addChild(right!)
}
}
I would not bother having two nodes for the object. The colour is just the texture after all. I would create one SKSpriteNode subclass, one physicsBody and store the rest as attributes. Then on collision you do the calculation on sides and colorus and rotaion.
enum PillColor {
case red
case blue
case yellow
}
class Pill: SKSpriteNode {
var rightSide: PillColor?
var leftSide: PillColor?
var rotation: Int
}
In the SKPhysicsContactDelegate's didBegin(_ contact) method you can use the contactPoint property of the SKPhysicsContact to know where the two actually met.

Number of nodes are high after loading a TMX file in SpriteKit using JSTileMap

I'm trying to load a 32x22 map with tile size of H:32px W:32px created using Tiled. After following answers here and tutorials on how to load .tmx files using JSTileMap in SpriteKit, I attempted my own:
var worldNode: SKNode!
var tileMap: JSTileMap?
var backgroundLayer: SKNode!
func createWorld(){
tileMap = JSTileMap(named: "Room1.tmx");
if tileMap != nil {
backgroundLayer = SKNode()
backgroundLayer.addChild(tileMap!)
}
worldNode = SKNode()
worldNode.addChild(backgroundLayer!)
addChild(worldNode)
}
The map is successfully loaded. My question now is the number of nodes shown on the bottom right is over 700. It seems to be quite high or is it normal to have such number loading tmx based maps?
That's fine. You have 704 tiles all together (32x22) , so nodes count is correct. SpriteKit is capable to render hundreds of nodes in a performant way (using batch rendering) and you should not be worried about nodes count as long as number of draw calls, needed for scene rendering, are kept to low. To see this number, you can set SKView's showsDrawCount property to true, like this:
skView.showsDrawCount = true

Q: How do I move objects down the y axis as my player moves up?

Creating a game like Doodle Jump where my player is constantly being moved up by bouncing off of obstacles. Ive tried every trick in the book but nothing seems to be working/doing exactly what I want. Can anyone give me some tips?
iOS 9 introduced the Camera Node. Use SKCameraNode, which is a subclass of SKNode, and can be translated and rotated in same way.
So, instead of moving all of your background elements in the opposite direction of your hero/player, you can simply attach your scene's camera node to your hero/player and the rest is taken care of.
PS. You can also do cool stuff like scaling the camera size.
EDIT.
Happy to include an example.
First, make a camera constant in your scene.
import SpriteKit
class myFirstScene: SKScene {
let myCamera: SKCameraNode = SKCameraNode()
...
}
Then in your didMoveToView() function, assign the scene's built-in camera variable to the camera constant we made earlier.
override func didMoveToView( view: SKView ) {
camera = myCamera
...
}
Now, there are a few different ways to "attach" your camera to your hero/player. The first is to attach your camera node to your hero.
hero.addChild( myCamera )
I don't even know if it works that easily because my game uses something different, a simpler version is below.
update(){
camera!.zRotation = hero.zRotation
camera!.position = hero.position
}

SpriteKit- How to zoom-in and zoom-out of an SKScene?

If I'm making a game in SpriteKit that has a large "world", and I need the user to have the option of zooming in and out of the SKScene, how would I go about this? Or, to make things simpler, in the didMoveToView function, how can I present more of the world to the user's device's screen (without using world.runAction(SKAction.scaleTo(0.5)) or something)?
There's a SKCameraNode that's built specifically for this. The SKCameraNode defines the viewport into your scene. You create a camera node and assign it to the camera property of your scene.
let cameraNode = SKCameraNode()
cameraNode.position = CGPoint(x: scene.size.width / 2, scene.size.height / 2)
scene.addChild(cameraNode)
scene.camera = cameraNode
You can then create actions and run those actions on the camera. So to zoom in on the scene, you'd do this.
let zoomInAction = SKAction.scale(to: 0.5, duration: 1)
cameraNode.run(zoomInAction)
The cameraNode basically is a square node in the scene, that I think takes the proportions of the view by default? Cuz there's no size initializer. So when you make it smaller, the scene looks like it gets zoomed. To zoom out you'd make an action that increases the scale. Basically imagine a rectangle on your entire scene, and whatever is in the cameraNode's rectangle directly shows on your iPhone screen. You can also add moveTo actions and sequence actions and set timingModes on the actions same as if it were your normal spriteNode.
Here's the WWDC where the apple guy shows what I've just said. CameraNode bit is around 3 mins before the end.
https://developer.apple.com/videos/play/wwdc2015-604/
So, the best solution I could could find goes something like this. In the didMoveToView function, create an SKSpriteNode called World and make it whatever size you want your world to be. Then, write world.setScale(0.5) if you want a 50% zoom-out. However, if you have a player node that needs to always be centered in the screen, you'll need to add the following to your update function.
override func update(currentTime: CFTimeInterval) {
world.position.x = -player.position.x * (0.5)
world.position.y = -player.position.y * (0.5)
}

Changing parent node in SpriteKit

I'm new to SpriteKit. When I drag and drop "Color Sprite" from object library to make a new node. It gives the correct size (as you can see in the image below). Here the parent is the main scene not the blue one.
But when I change the parent node from the main scene to any other node (like blue one in my case) the new node gets larger however it's width and height remain same (shown below).
EDIT:
I'm running Xcode 7 beta 3.
I prefer you to do everything programmatically in sprite kit. I didn't understand what is going on there actually but if you want to make a shape like this, you should use SKShapeNode. SKSpriteNode is generally for making a node with a texture and animating that stuff etc...
//for example
let node = SKShapeNode(rectOfSize:CGSize(width:100,height:100))
node.position = CGPoint(x:500,y:350)
node.fillColor = UIColor.redColor()
self.addChild(node)
if you still have problems with the size, you can play with;
// for example;
node.xScale = 469
node.yScale = 300