Friday, August 12, 2016

SpriteKit From Scratch: Advanced Techniques and Optimizations_part 2 (end)


3. The Animation Loop

Before each frame of your game renders, SpriteKit executes a series of processes in a particular order. This group of processes is referred to as the animation loop. These processes account for the actions, physics properties, and constraints that you have added to your scene.

If, for whatever reason, you need to run custom code in between any of these processes, you can either override some specific methods in your SKScene subclass or specify a delegate that conforms to the SKSceneDelegate protocol. Note that, if you assign a delegate to your scene, the class' implementations of the following methods are not invoked.

The animation loop processes are as follows:

Step 1
The scene calls its update(_:) method. This method has a single NSTimeInterval parameter, which gives you the current system time. This time interval can be useful as it allows you to calculate the time it took for your previous frame to render.

If the value greater than 1/60th of a second, your game is not running at the smooth 60 frames per second (FPS) that SpriteKit aims for. This means that you may need to change some aspects of your scene (for example, particles, number of nodes) to reduce its complexity.

Step 2
The scene executes and calculates the actions you have added to your nodes and positions them accordingly.

Step 3
The scene calls its didEvaluateActions() method. This is where you can perform any custom logic before SpriteKit continues with the animation loop.

Step 4
The scene performs its physics simulations and changes your scene accordingly.

Step 5
The scene calls its didSimulatePhysics() method, which you can override with the didEvaluateActions() method.

Step 6
The scene applies the constraints that you have added to your nodes.

Step 7
The scene calls its didApplyConstraints() method, which is available for you to override.

Step 8
The scene calls its didFinishUpdate() method, which you can also override. This is the final method you can change your scene in before its appearance for that frame is finalized.

Step 9
Finally, the scene renders its contents and updates its containing SKView accordingly.

It is important to note that, if you use a SKSceneDelegate object rather than a custom subclass, each method gains an extra parameter and changes its name slightly. The extra parameter is an SKScene object, which allows you to determine which scene the method is being run in relation to. The methods defined by the SKSceneDelegate protocol are named as follows:
  • update(_:forScene:)
  • didEvaluateActionsForScene(_:)
  • didSimulatePhysicsForScene(_:)
  • didApplyConstraintsForScene(_:)
  • didFinishUpdateForScene(_:)
Even if you don't use these methods to make any changes to your scene, they can still be very useful for debugging. If your game consistently lags and the frame rate drops at a particular moment in your game, you could override any combination of the above methods and find the time interval between each one being called. This allows you to accurately find whether it is specifically your actions, physics, constraints, or graphics that are too complex for your game to run at 60 FPS.

4. Performance Best Practices

Batch Drawing
When rendering your scene, SpriteKit, by default, runs through the nodes in your scene's children array and draws them on to the screen in the same order as they are in the array. This process is also repeated and looped for any child nodes a particular node might have.

Enumerating through child nodes individually means that SpriteKit executes a draw call for each node. While for simple scenes this method of rendering does not significantly impact performance, as your scene gains more nodes this process becomes very inefficient.

To make rendering more efficient, you can organize the nodes in your scene into distinct layers. This is done through the zPosition property of the SKNode class. The higher a node's zPosition is, the "closer" it is to the screen, which means that it is rendered on top of other nodes in your scene. Likewise, the node with the lowest zPosition in a scene appears at the very "back" and can be overlapped by any other node.

After organizing nodes into layers, you can set an SKView object's ignoreSiblingOrder property to true. This results in SpriteKit using the zPosition values to render a scene rather than the order of the children array. This process is far more efficient as any nodes with the same zPosition are batched together into a single draw call rather than having one for each node.

It is important to note that the zPosition value of a node can be negative if needed. The nodes in your scene are still rendering in order of increasing zPosition.

Avoid Custom Animations
Both the SKAction and SKConstraint classes contain a large number of rules you can add to a scene to create animations. Being part of the SpriteKit framework, they are optimized as much as they possibly can be and also fit in perfectly with SpriteKit's animation loop.

The wide array of actions and constraints provided to you allow for almost any possible animation you could want. For these reasons, it is recommended that you always utilize actions and constraints in your scenes to create animations rather than performing any custom logic elsewhere in your code.

In some cases, especially if you are needing to animate a reasonably large group of nodes, physics force fields may also even be able to produce the result you want. Force fields are even more efficient as they are calculated alongside the rest of SpriteKit's physics simulations.

Bit Masks
Your scenes can be optimized even more by using only the appropriate bit masks for nodes in your scene. In addition to being crucial for physics collision detection, bit masks also determine how regular physics simulations and lighting affect the nodes in a scene.

For any pair of nodes in a scene, regardless of whether or not they will ever collide, SpriteKit monitors where they are relative to each other. This means that, if left with the default masks with all bits enabled, SpriteKit is keeping track of where each node is in your scene compared to every other node. You can greatly simplify SpriteKit's physics simulations by defining appropriate bit masks so that only the relationships between nodes that can potentially collide are tracked.

Likewise, a light in SpriteKit only affects a node if the logical AND of their category bit masks is a non-zero value. By editing these categories, so that only the most important nodes in your scene are affected by a particular light, you can greatly reduce the complexity of a scene.

Conclusion

You should now know how you can further optimize your SpriteKit games by using more advanced techniques, such as texture atlases, batch drawing, and optimized bit masks. You should also be comfortable with saving and loading scenes to give your players a better overall experience.

Throughout this series, we have looked through a lot of the features and functionality of the SpriteKit framework in iOS, tvOS, and OS X. There are even more advanced topics beyond the scope of this series, such custom OpenGL ES and Metal shaders as well as physics fields and joints.
Written by Davis Allie

If you found this post interesting, follow and support us.
Suggest for you:

Complete Beginners Guide to iOS Development - Build 10 Apps

The Complete iOS 10 Developer - Build Real Apps with Swift 3

Swift 3 and iOS 10 The Final Course Learn to Code like a Pro

No comments:

Post a Comment