Sprite kit sprites loading performance improvement
I have had the same problem! The issue is in this line...
let element = SKSpriteNode(imageNamed: "obsticle1")
SpriteKit isn't smart enough to know that a texture was already created with that image. So what it is doing is creating that texture over and over again and that is expensive.
Instead create a texture outside of the loop first and then create the sprite node with a texture. Something like this...
let elementTexture = SKTexture(imageNamed: "objstical1")
for i in 0...300 {
let element = SKSpriteNode(texture: elementTexture)
element.hidden = true
obsticle1Pool.append(element)
}
Not only will this be a ton faster it will decrease your apps memory a ton...assuming it was the same issue I was having. Hopefully that helps.
SpriteKit - How to improve performance?
Is there any techniques I could try to further improve performance with SpriteKit
I'm going to focus on that portion, because you'll never achieve the same performance with a heavy, general-purpose library as you would by writing by hand exactly what you need (as you would working with bare-bones Vulkan code for example).
Optimizing SpriteKit
itself is also out of reach, what is, is.
What you can do is focus on your own code. Specifically looking at your update function you're iterating over every single object every single frame in the hopes that they're under your mouse, but the mouse position can only be in one place on the screen at a time. So if you partition your data in screen quadrants you're able to only test a tiny subset of all of those.
You can either do it in an uniform grid (like 10x10 or something, depending on your window shape), or write a full blown octree which partitions groups into more buckets the more they're clustered.
Correct way to import and create many large sprites: SpriteKit
So let me see if I can reasonably answer your question. Please keep in mind that while I have used SpriteKit, I'm not a big fan of it. And when I did use it, I did not use any of the tools Apple provided (eg. SKTextureAtlas
, sks
files, etc). Nevertheless, this should still be applicable. Best practices as per Apple? I have no idea.
Question: Import folder, or images?
If you use Xcode's means of generating atlases, you Add Files...
to your project a folder named XYZ.atlas
, where XYZ
will be the texture name. This folder contains all of your textures which will be in the atlas.
Question: Import to WHERE within the project?
Just has to be in your project. But you should have some organization Group
hierarchy in your project file.
Question: Load and reference them, how?
An example of loading would be something like (yeah, I know boo, Obj-C):
self.myTextureAtlas = [SKTextureAtlas atlasNamed:@"MyTexture"];
Inevitably, you will want access to the actual textures and you'll need to do something like:
self.tex0 = [self.myTextureAtlas textureNamed:@"tex0"];
A good tutorial is here: https://www.raywenderlich.com/45152/sprite-kit-tutorial-animations-and-texture-atlases
Here is also a screenshot that shows the .atlas
folder. It also shows a couple of tool generated atlas files.
So here you can see I have 2 Groups which are references to folder MyTexture.atlas and ProgressBar.atlas. In game, they will be called MyTexture
and ProgressBar
.
I have also included, just as an example the same atlases, but pre-built. You would not have both in your project, I only used it to show what including a pre-built would loo like. I generated them using TexturePacker. I'll get into why this may be a better option later on. TexturePacker also can create SpriteKit atlases instead of the atlas PNG.
In reality, an atlas is really just a texture with sub-textures are associated with it. The sub-textures are X/Y/W/H sections of the texture. The actual memory for the texture is "held" by the atlas. Understanding what an atlas is is a useful thing, because it allows you to think through how you would support it if you had to implement it yourself. Or enhance it.
Now let's go over how you would use this altogether to do something useful.And for this you're going to need a texture manager (aka TextureManager
), sprite manager (aka SpriteManager
) and a manifest of some sort.
The manifest is really some form of association between "sprite name" to atlas:sub texture pair. For example:
{
"progressbar": {
"atlas": "ProgressBar",
"subtexture": progressbarbacking"
},
"progressbarfillr": {
"atlas": "ProgressBar",
"subtexture": progressbarfillr"
}
}
In this case it is some JSON, but you can have whatever format you want. When I build my games, I have a build assets phase which generates all my textures and from that, builds a manifest. This manifest tells me not only what textures exist, but is used later on to find the correct association of a "sprite name" to the actual atlas and sub texture. "sprite name" is just some name you have associated meaning. It could be "zombie" for example.
You use a TextureManager
as your asynchronous loader. In addition, it is your inventory manager of all your textures. As an inventory manager, it will prevent you from double loading textures and also give you the correct reference to textures/atlases when requested (if they exist).
You would use the SpriteManager
to create a new SKSpriteNode
using the data from the manifest. For example:
SKSpriteNode *progressBar = [[SpriteManager sharedInstance] createSprite:@"progressbar"];
Here I've made it a singleton. Use whatever implementation you want. If you hate singletons, that is fine, this is just an example. You'll note that it returns a SKSpriteNode
. I see a lot of people making subclasses from SKSpriteNode
s. I never do this. To me, the graphic component is always a "has a". However, if you are doing an "is a", you can still do this. You just need to feed in the class you need. I'm considering the way of handling that out of scope for this question.
Now if you look at the manifest, you'll notice that progressbar
is associated with an atlas named ProgressBar
and a sub texture named progressbarbacking
. To get the texture you need, you'd have some implementation code in SpriteManager
like:
// NOTE the literal names would be variables which contained the unpacked association from progressbar
// literal strings are used to make the idea easier to follow
SKTextureAtlas *atlas = [[TextureMaanger sharedInstance] findAtlasNamed:@"ProgressBar"];
//Error check of course
SKTexture *tex = [atlas textureNamed:@"progressbarbacking"];
// Error check of course
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:tex];
Or perhaps you would just have a call:
SKTexture *tex = [[TextureManager] sharedInstance] texNamed:@"progressbarbacking" atlas:@"ProgressBar"];
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithTexture:tex];
And there you have it. A means to get sprites from atlases.
Best practices as per Apple? Dunno, but it is along the lines of what I do.
Some people will complain that there will be dictionaries involved and some of this will be "slow". And yes, there is a penalty for this. And reality, I don't use a dictionary for this either, but it is easier to get the idea across as the dictionary. Also keep in mind that I consider the usage of this to occur during loading phases and very little during game play, if at all. One of the tricks to performant games is pre-loading all or as much of the data you need prior to actual game play.
Oh, going to why I pre-build the atlases. So part of your question was organization of textures to atlas. And that is why. I like to see the generated atlas and understand what the size is and what is in it. Additionally it makes downloadable atlases easier.
As an optimization, you would want to try and put textures in which are all drawn relatively the same time. For example, it would make sense to have all HUD items in the same atlas versus mixing HUD with background.
how to preload texture atlas to improve performance and reduce lagging? swift, spritekit
I'm not sure what imageLoad
does, but here's one way to preload and reuse a texture.
Add the following code to your SKScene subclass
let ballTexture = SKTexture(imageNamed: "Ball")
Modify your addBall
method to create your ball sprite from a texture by
func addBall() {
let ball = SKSpriteNode(texture: ballTexture)
...
I recommend you create a performSelector
SKAction
to add balls at fixed intervals instead of adding them in the update
method.
Lastly, I suggest you move self.physicsWorld.contactDelegate = self
and self.physicsWorld.speed = 1
from addBall
to the didMoveToView
method, so they aren't called repeatedly whenever you create balls.
Sprite-kit: How to position sprites on a circular path
Like I said, you need to know where is the center of the path (in your case that is CGPoint(x: frame.midX, y:frame.midY)
which is "center" of the screen) and you have to know the radius (you have it already calculated when you was creating the path) and you need an angle that the ray from center (frame.midX,frame.midY)
to the point on the circumference (coinX,coinY)
makes with positive x axis:
import SpriteKit
class GameScene: SKScene {
let player = SKSpriteNode(color: .purpleColor(), size: CGSize(width: 30, height: 30))
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// Adding the big circle
let runway = SKSpriteNode(color: .orangeColor(), size: CGSize(width: 150, height: 150))
runway.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))
addChild(runway)
// Adding the player
player.position = CGPointMake( CGRectGetMidX(frame) , (CGRectGetMidY(frame) + runway.size.width/2) )
addChild(player)
// Calculating the initial position of the player and creating a circular path around it
let dx = player.position.x - frame.width / 2
let dy = player.position.y - frame.height / 2
let radius = (runway.frame.size.width / 2) - 20.0
let radian = atan2(dy, dx)
let playerPath = UIBezierPath(
arcCenter: CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame)),
radius: radius,
startAngle: radian,
endAngle: radian + CGFloat(M_PI * 4.0),
clockwise: true)
let follow = SKAction.followPath(playerPath.CGPath, asOffset: false, orientToPath: true, speed: 200)
player.runAction(SKAction.repeatActionForever(follow))
let numberOfCoins = 8
for i in 0...numberOfCoins {
let coin = SKSpriteNode(color: .yellowColor(), size: CGSize(width: 10, height: 10))
let angle = 2 * M_PI / Double(numberOfCoins) * Double(i)
let coinX = radius * cos(CGFloat(angle))
let coinY = radius * sin(CGFloat(angle))
coin.position = CGPoint(x:coinX + frame.midX, y:coinY + frame.midY)
addChild(coin)
}
}
}
Just a sidenote : In Sprite-Kit the angle of 0 radians specifies the positive x axis. And the positive angle is in the counterclockwise direction. The source - Building Your Scene.
Preloading textures in SpriteKit
One Single Texture Atlas
Put all your assets into a single Sprite Atlas. If they don't fit, try at least to put all the assets of a single scene into a single Sprite Atlas
Preloading
If you want you can preload the texture atlas in memory with this code
SKTextureAtlas(named: "YourTextureAtlasName").preloadWithCompletionHandler {
// Now everything you put into the texture atlas has been loaded in memory
}
Automatic caching
You don't need to save a reference to the texture atlas, SpriteKit has an internal caching system. Let it do it's job.
Which name should I use to reference my image?
Forget the name of the file image, the name you assign to the image into Asset Catalog is the only name you will need.
How can I create a sprite from an image into a texture atlas?
let texture = SKTextureAtlas(named:"croc").textureNamed("croc_walk01")
let croc = SKSpriteNode(texture: texture)
Optimizing SpriteKit
Yes, avoid using textureNamed:
(frequently) while the game is running. Even with preloaded textures it does take time that will accumulate.
Instead cache the needed textures locally (in the class using them) in individual ivars or NSArray or NSDictionary ivars.
Major iOS9 performance issues with Sprite Kit
It seems the majority of iOS9 issues are addressed in the latest iOS9.2 beta & Xcode 7.2 beta.
Thank you Apple for finally addressing the problem. Too bad I spent 2 months working on work-arounds.
I've been posting on Apple's forums, submitting bugs and communicating with a support technician. It's a shame that at no point did Apple offer any clarity over what issues they were aware of, and what the Sprite Kit team were addressing.
Still, at least it seems the majority of Sprite Kit issues are now resolved, and a career change is no longer necessary :]
Related Topics
iOS 7.0 and Arc: Uitableview Never Deallocated After Rows Animation
How to Use Nsurlsession to Determine If Resource Has Changed
Reordering Uitableview Without Reorder Control
How to Register Undomanager in Swift
Will iOS Awake My App When I Receive Silent Push Notification(When App Is Not in Running State)
What Does $0 Represent in Closures in Swift
Multiple Uialertcontrollers in iOS
How to Remove Black Edge on Uiimageview with Rounded Corners and a Border Width
Fit Image of Random Size into a Uiwebview (Ios)
Crashlytics Does Not Show Crashes
Code Signing Issue in Xcode Version 8
Making Phone Calls on the Iphone
iPhone Only App Rejected for Not Running on iPad
Swift - Add Gesture Recognizer to Object in Table Cell
How to Use Phcachingimagemanager
Undefined Symbols for Architecture Armv7 for Cocoapods Libraries
Safari Web View Opening When Logging to Fb Through iOS 9
Xcode5:The App References Non-Public Selectors in Payload/<Appname>.App/<Appname>: Setattribution: