WWDC2014 Session 608

Transcript

X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
>> 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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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 ]