Graphics and Games • iOS, OS X • 51:38
Gain a deeper understanding of the best practices to follow when building a SpriteKit based game. Get tips about game construction and see how to map the logical elements of your game into SpriteKit. Learn how data driven techniques can speed your development cycle, and discover how to achieve maximum performance for rendering, physics, and animation.
Speakers: Jacques Gasselin de Richebourg, Nick Porcino
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Hello everyone. My name is Jacques. I have the pleasure of managing this game technologist team. I'm here today to start you off for the best practices for building SpriteKit games. So, we're going to start this off with talking a little about the history of SpriteKit. It's now one year old.
You guys have made thousands of games in SpriteKit. And I'm sure you've seen some of the successful ones out there like "100 Balls" for example, even achieving the top one free spot. So, what you guys have done with it is absolutely amazing. And we hope that some of the new features we've added are really going to help you make that next game. So, let's go through what this talk is going to be about.
We're going to discuss scalability best practices to help you save on cost as you develop your game and also set yourself up for the future. We're going to talk about game structure best practices. And then, Nick is going to talk to you about performance best practices. So, let me dive straight in to scalability best practices.
So, what I'm talking about here is setting yourself up so that you can add content to your game with the smallest possible cost so you can collaborate with the rest of your team and also perhaps work better for the future, learning better techniques. So, one of the problems that you can see as a beginning game developer, and I used to be one of those, is you get really, really excited.
And so, you're starting on this idea that you have. And I mean the idea is right. You want to get your game going as fast as possible. So, you dive in your hardcode. Everything is done in one scene. All the references are done in one place in code. You hardcode level 1.
Level 2 was a bit more work to make it different. Level 3 looks the same because face it, you just did copy and paste. And-- well, you're probably stuck on level 4. You're thinking different-- it's going to take time. We're probably going to do copy and paste again.
The second problem is you tend to get tempted to write data as code, and here is just the example of some of you could get stuck doing even in SpriteKit. Obviously, SpriteKit is excellent. You probably won't get stuck with this, but you might get stuck with this in other APIs.
You're encoding things like the position of your nodes inside code. You're encoding rotations. You're probably even encoding the number of objects you have in code. You may be encoding properties like how many hit points you get, what types you're going to start, the colors in code. And what you end up with is that you made changing art assets meaning changing code, right? So, now the artist and the coder both have to make some changes that take time. The only visual feedback you get is your Build and Run, right? Iteration time has been worsened. And you're forcing your designers to be programmers because they need to edit code to change the game design.
On the second level here, you're also duplicating structural code. And code as data is not very efficient, right? There are better ways of describing data, namely data. And also, it's hard to change collaboratively because you're probably doing these edits in the same M or MM file for all the changes. So, your artist has changed another asset. Well, that's an M file change. So, in your virtual control system it looks exactly the same as the game design you're changing a parameter.
So, the solution. Well, you got a separate game content from game logic. That's the one we touched on earlier. You want to separate scene structure from assets. So, structure we're seeing might be different from the actual artwork delivered. And you want to separate data from code. We also want to make sure you're visualizing as early as possible. Build and Run is not the best system in the world to visualize what is done.
So, implementing solution consists of putting a game logic in MyScene.m. I'm referencing that here because that's what you get in the template. You put your game structure inside the SceneKit serialized file, this MyScene.sks. And you put your scene assets in separate SKS files that you're referencing. And if you can do sidecar data, we have a fantastic format called .plist which is XML when you edit, when you deploy.
So, the tools that we provided for you to do this in Xcode 6 is we also have a SpriteKit template both for Objective-C and Swift. The editor, as Norman showed earlier, let's you do visual feedback, visual editing, feedback is immediate. And you can also simulate the physics live. We also have, and have had for the long time, the ability for you to edit .plist files inside Xcode. And it's as simple as creating a .plist file, adding and raise dictionaries to them, and then reading them back into your code; it's super simple.
So, I'm going to show you a demo of the SpriteKit template and how we see this get set up both the wrong way and the right way. OK. So, let me show you here my very rushed example on making a sort of topple over the towers game. So, I made a little hammer throwing game here. I just throw them at totem and I get a score.
I was really excited about making this, so I just threw everything inside the game scene, OK. I'm sure now that you've never seen this before. You see here I'm referencing art right inside the scene file. I'm setting positions of things and changing the properties. So, I got this going really quickly. And it must seem it's totally wrong for you to do. Whatever you do to make money and get success is great. I'm just going to provide an alternative. So, this run did the job, that's great.
However, you could structure this slightly differently. So, here I have a scene which-first of all, let's make sure this operates in a similar fashion. OK. Yup. That's the same stuff. It seems to be the same game. What I have done here is I've used the SpriteKit template which provides you the start of separating the code and assets.
And the key here you're going to see is right on this line here: GameScene unarchiveFromFile. That seems kind of cool. What's actually happening? Well, the template here has set you up with some of these amazing APIs that we have, that we're already using from Foundation. It's called NSKeyedUnarchiver. This is how you load and save all data that's related to SpriteKit.
So, there's a super special piece of magic that we're doing in here. We're telling the KeyedUnarchiver to replace any instance of SKScene with our own class. So, this means that any SKScene that you've made, you can make it automatically load one of your own scene files. So, now we've typed logic together with assets, even though they're designed separately. OK. Now let's go to the actual game scene here. Notice the setupLevel1 is markedly smaller than it was in the previous example.
So, what's happening here? Well, we're using SpriteKit's powerful search features to enumerate through the content of the scene and by bringing upon no names and conventions with your artists, we could make it really easy to pull the logical elements out of the scene by name and hooking them up to code.
And here we said there's a totemNode and we're going to attach children based on level 1. So, let's go through the scene here. This is the basic structure of this first scene. That's a base. It's got a background and it's got a little empty node here which I named totem. So, in code I'm going to load up the scene. I'm going to fetch the node called totem.
At that node I'm going to attach level 1. And level 1 looks like that. So, I've separated out the structure of the theme, which is my game, with the contents of level 1, which is just a stack. And continuing this theme when I do my game logic, I keep using names in order to evaluate whether or not an object says totem. This means like the artist can add incidental objects but they're not named totem. Adding them to the scene don't affect the scoring of your game. So, just as a simple example of what you should do to separate out your game logic from the game lessons. OK, thanks.
[ Pause ]
OK. Let's recap this. So, you saw the SpriteKit template. What we're doing is we're taking scene file which, in this case is MyScene.sks, and your basic code file is MyScene.h and MyScene.m. And we're separating out the logic from the assets. The key here is to use NSKeyedUnarchiver, which some of you might not be familiar with already. You insert the class replacement, in this case arch setClass:MyScene.class forClassName:@"SKScene". This replaces any reference to SKScene inside the scene file with your own class. And it's as simple as that.
So, let's go on to Game Structure Best Practices, like you've seen in the example. Motivation here is of course get your game running on the first day. How are you going to know that your game is fun if you're not playing it? So, get it up and going on the first day.
You won't do this without compromising scalability, OK. So, don't rushing into it, and set yourself up to iterate collaboratively. Designers want to be designers, artists want to be artists, coders want to be all of the above. So, make your generic level, as I showed in the example. To make it simple, get your designer to commit to where the general gameplay is going to be happen, what object is interacting with what.
Add placeholder content. I'll go through that shortly. Hook up with interactions. So, this is you as a coder and the designer working out what interactions would need to be there. Get the game logic working. And then, of course the easiest part of all, which is finish the game.
All right. So, make your generic level. This should be logical layout only. So, if you don't have any artwork right now, don't worry about it. Place markers, where you think content is meant to go. So, if you have action going from left to right, well, structure your scene that way.
Place markers where you think the hero is going to begin, name that marker "hero". Place markers where you think enemies are going to start out a new scene, probably name them "enemy". And any logical layers you have, such as platforms or maybe the background. You want to make sure you're not putting anything accidentally on the background.
Then you move on, you add placeholder content. This is often what we called red boxing. So, pick a color that you like for your heroes. Blue perhaps is perfect. Add them as colored SpriteNodes. Don't bother with the texture right now, just put them in the right spot, right size. This is a key to your artist that you're saying I want this to be roughly the size over here, right, because your artist can only make crazy assets.
OK. Now, make the parent-child relationships here too. So, particle emission locations, if you're making a train game, it is highly likely that smoke is going to come out of a smoke stack and that smoke should probably move with the train. And so, I've set that up as a parent-child relationship. Next stop, hook up the interactions. So, this is the pass where you're going through the physics interactions and making sure that they're right.
So, for example if our object is meant to topple over or our object is meant to stay absolutely put when they're hit by the hammer for example. Sort out your collision masks, which objects are meant to interact with which and make sure that you simulate this right within Xcode. Get it right from the start.
Then you get the game logic working. And this is where you initialize your scene logic and game logic together. So, you're using those names that you set up in the scene and you're hooking them up which is search code within your actual game code. And we had a placeholder inside the sample before, which was a totem where we loaded a level scene and then replaced it wherever the totem empty locator was. This is where you want to do that.
So, here's a sample of me having the scene structure found on one side and the actual scene code on the right and I'm just making sure that my code matches what I think the scene structure is. So, of course just hooking it up is probably not going to make your game great and fun. You're going to make sure that it's doing what it's meant to be doing and that you've got unit stats loaded, scores and similar things.
There are two logical places within your scene to do this. There's one which is on first load which is called initWithCoder. And this is going to get called automatically by the KeyedUnarchiver. The second is didMoveToView and this is when you're presenting it as a scene on your SKView.
So, let's jump into the details with that. So, on first load, as I said, automatically called by NSKeyedUnarchiver. This is where you're reloading sidecar data. Sound, for example, AI, or any unit stats that aren't going to change. That's called initWithCoder, make sure you go super init or initWithCoder depending upon what your super class needs. And load up the enemy stats here. I just loaded NSArray from a .plist, very simple just a one-liner.
Next up, you can move to your node first shown in this message point. So, this is called an SKView.presentScene:is called with your scene. And this is a great place to cache your visual elements. So, SKScene is going to get loaded by the KeyedUnarchiver. And all the visual elements, the PNGs that are referenced in there are going to get loaded well before you get to here.
So, you can actually do quite a bit of work inside didMoveToView without worrying about a lot of loading costs and latencies. If you have a lot of visual elements you can interact with, cache them right here. In this case, I'm finding all the enemies and I'm saving them away in my enemy array.
So, the motivation for doing this is that you want to have a simple way of taking your logical scene elements and hooking them up to your code. There are two different methods for searching, childNodeNamed for finding a single element and enumerateChildNodesWithName for multiple elements and that's the results. I really can't stress it enough.
You saw in the previous slide just a quick hint of a search syntax that might not have been apparent to you at first. We have an Xpath-style search in your scene which, in this case, was //enemy. What that means is find all enemies from the root, recursive down. Some examples here: @"hero" which is find the child called hero, without recursive. //hero would find all the nodes in hero in recursive order.
We can also search by class, not just name but class. So, we can go //EmitterNode, we'd find all the emitter nodes in your scene. And you can also use blob-style partial matches, such as the wildcard here where it's anything, any node named starting with "he". Super powerful, and this is going to be your best way of lodging up-of linking up your logical scene with the actual game code.
And obviously that's what we did. In the sample that you saw before, we can see child node remain being used extensively to find the actual objects within the scene inside the game code. This is very fast, so you can do it often but we do recommend your cache results if it becomes a performance problem.
All right. Last step, I'm just going to gloss over this one because it's easy. Finish the game-that's totally up to you, of course. Add the artwork. You can then replace the textures on the red boxes, because hopefully your artist has delivered them at the right size. Add the levels that you need, which should be easy if you followed all the points, add any effects.
Norman showed you how easy it is to add shaders to things. You can also use Corelmage filters on the effect nodes to add that special image post processing effects. And of course, play test-iterate. I can't stress this enough: have fun. This is what games is all about. Make sure you're having fun when you're making a game. OK. Let me hand you over to Nick now to talk about performance best practices. Thank you so much.
[ Applause ]
Thanks, Jacques. Today I want to talk about my two favorite things, well, two of my favorite things. One, which is improving performance in order to squeeze down the amount of time that your game takes to execute 60 times a second and then increasing the awesome that you can squeeze into that space that you made by improving your performance.
So, I have a bunch of topics to go through and just to quickly give you a preview and then talk about understanding what is involved in drawing and where their time is going to go. I'm going to talk about actions and constraints, how they're the secret window into the underlying SpriteKit engine. And physics, there's a cost hierarchy there, it's just very helpful to understand, similarly for shape nodes. Talk a little bit about effects and some good to know facts about our lighting systems. So, drawing performance.
There's two things that impact your drawing performance in a big, big way. One is the choice of draw order, how it is that you submit your sprites to the engine, and two is sharing the resources that are used to accomplish that drawing. Now, SpriteKit has by default a sibling order draw rule which influence the standard painter's algorithm. There are two rules, your parent-- the parent draws first then recurses down and draws the children and then their children and so on as it goes. And children are rendered in the order that they appear in the child array.
Now, in this diagram here, obviously the helicopter has got at its very bottom some missiles, then the body and the rotors. And it's straightforward to create this in code just by adding the various parts. Now, this is very convenient for prototyping, but what is it do to drawing performance? So, here's my amazing game scene and it's going to have a bunch of helicopters in it.
The each piece as we recurse down the draw order comes down one at a time. It's a little draw call. And that's a lot of individual draw calls. It's going to take some time and you get the idea, let's just fill them all in there. Now, what SpriteKit gives you in order to avoid this problem is the magical third dimension.
So, let's have a look at what we can do with that. If we set ignoreSiblingOrder to YES, we can use depth to control the order. That's going to be pretty helpful. So, we've got the helicopter, we are going to arbitrarily set it on level 100. Let's stretch out the rotors and pull them towards the camera at level 1. Now, let's push the missiles back like that. So, now they're stacked.
Now, SpriteKit knows what you intended with your composite. It's not based on the order that was added in code or anything like that. It's based on something absolute, which is, where is my parent in space. So, what does that do for a drawing? Well, bit by bit, you can see that we've got a lot less draw calls because we're done already. So, that's great. And you've got a whole bunch of performance back right away especially if you have lots of objects, but you can go further of course.
Use texture atlases. So, here I've put all of the sprites onto one texture and how is that going to help me? Well, here's my magical scene again with lots of helicopters and boom, I'm done. SpriteKit can know that's one texture. It knows what order all the bits have to be drawn in and just batch it all up for the GPU and blast it on out and potentially with just one single call, so that's pretty awesome.
Now, there's a whole bunch of other things that can be shared and you should be aware of those things. We have a new normal map generation scheme. And you can see that there is a little flower block. And because SpriteKit textures are keyed off a name and normal maps were generated on the fly and without a name and just assigned somewhere.
If you're going to use that normal map on the block, you need to know-- let the engine know that it's generating a shared thing. So, generate your normal map, store it on a texture and then assign that texture all the sprites they're going to use it as opposed to every time you have that block asking for it to be generated.
Similarly, we've got some cool procedural noise which you might be like to using your shaders or to provide some sort of, you know, television snow effect or something like that. If you can reuse the noise at the scale that you've generated, cache that off because every noise texture that you make is going to consume memory, and once again, SpriteKit won't know unless if you tell it by sharing a variable in a cache pointer somewhere that it is actually the same texture it won't know and it won't be able to batch.
Couple of other similar points. If you reference shaders from files, SpriteKit will know that vortex.fsh is vortex.fsh everywhere you reference it. So, when you're prototyping, it's often very convenient to reference shaders from strings, so go ahead and do that during prototyping. But SpriteKit doesn't really have any way of knowing that genuinely these two shaders are the same even though the source code might be the same.
If you reference it from a file, then we can do all kinds of pre-analysis beforehand to just cache it all off and it's going to go that much faster because if all those helicopters have the same shader, once again, it knows it can just blast them all off like that. And then, finally, slightly esoteric point on batching here is we offer a number of blend modes such as additive or multiplicative or whatever.
Because changing a blend mode changes the GPU stage, we have to actually interrupt the drawing every time we change that stage in order to allow the bind to occur. So, a trick you can use, you can imagine for example if you had some sort of a misty forest scene with a layer of trees and then a layer of creatures and then another layer of trees and the trees have alpha in them.
If you put all those things on the same Z, like literally exactly the same Z, then SpriteKit will know these things go together with their blend mode and, for example, all of the trees in the front can go out in one draw call with one blend mode and that may or may not be just that little extra bit that you needed to get a little more time to squeeze in that little more awesome.
Now, there's tools to help you evaluate graphics performance. So, you don't just need to have like a mysterious mental model of these things. And there is actual, you know, quantitative things you can do to get some insight into what SpriteKit is doing behind the scenes. So, first of all, there's a number of flags on the view which you can turn on and off in order to get some insight.
There is the obvious frame counter, FPS, frames per second. There's a DrawCount which is actually going to tell you how many batches did I get; and there is your NodeCount which is how many SK-like SpriteKit nodes-actually went down the pipeline, and finally the QuadCount. If you're using something like a shape, it might be comprised of multiple subquads.
So, it's going to tell you that and I have just a quick, little thing here to show you that. I have all these gears and it's showing 12 nodes. You'd have to inspect my scene graph to discover why there's 12. But there's six gears, you can probably guess sort of what might be going on there. There's a heck of a lot of quads, like really a lot, I have 12 draws but it's running at 60 frames a second. So, I'm pretty happy with that right now for my magical gear game.
So, the other thing you've got is the GL frame, the GL frame debugger. So, there's all kinds of cool gauges and tell you what proportion of this and that is happening throughout your frame and it will give you some insight into what GL calls are being evoked on the fly.
And there's a heck of a lot of documentation and information about this. It's a really fantastic tool for understanding the runtime characteristics of your game. You'll notice as you run this thing what's going on in the hardware and corresponding to what's going on in your game and study that will give you good insight into what the engine is doing for you.
So, to kind of roll that all up. The idea here is compose your scenes as layers, common Z values are going to help you a lot for the reasons I mentioned. Put overlapping things in different layers in order to control the draw order instead of using the SiblingOrder and SiblingOrder = YES. Share your stuff. Blend modes go together. And use the HUDs and profilers to really understand what the engine is doing.
All right, next up: Actions and Constraints. As I mentioned in the introduction, cool thing about actions is they really are a magic window into the very guts of the SpriteKit execution engine. Deep down inside we continuously improve the performance of the various operations SpriteKit provides. Actions, or these little Objective-C things that you might think, "That might be heavy." But it's not.
It really queues up instructions for the execution engine, just a couple of bytes here and there, to tell you what's going to happen. And then it puts it off into our internal queues and whatnot to run. So, making action. Here I've got a node and I'm going to rotate by pi apparently for one second. You can chain them, group them, reuse them.
Here, the little airplanes that you might all be familiar with. And they're rotating continuously for a period of a second. They're moving around and they're scaling. It's really simple to set this stuff up. The execution engine is ticking it over. It's almost free. It's great to let the SpriteKit engine do this stuff for you rather than coding it all up in your update.
So, a little bit about sequencing. Say I've got something that I have got waiting off stage left, like a secret monster who will make his appearance in less than one second. And I can have it wait and then move. So, that's like a sequence. Also, when my monster comes, he might rotate and scale and then fade away because he was merely a ghost. So, that's the use of like an SKAction group.
And then you can compose those things as deeply and "complexively" as you like. Am I allowed to make up words? All right. So, in this case my monster is going to move. He's going to scale and rotate and he's going to fade away. Now, SKAction.h is your friend. This is the documentation here. And I'd like you all to carefully write these all down. No. Actually go look at the documentation because there's a lot of it. Now, there's a ton of power in there. It's a lot of fun to play with.
Constraints, and you're here for the previous section. Norman introduced it quite well. I have just got a quick little summary slide here. Constraints are another thing which gives you access to the high-performance engine running underneath SpriteKit. In this case it's running inverse kinematics. And this little scene was set up in Xcode. And very quickly just clicking and setting up a couple of parameters.
Here's another one that's actually new and cool. You can create a followpath node which just takes the CGPathRef. And if you've ever spent any time with Bezier's points and whatnot, the math is kind of hairy, and especially making things move at a uniform speed is particularly hairy. So, we went ahead and given you an SKAction to follow a path along a spline that you create, it's a Catmull-Rom spline if you care. And we'll move things along it at a constant velocity for you. Click, here we go.
All right. OrientToNode, similar sort of thing. You don't need to bother with, you know, arc tangents and all that sort of thing. The blue ball is moving around, the arrow is following it. And the other really cool thing about actions is they're kind of latent in memory when you create them. Build them once, take advantage of the fact that they're copying on add and that they run when you add it to a scene.
My example here is that he might have a spaceship or a monster that's going to enter from stage left in the same way every time and maybe follow a path and then leave. If you create that node with the spaceship and the action, cache it on a pointer and then copy it and add it to your scene, it will run every single time, and exactly the same way with very, very little overhead.
And another fun thing that's easy to overlook is the purpose of naming your actions. So, in this example here, what I've got is the idea that I have a sprite that is basically going to do something like say follow a touch point. I might run an action that is like "move to point".
If I give it the name "move", now I can override that action anytime I want just by using the same key. So, it might be in progress. If I run the action again with a new point, it will immediately segue without a hitch or stutter onto the new action and continue on.
And finally, if my monster is coming in here and then he saw that I had, you know, the Big Monster Ray Repeller 2000 and he decides he doesn't want to go all the way into the scene, we can just use removeActionForKey with his move key and that will cancel that action right away. And you can go and do something else. So, we don't have to wait for the execution now to play out to the end.
All right. Next, physics. Physics are a lot of fun. I really, really love the new simulation tool that we've got in Xcode because the hardest thing about working with physics is getting the parameters right. Now, we set things up with good defaults. For example, if you set a mass of 1 and a strength of 1 on a rigid body and a strength of 1 on a field, they're going to interact in kind of an interesting way out of the bat.
But that's just the starting point for your iteration. So, rigid bodies, I just mentioned rigid bodies. That's all the things in your game. They're bouncing, falling, rolling and sliding. The things that the physics engine has to do each frame are your rigid bodies, handling collisions and letting you know that they have occurred and physics fields.
Now, a primary thing for getting access to iteration and understanding what's going on is the new physics visualizer. So, let's just go back to my gear scheme here. I'm dropping in these wonderful gears. And wait a second. His teeth weren't acting properly. What's up with that? The other ones are interlocking but just not that one.
So, I'll turn on the visualizer and you can see, oh, that one is a circle. I made a mistake somewhere. So, I can go either in Xcode and click on my thing or, if it's procedural, I can look at my code and I can find out why did this one slips through the cracks. So, this is your-- This is your great first line of defense for just understanding what the dynamic behavior is if it's not what you expected. You can also use this mode for identifying quickly what is the cause of physics performance issues.
An object might be stuck in a wall and it might be an invisible wall because it was only there as a physics body. Now, you'll just be able to see that, oh, it's continuously pushing in and out of collision and can't escape. And the next steps to solve that problem will probably occur to you.
So, in order to take best advantage of physics in order to get performance, so you can squeeze down the amount of time that you're spending, you can squeeze in more awesome, is to understand the cost hierarchy. First of all, dynamic objects cost more, because their collisions need to be resolved and they need to be moved out of intersection, than objects that are static, like hopefully the stage that I'm standing on. So, if you can set an object to dynamic = NO, you're going to get a lot of performance back right away.
At the most efficient end of the scale are circles. You can have dozens of circles for every other kind or shape that you can have. And circles might work really, really good for a lot of things, especially if they're circular or they're small. So you're not going to really notice what the interaction is. The circle doesn't fit very well on that hammer.
That's a bit better. The hammer at least, you know, has edges and stuff. This is more expensive than the circle where you could have dozens of circles. You can have maybe, I don't know, eight of these. Just, you know, being a little bit silly there. And polygons are better.
This is probably going to be the one that serves a lot of complicated shapes the best. It's more expensive than the rectangle because there's more edges to compute. Compound objects need to be iterated through all the way for all the pieces. Lots of circles compounded are a lot cheaper than a lot of boxes compounded.
And finally, there is the perfect pixel alpha mask bodies. These are potentially cheap. If it was a circle, it's going to be cheap actually. But if it's got a convex or complex, convoluted shape, there might be a lot of computation involved there. So, just be careful with these. You need to pick the representation that best serves your game. You might find that the per pixel thing is actually better than having like say 20 little circles to describe something. But prefer things on the efficient side of the chart as much as you can, serve your gameplay.
All right. Collision Masks is another thing that you can use to regain performance, especially in a case like this. I've got these two jars, a white jar with white marbles and a green jar with green marbles. Now, the funny thing about this is they're pretty close together and it might be hard for the engine to actually understand that the white marbles have no way of getting in the green jar and vice versa. So, you can help the engine out. This is what the engine sees. It's like, for every one of those green marbles and every one of those white marbles, are they hitting? So, just imagine how many comparisons that is.
Putting the jar back again. Now, if you use a collision mask, give the white marbles mask 1 and the green marbles mask 2, you're going to get a whole ton of performance back. This is an order N squared problem. And so, by cutting the number of comparisons in half, well, do the math. A lot of computation that you can get back just through this simple experience.
All right. Norman introduced force fields very nicely in the previous one, so I'm not going to belabor what all the force fields are and their fields and things like that. But I am going to point out that they're building blocks, use them together and use actions to fade in and out.
I have a big, nasty particle system here. And what I've done is just made a whole bunch of force fields throughout the center of the screen. And I'm just using an action to modulate the strength of each force field so that they smoothly segue one into the other.
That's a lot nicer than just turning them on or off. Well, I love playing with these things endlessly. I had to throw that slide in. Now, here's another example of how force fields can help you out in reducing game logic and simplifying updates, similarly to the way that the actions or the window into the engine.
Force fields are another thing that you can use to replace game logic. So, you can kind of tell without even telling you what kind of a game this one is going to be. But let's just see what it looks like for a second. You can see the little guys are following the cursor around. And they're doing their darndest not to run into those green planet things. I guess my spaceships are nearly as big as a planet, that's kind of amazing.
It's hypnotic. OK. I think I can stop staring at that now. All right. Wait, that's the big reveal. Let's go back to the picture of the-of movie again, or maybe not. Before I show you that next slide, what I want to tell you is, using Xcode I made that level.
So, I used an idea like-that I like to call prefabs, like prefabricated objects, to make a little scene graph with the spaceship's picture and a force field right underneath it. I gave the force field on the spaceship a negative strength, so they push each other apart. I gave the planet of child, which is a force field with a negative strength, so that it pushes little spaceships away. I made the planets not dynamic and I made the spaceships dynamic. OK. Now, we can look at that code. All right. So, this is the whole entire of that game aside from the bit that I built in Xcode.
It's next to nothing and you're getting the same sort of behavior that you see in classic RTSs like, you know, Star Craft or whatever. So, what it's doing from top to bottom is just the target is where I tapped the screen and well, you know what the ship's position is. I'm calculating and I don't want to divide by zero so I'm checking for that little minimum length. I'm getting the velocity from the physics body. I'm finding out what direction the velocity is in. Little arc tangent gives me an angle to slap on Z-rotation.
I'm taking the position difference of where I want to go from where I am. Subtracting the velocity. Multiplying it by a magic number which I arrived at by iterating, and then I'm just applying an impulse using that calculated magic number. And you basically get what looks like artificial intelligence for having done nothing at all.
Now, if you want to learn more about that stuff, you can like search the internet for Boids. There is a wonderful body of work around these kinds of techniques. Now, just like physics debug drawing can give you insight into what's going on, here is, you know, the wizard behind the curtain.
I've turned on the field debug drawing. You can see the fields around the ships, the fields around the planets, and how they interact. And I don't know. It's a crazy amount of fun for like 10 minutes of coding. So, you all go out and make classic, cool spaceship games for me to download and play.
I don't want to let that slide go away because I'll just watch it forever. OK. So, physics, choose I don't know which way I'm supposed to gesture. I had cheap down at the-- that side of the screen. Choose the cheapest one that's going to best serve your game. Use the expensive ones judiciously to serve your gameplay.
Separate your groups with mask so the engine knows what your intent is because that's the only way that they can read your mind. And fields replace update logic, use a little creative thinking and imagination. And the debug drawing is going to give you all kinds of insight into what the engine is doing for you behind the scenes.
So, my next topic: shapes. I have a little refresher here on the difference between bitmap art and vector art. Anyone care to guess which is the vector art? Now, there's a cost hierarchy of shapes just like there's a cost hierarchy of physics. You know, what I'm going to say there. Choose the one that best serves your game.
These guys, in particular squares and rectangles, are really cheap. I'm going to say that sprites are way, way, way off the cheap scale beyond these guys but not that far off. You can use these things a lot where it's appropriate. These are just a bit more expensive. You can use them a lot.
These have had a ton of love put into them in this iteration of SpriteKit. And you can draw little mini maps and, you know, compass roses, all kinds of things like that with vectors that in previous iterations of SpriteKit you might have had them in the judicious column. Now, they're in the delicious column.
All right. Stroked curves, now I wish I could have, you know, a hockey stick to you show you the cost here. These things look really great and use a few of them and you're not going to really hit your performance at all. Use a bunch, you know, you might get a call from mom. Finally, filling them in costs even more. So, I just put a little bracket there so you know which is the cheap half and which is the expensive half. Use the thing that's appropriate to your game.
Just because I can't stop myself I made a fractal space pulling curve thing with tens of thousands of points in it and just to kind of say hey, they're cheap use them. All right. EffectNodes, use EffectNodes when you want to have a subtree of sprites or whatever that are all going together have some kind of effect applied to them, such as tinting or blurring. They have to be drawn offscreen and then brought back to the frame buffer for feeling. So, there's a cost. You can get really powerful Corelmage support. You can run very sophisticated filters and all kinds of amazing things, but it's expensive.
Use them when you need to. Shaders are an awesome option. Whenever you don't need to have things composited offscreen particularly and you can just go ahead and draw, shaders will save your performance bacon. If you want to just tint everything green, that's a really simple shader. You can just bypass, you know, the whole processing stack, just go right out to the hardware. So, learn about that. And shouldRasterize just gives us a hint about what can be cached and will hang around for a while and what needs to be recomposited every screen.
If you've got things that are going to be static for a while, for example, maybe you've got some kind of a HUD display that only updates once every 10 seconds but you want to have, you know, N cool glows and blurs and real distorts and all kinds of things going on, raster it out.
There's a couple of cool things that you can do here. You can create a texture from a node and just specify the node, ask the view to raster it for you and now you've got a texture, just slap that up, it's going to cost almost nothing. You have a CI filter. There is the other variant right there. All right. So, that's what I have to say about effects. Now, for lighting.
We have some good introduction from Norman in the last section about lighting. So, I'm going to talk about the performance costs. So, the first thing that you need to know is that, well, we're going to compute the lighting per pixel. So, the cost is there for proportional to the amount of lit pixels. You can optimize that with bit masks. If you say some things aren't going to be lit, then you're not going to pay that cost. So, that's what that's all about. Ambient light is free.
You can have 8 lights per sprite. Now, you're going to pay for, you know, 8 times the processing per pixel per sprite, so weigh if you want to do that and keep the cost down with the bit masks. Normal maps are cheap. The shader just sees those up, so stick normal maps on. They look great. Remember to share them.
Shadows are cost proportional to the number of lights. It's pretty much constant. So, if you turn on one shadow, that's going to cost you one. Two shadows is going to cost you two. So, keep the number of those things down. You potentially have to shadow the whole entire screen. So, they look really great. They're going to add a lot of impact and awesome. Use them, don't abuse them.
All right. So, we talked about drawing performance, understand what controls batching and draw order. And learn about the tools to get insight into what's going on. Actions and constraints are your window into the SpriteKit engine. We improve the efficiency all the time. The more you use actions, the more you're going to get performance back over time as we iterate the frameworks.
Physics, understand the cost hierarchy. Choose appropriately to serve your game play, same with shapes. Effects: understand the cost, use them wisely. And lighting is a cheap and easy way to extra awesome. And I'd like you all to make your games as awesome as possible. All right. So, today we talked about structuring your games for scalability, understanding how to set up scene graphs and scene graph snippets so that you can put things together in a data-driven away. And we talked a whole bunch about performance.
For more information, I encourage you to contact Allan Schafer and Filip Iliescu who are our games evangelists. The SpriteKit programming guide, recently refreshed, has a ton of interesting information in it. And of course, there's always the forums. And tomorrow I would love if you could all come and visit us in the labs and talk about what you've got going on and, you know, say hi. And with that, thanks very much and I'll see you all later.
[ Applause ]