I use SKScenes for different screens of my main menu. Each of these screens is rather complicated, sporting dozens of elements.
So each time user moves between those screens there is like 1-2 second lag before going next scene or previous scene as I recreate them each time.
Should I just store my scenes in some singleton after they are created so that next calls are faster?
Is it a good thing to do? Won't it be too memory-heavy?
Some optimization tips:
1) Your biggest memory hog is going to be the graphics. To keep this under control, use SKTexture object with a texture atlas. Always use a texture atlas because:
Texture atlases can improve memory usage and rendering performance. For example, if you have a scene with sprites drawn with different textures, Sprite Kit performs one drawing pass for each texture. However, if all of the textures were loaded from the same texture atlas, then Sprite Kit can render the sprites in a single drawing pass—and use less memory to do so. Whenever you have textures that are always used together, you should store them in an atlas.
2) Create your texture atlases based on levels so you do not use up memory for nothing. In other words, create an atlas for level 1, another one for level 2 and so on. According to the docs:
For a larger game, you may need to split the textures into levels or themes. Each level or theme’s textures are designed to fit in a specific amount of memory. When the player starts a new level, you preload all of that level’s texture objects. When the player finishes playing the level, the textures not needed for the next level are discarded.
3) You can use a (SKSpriteNode) singleton if you have a lot of the same animations used in different scenes. For example, an enemy present in all levels who has animations for idle, walking, running, jumping, attacking, etc... The same singleton can load all of your texture alases for your animations. You can also use a singleton to store all of your game sounds and thus further reduce load times but keep an eye on memory usage if you have a lot of sounds/music.
4) Setting the skView.ignoresSiblingOrder = YES; also gives an optimization boost. According to the documentation:
The default value is NO, which means that when multiple nodes share the same z position, those nodes are sorted and rendered in a deterministic order. Parents are rendered before their children, and siblings are rendered from eldest to youngest. When this property is set to YES, the position of the nodes in the tree is ignored when determining the rendering order. The rendering order of nodes at the same z position is arbitrary and may change every time a new frame is rendered. When sibling and parent order is ignored, Sprite Kit applies additional optimizations to improve rendering performance. If you need nodes to be rendered in a specific and deterministic order, you must set the z position of those nodes.
5) If your images do not absolutely require alpha, do not include it in your images.
6) Remove nodes from the parent if you no longer need them. Also make sure you remove any reference to them such as inclusion in any arrays. The rest is taken care of by SpriteKit as stated in the docs:
An SKTexture object is created and attached to the sprite. This texture object automatically loads the texture data whenever the sprite node is in the scene, is visible, and is necessary for rendering the scene. Later, if the sprite is removed from the scene or is no longer visible, Sprite Kit can delete the texture data if it needs that memory for other purposes. This automatic memory management simplifies but does not eliminate the work you need to do to manage art assets in your game.
In other words, delete a SKTexture object by removing any strong references to it, including:
All texture references from SKSpriteNode and SKEffectNode objects in your game
Any strong references to the texture in your own code
An SKTextureAtlas object that was used to create the texture object
Related
So, I am building a basic tycoon game. In this game I want to have a simplex 3D generated tile-map that the player can build on. I need the tile-map to be 3D (or have a 3D appearance) because I want to be able to use some realistic shaders on the 3D models the player is going to be able to build.
At first, I thought I could combine SpriteKit and SceneKit. I would generate the 2D SKTileMapNode and then allow the player to build the 3D buildings on top of that. But that wouldn't work as well because SKTileMapNodes aren't easily pannable or zoomable.
My second idea was to build an SKTileMap randomly (which I know how to do easily) and then use that as a reference to build a 3D scene. That would allow me to have full control over what goes where, but there is a catch. Each 3D block (representing a tile in the tileMap) would be treated as a node and cause huge performance issues, Unlike SpriteKits SKTileMapNode that treats the tile-map as a single large node once it is filled with the tiles.
I would prefer to not use an isometric SKTileMap because that wouldn't allow the player to be able to pan/zoom the map and thus, reduce the depth and feel that I would like to achieve with this game.
For instance, this is what I am going for (similar in build but completely different in style)
“Each 3D block (representing a tile in the tileMap) would be treated as a node and cause huge performance issues,”
I think your question should be “how to render a 3D tile map with good performance in Scenekit” because imo Scenekit is definitely the way to go here and it is certainly doable to prevent those “huge performance issues”.
For starters, how do you create a tile? If you use the builtin primitives you can get a huge performance increase by using a tile from a dae or obj file, or even by creating it programmatically.
If the tile is the same model throughout, you should add it only once, and then clone it for all the other tiles:
https://developer.apple.com/documentation/scenekit/scnnode/1408046-clone
(Note you will have to create a copy of the material and assign it to a clone to prevent it from being shared across all tiles).
Additionally, by adding them all to a single parent node, you can create a socalled flattened clone to combine all the tiles into a single node.
https://developer.apple.com/documentation/scenekit/scnnode/1407960-flattenedclone which significantly reduces the number of draw calls.
If that isn’t fast enough, another option is to create the entire map programmatically. That is, create all the vertices and create a SCNGeometry based on those.
Yet another, probably blazing fast option, would be to use 4vertices to create a plane, and then use a shader and displacement map to create the tiled map.
The part about cloning and flattened clones also applies to the buildings that have the same geometry.
And just for completeness, in case that wasn't obvious, you should set the https://developer.apple.com/documentation/scenekit/scncamera/1436621-usesorthographicprojection property of the camera to true to get that isometric look.
I'm new to ios game development field.
I have been going through the following apple tutorial multiple times but not getting the points
https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Sprites/Sprites.html
thanks
Directly from the SKTextureAtlas class reference :
Texture atlases can improve memory usage and rendering performance. For example, if you have a scene with sprites drawn with different textures, Sprite Kit performs one drawing pass for each texture. However, if all of the textures were loaded from the same texture atlas, then Sprite Kit can render the sprites in a single drawing pass—and use less memory to do so. Whenever you have textures that are always used together, you should store them in an atlas.
SKTextureAtlas Class Reference
animation can be hundred of files. atlas puts them in one large file.
reading one file (large with lots of images) is much more efficient then reading hundreds of files (with one image each)
rendering of one large image and then showing only part of it is much more efficient then rendering each file separately.
I have a pool of CCSprites numbering 1200 in each of two arrays, displayGrid1 and displayGrid2. I turn them visible or invisible when showing walls or floors. Floors have a number of different textures and are not z-order dependent. Walls also have several textures and are z-order dependent.
I am getting about 6-7 frames when moving which is okay because its a turn based isometric rogue-like. However, I am also getting a small amount of flicker, which I think is performance related, because there is no flicker on the simulator.
I would like to improve performance. I am considering using an array CCSpriteBatchNodes for the floor which is not z-order dependent but am concerned with the cost of adding and removing sprites frequently between the elements of this array, which would be necessary I think.
Can anyone please advise as to how I can improve performance?
As mentioned in the comments, you're using multiple small sprite files loaded individually which can cause performance issues as there is wasted memory being used to store excess pixel data around each of the individual sprites. Each row of pixel data in an OpenGL texture must have a number of bytes totaling a power of 2 for performance reasons. Although I believe OpenGL ES under iOS does this automatically, it can come with a big performance hit. Grouping sprites together into a single texture that is correctly sized can be a tremendous boon to rendering performance.
You can use an App like Zwoptex to group all these smaller sprite files into a larger, more manageable sprite sheets/texture atlas and utilize one CCSpriteBatchNode for each sprite sheet/texture atlas.
Cocos2D has pretty good support for utilizing sprite sheets with texture atlases and converting your code to using these instead of individual files can be done with little effort. Creating individual sprites from a texture atlas is easy, you just call the sprite by name instead of from the file.
CCSpriteBatchNodes group OpenGL calls for their sprites together, a process known as batching, which causes the operating system and OpenGL to have to make less round trips to the GPU which greatly improves performance. Unfortunately, CCSpriteBatchNodes are limited to only being able to draw sprites for the texture that backs them (enter sprite sheets/texture atlases).
Given one texture sheet is it better to have one or multiple CCSpriteBatchNodes? Or does this not affect at all the GPU computational cost in processing the non visible CCSprite quads?
I am thinking about performance and referring to this question and answer I got. Basically it suggests that I should use more than one CCSpriteBatchNode even if I have only one file. I don't understand if the sentence "Too many batched sprites still affects performance negatively even if they are not visible or outside the screen" is applicable also having two CCSpriteBatchNode instead of one. In other words, does the sentence refer to this "The GPU is responsible for cancelling draws of quads that are not visible due to being entirely outside the screen. It still needs to process those quads."? And if so it should meant that it doesn't really matter how may CCSpriteBatchNode instances I have using the same texture sheet, right?
How can I optimize this? I mean, how can I avoid the GPU having to process the non visible quads?
Would you be able to answer to at least the questions in bold?
First case: Too many nodes (or sprites) in the scene and many of them are out of screen/visible area. In this case for each sprite, GPU has to check if its outside the visible area or not. Too many sprite-nodes means too much load on GPU.
Adding more CCSpriteBatchNode should not effect the performance. Because the sprite-sheet bitmap is loaded to the GPU memory, and an array of coordinates is kept by the application for drawing individual sprites. So if you put 2 images in 2 different CCSpriteBatchNodes or 2 images in 1, it will be same for both CPU and GPU.
How to optimize?
The best way would be to remove the invisible nodes/sprites from the parent. But it depends on your application.
FPS drops certainly because of two reasons:
fillrate - when a lot of sprites overlap each others (and additionally if we render high-res texture into small sprite)
redundant state changes - in this case the heaviest are shader and texture switches
You can render sprites outside of screen in single batch and this doesn't drop performance singnificantly. Pay attention that rendering sprite with zero opacity (or transparent texture) takes the same time as non-transparent sprite.
First of all, this really sounds like a case of premature optimization. Do a test with the number of sprites you expect to be on screen, and some added, others removed. Do you get 60 fps on the oldest supported device? If yes, good, no need to optimize. If no, tweak the code design to see what actually makes a difference.
I mean, how can I avoid the GPU having to process the non visible quads?
You can't, unless you're going to rewrite how cocos2d handles drawing of sprites/batched sprites.
it doesn't really matter how may CCSpriteBatchNode instances I have using the same texture sheet, right?
Each additional sprite batch node adds a draw call. Given how many sprites they can batch into a single draw call, the benefit far outweighs the drawbacks. Whether you have one, two or three sprite batch nodes makes absolutely no difference.
In my game, there're plenty of enemies(subclass of CCNode) which animation frames are distributed into multiple texture files(enemy01.png, enemy02.png...) and enemies from different textures may be generated in the same scene.
I'd like to have a unified way to dynamically add a randomly generated enemy to the correct CCSpriteBatchNode, but a lookup table seems not so elegant. Is there any better way to handle it?
Thanks
Since each enemy uses its own texture, you can use one sprite batch node per enemy. So when you create enemy "X" it shouldn't be hard to get the corresponding sprite batch node from wherever you store it (perhaps an NSDictionary).
Keep in mind that one sprite batch node per enemy kind of defies the purpose, especially if the texture atlas for each enemy is rather small and not even close to the maximum allowed size (1024x1024 for old devices, 2048x2048 or more for iPhone 3GS and newer). If possible, cram as many images into one texture atlas and use one sprite batch node for it.