WWDC2016 Session 608

Transcript

[ Music ]
[ Applause ]
>> Good morning, everyone.
My name's Bruno Sommer.
I'm a game technologies
engineer here at Apple,
and this is What's
New in GameplayKit.
So last year, we
introduced GameplayKit,
Apple's high-level
gameplay framework,
and what GameplayKit
is is a collection
of common architectural
patterns, data structures,
and algorithms that enables our
developers to make really great
and compelling gameplay
in their games.
We want you guys to think about
GameplayKit as your toolbox
for great gameplay, so
regardless of the type
of game you're making, whether
it's a platformer or an RPG
of game you're making, whether
it's a platformer or an RPG
or a city builder, there's
something you can find
in GameplayKit to make
your life a little easier
and to make your gameplay
a little stronger.
So last year when we
introduced GameplayKit,
it was made up of
seven major systems --
things like entities and
components, state machines,
and our game quality
random sources.
This year, we're making
improvements to pathfinding,
agents, and game AI, and we're
also introducing three new major
systems to GameplayKit.
We have a really powerful
spatial partitioning system
that's going to let you get
really great performance
out of runtime queries
in your games.
You must have a really rich
procedural generation system
that's going to let you
make really compelling
runtime content.
And this year, we've also
integrated GameplayKit
into our Xcode Game
Editor, so now a lot
of the workflows previously
that you could only do in code,
now you can do right in
the Editor data side,
no recompile necessary.
So we have a lot to
talk about today.
I'm going to jump right in
with what's new in pathfinding.
I'm going to jump right in
with what's new in pathfinding.
So last year, we introduced
our obstacle graphs.
These are our graph type
that deals with a set
of impassable obstacles
in your game world.
And under the hood, we use
a line of sight algorithm
to map the passable area
between those obstacles.
Now, this method
is very powerful.
It results in really good
quality paths, but especially
for larger game worlds and game
worlds that have a large number
of obstacles, these can
be really computationally
intensively to calculate
and really memory
intensive to store.
So this year, we're providing
you with an alternative.
We're introducing GKMeshGraph.
Now, this is very similar
to our obstacle graphs.
Again, we're dealing with a set
of impassable obstacles
in our game world.
But now instead of
using line of sight
to calculate the passable
areas between those obstacles,
we're actually going to
triangulate that space.
We're going to make a
triangle mesh out of it
such that every passable point
in your game world
is represented on one
and only one triangle.
So this new triangulation
method results, still results
in really good quality paths
but has the added benefit
in really good quality paths
but has the added benefit
of being really fast
to calculate
and really low overhead
to store,
especially for really
large game worlds.
In addition, you have
a lot of flexibility
with where nodes get
placed on these mesh graphs.
You can place them at triangle
centers, triangle vertices,
and on triangle edges and
all the combinations thereof.
Let's look at a quick
code example
of what using a mesh graph
looks like in GameplayKit,
and this is going to look
very familiar to those of you
that have used our
obstacle graphs.
They're solving the same
problem, just different ways.
So here at the top, I'm going to
go ahead and make my mesh graph.
I'm going to pass in
a buffer radius of 10.
Recall that this buffer
radius is related to the size
of your agents that are
actually doing the pathfinding
in your world.
We're going to artificially
increase the size
of your obstacles under the hood
to compensate for
that agent size.
Here we're also going
to pass in two points --
(0, 0) and (1000, 1000).
This is the span
of my game world
that this mesh graph
is going to represent.
Next, I'm going to set
the triangulation mode
on my mesh graph.
This is that flexibility
with where nodes get placed
that I was talking
about earlier.
that I was talking
about earlier.
Here we're going to specify
that I'd like nodes to be placed
at triangle vertices
and on triangle centers.
And lastly, we're
going to add our set
of obstacles into
the mesh graph.
We have a set of obstacles
associated with our game world.
Then we're going to
call triangulate.
This actually commits those
obstacles to the graph,
run the underlying
triangulation algorithm,
and then we're good to go.
This graph is ready for use
in pathfinding in our game.
So in addition to
our mesh graphs,
this year we're introducing
custom node classes
to pathfinding.
A number of our graphs
automatically instantiate
their nodes.
That's our grid graphs,
our obstacle graphs,
and our mesh graphs.
Now at initialization time,
you can optionally specify
a custom node class for them
to instantiate instead.
And this is really useful if you
need to attach any custom data
or logic to your nodes, which
is sometimes useful depending
on the game you're
trying to make.
We call the appropriate init()
when we would've generated
our original node type.
And all these graph classes
now support Objective-C
and Swift generics, so
no casting is required
when you query your
custom nodes.
So that's what's new in
pathfinding this year.
Let's go ahead and move on
on what's new in agents.
A little bit of a refresher on
what agents are in GameplayKit.
They are autonomously moving
entities controlled by a set
of goals and behaviors,
and they're under a number
of realistic physical
constraints --
things like velocity, mass,
obstacle avoidance,
and path following.
On the right here, you see
you have a number of goals
at your disposal to achieve
the behavior you're looking
for in your game.
And this is things like
seeking and avoiding
or wandering and fleeing.
So previously, agents
were purely in 2D.
This year, we're
excited to announce
that we're bringing
them fully into 3D.
The class is GKAgent3D and the
interface is extremely similar
to its 2D variant.
The key differences are that
position is of course a float 3
and rotation is of course
a flow 3 by 3 matrix.
And all the same goals and
behaviors are supported.
So a couple things to note
here with this transition.
GKPath has been changed to
support both 2D and 3D points
with regards to our
path following goals.
And with regards to obstacles
in our obstacle avoidance goals,
those, if you're going to
use those obstacles in 3D,
they still live on
a single plane,
so you need to pick a plane
that makes sense for your game.
So in addition to bringing
our agents into 3D,
this year we're introducing
behavior composition.
We have a new class
GKCompositeBehavior that's a
subclass of GKBehavior,
and this is a collection
of weighted behaviors.
This is really similar
to the relationship
between behaviors
and goals previously.
Behaviors are a weighted
set of goals.
So these are fully nestable,
so now you can do really
interesting nested behavior,
behaviors in your game, and
this also makes them much easier
to maintain, especially
if you're working
with a large number of
behaviors in your game.
Let's take a look at
a quick code example
of these composite
behaviors in action.
At the top here, I'm going
to make a flocking behavior
by combine an align, a
cohere, and a separate goal.
by combine an align, a
cohere, and a separate goal.
Next, I have some obstacles
and enemies in my game world
that I'd like my agents
to avoid, so we're going
to make an avoidance behavior
by combining an avoidObstacles
and an avoidEnemies goal.
Then I'm going to combine
those two behaviors
into our new composite behavior,
effectively combining
them into one.
And lastly, I'm going
to make my agent,
I'm going to set my
composite behavior
as the agent's behavior,
and now we're good to go.
The next time we
update this agent,
it's going to correctly
simultaneously attempt
to achieve both of those
sub-goals or sub-behaviors.
So that's what's new
in agents this year.
Let's move on and talk about our
new spatial partitioning system.
So a little bit of background
on why spatial partitioning
might be important to your game.
A lot of times when we're going
high-level gameplay programming,
we ask a lot of spatial
questions about our game world,
things like, how many
enemies are near the player?
Or where are all the
items in my world?
Or what projectiles will
hit the player this frame?
Or what projectiles will
hit the player this frame?
Now, especially for larger
game worlds or game worlds
with a large number of game
objects, answering these types
of questions can be expensive.
In gameplay programming, we
often speed up these sorts
of spatial queries using a form
of caching called
spatial partitioning.
So a little bit of an overview
of what we're providing
with our spatial
partitioning system.
This is a set of tree-like
data structures that allows you
to cache your game
objects spatially.
You add objects to these
tree-like data structures
and they get grouped
into hierarchies
and buckets under the hood.
And then future queries
on these objects are made
implicitly faster.
This year, we're introducing
three such data structures
for you spatial partitioning
needs.
We have R-trees,
quadtrees, and octrees.
Let's dive a little deeper
into these data structures.
Let's talk about R-trees.
Now, what an R-tree is,
it's a tree data structure
that has a number of
hierarchical buckets.
Whenever you add an object
to an R-tree, it gets fitted
into one of those buckets.
Now, all these buckets have
a bounding box associated
with them that is the sum
of the bounding box of all
of the children that
are in that bucket.
Now, R-trees have a special rule
that when these buckets grow
too large, they need to split,
and this is a user-configurable
parameter
of just how large
these buckets can grow.
And we have a number of
strategies at our disposal
to decide how these
buckets should split.
We can simply have them or we
can try and optimize for linear
and quadratic distance
or try and reduce overlap
of the resulting buckets.
I'm going to give you
a quick visual example
of what building a
simple R-tree looks like.
Let's say I have a space game
with some spaceships
and some asteroids.
I'm going to add a
spaceship to my R-tree.
It gets fitted to a bucket
and it's just simply the
bounding box of that spaceship.
And then I'm going to add a
couple asteroids to that bucket,
and you notice it grows larger
to encapsulate those objects.
I've specified a rule in
this particular R-tree
that these buckets need to
split when they grow larger
than three objects, so
I'm going to go ahead
and add a fourth
object to that bucket.
We've grown too larger.
Now we need to split.
And we're just going to do a
simple linear distance split,
and we end up with
two resulting buckets.
Again, I'll add a couple objects
to the bucket on the right.
We've grown too large.
We need to split.
And again, we'll do a
linear split and end
up with two resulting buckets.
That's the gist of how
R-trees work under the hood.
So let's move on and talk
about quadtrees and octrees.
I'm going to address
these singularly
because they're solving the same
problem, just quadtrees live
in 2D and octrees live in 3D.
The interface is identical.
These are tree-like data
structures that have a number
of levels and hierarchies,
and at each level,
space is subdivided evenly.
Here on the right, I have
an example of a quadtree.
And you see in the upper
left, I've subdivided
that quadrant once, and then
in that new subdivided
quadrant's upper left,
I've subdivided again.
So quadtrees and octrees have
a max cell size associated
with them, and that controls
just how deep these trees can
grow and just how small
those cells can get.
grow and just how small
those cells can get.
Now, when you add an object
to a quadtree and octtree,
it gets placed into the smallest
cell that it fits in entirely.
And a small note on
this maximum cell size,
this is really related, this
value is particular important
to the performance gains
you're going to see
out of these data structures,
so you should pick a cell size,
or a max cell size that
makes sense for your game.
Typically, this is
on the order of some
of the smaller game
objects in your game world.
Again, I'll give you a visual
example of building a quadtree.
Same examples, spaceship
and asteroids.
I'll insert a spaceship
into our quadtree.
Gets placed into the lower
left quadrant two levels down.
I'll add some bigger
objects, and they get,
they live one level up.
Take special note of that
asteroid on the left.
You see it sort of straddles
the quadrant boundaries.
It's actually going
to live one level
up because it doesn't fit neatly
into either of those cells.
And lastly, I'll add
some smaller objects,
and you see that they
live three levels down.
So that's the gist of what's
going on under the hood
when you use a quadtree
or an octtree.
Let's look at a code example
of a quadtree in action.
Let's look at a code example
of a quadtree in action.
So at the top here, I'm
going to make my quadtree.
I'm going to pass in a
quad, which is the area
in my game world that I want
this quad tree to represent.
Here we're going to cover the
span between (0, 0) and (1000,
1000) in my game world.
I'm mostly going to specify
a minimum cell size of 100.
No cell in this quadtree's going
to be smaller than 100 units.
So I also have some
enemies in my game world.
I'm going to go ahead and
add them to my quadtree.
Notice here that all these
enemies are associated
with a quad of their own.
This is where that enemy
is in our game world
and where it's going to
end up in our quadtree.
And lastly, I'm going to
run a query on our quadtree.
I'm going to ask my quadtree
to give me back all the objects
that are between (0, 0) and
(1000, 1000) in that quadtree
and therefore in my game world.
And it just so happens that
all three of these enemies are,
and I'm going to get all three
of them back in my query.
So that's spatial
partitioning in GameplayKit.
Let's move on and talk about our
procedural generation system.
So a little bit of background
on why procedural generation
might be important to you.
I'm sure we're all familiar
with premade content in games.
This is things that we
make before the game is run
or before the game even
ships, and this is things
like artist design, or
designer designed levels
or artist developed
textures and characters.
And these are great assets.
They really work
for a lot of games.
But for other types of
games and particular genres,
you run into problems because
these sort of assets are static.
They don't change at
runtime very much.
So especially if I'm
looking for a random feel,
every time I play my
game, it feels new,
I can't really use these
kinds of static assets.
So what I'm looking for
is procedural content,
and this is things like
randomly generated worlds,
procedurally generated
textures or height maps.
So we're looking to make this
procedural content in games.
What we're really looking for is
a source of coherent randomness.
A lot of these random elements
I'm trying to make are spatial
in nature, things like worlds
and textures and height maps.
in nature, things like worlds
and textures and height maps.
So we need a source
of randomness
that makes sense spatially,
that actually has an underlying
spatial pattern to it.
Now, you might say
to yourself, well,
I can just use a random
number generator, right?
I can pull some values
from my RNG,
make my random content,
and I'm good to go.
Anyone that's ever tried to do
that very quickly runs
into some hurdles.
Outputs of RNGs tend to
fluctuate very wildly.
It's difficult to have
meaningful spatial relationships
between subsequent calls
and it's also very
challenging to have determinism.
Every time I randomly
generate my content,
I want it to appear the same
way if I'm given the same seed.
So we're looking for this
source of coherent randomness.
One such source of
this is called noise.
Now, what noise is, it's a
function that takes inputs
and gives output values,
but there's some rules
to that relationship.
For small changes in input, I
get small changes in output,
and for large changes
in input, I get random
but still spatially
meaningful changes in output.
but still spatially
meaningful changes in output.
There's some underlying
pattern to this noise source.
And noise functions
are also infinite
for the whole range of inputs.
It continues infinitely,
and they're deterministic.
Given the same input, I
always get the same output.
So once we have this noise
function, we can then sample it
at intervals that are relevant
for my game and for the type
of content I'm trying to make.
So if I'm trying to
randomly generate a world,
this might be coordinates or
tile indexes or biome indexes.
Or if I'm trying to
randomly generate a texture,
this might be texels
or pixels and so on.
So a little overview
of what we're providing
with our procedural
generation system
and our noise system in general.
You have a number of noise
sources at your disposal
to sample and make meaningful
content in your game,
and this is things
like random-ish noise,
things like Perlin
noise and Voronoi noise,
and also geometric noise
sources, things like billows
and spheres, ridges and
cylinders, and also some sources
of constant noise like
the checkerboard pattern
or the constant noise function.
So you can then combine noise
sources in a noise object
So you can then combine noise
sources in a noise object
and perform a number of
transformations on them.
And these are things like
combining noise sources
or translating, scaling,
rotating noise sources.
So once we've combined
them in some meaningful way
into a noise object, we
can then sample a region
of that underlying noise
map, that noise function,
in a noise map, get our samples,
and then make our game content.
So let's dig a little deeper.
Let's talk about
our noise sources.
Now, all of our noise
sources output values
between negative 1 and 1.
We'll talk a little bit
more about this later.
And they all have parameters
to tweak their various
noise outputs,
that underlying noise
function, so for our more random
and coherent noises like Perlin
and Voronoi, these can be seeded
with a GKRandomSource and they
also have a number of parameters
on them to tweak their
underlying pattern.
And for our more geometric noise
sources, there's parameters
to alter their shapes, so the
size of spheres and cylinders
or the frequency of
ridges and billows.
So once we have some
noise sources we like,
So once we have some
noise sources we like,
we can then combine them
into a GKNoiseObject.
Now, this has all the functions
necessary to transform, combine,
and modify our noise sources,
and a lot of common mathematical
and logical operations
are supported.
So if I'm trying to combine
noise sources, I can add,
multiply, min or max,
but if I'm trying
to transform a single noise map,
I can scale, rotate, translate,
or I can modify it by
taking the absolute value,
clamping, inverting.
So once we have our
noise the way we like,
we can them sample a
region of that noise,
that underlying noise
function, using a GKNoiseMap.
You specify an origin
and a size.
This is the region of
that underlying noise map
that we're sampling.
And you also specify
a sample count.
How many times do we sample that
noise function in this region?
What's my fidelity at
which I'm sampling?
So then once we have
our region sampled,
we can then get the value
at any given position
on that noise map, and again,
the range is in negative 1 to 1
like I mentioned before.
And at runtime, you can
optionally overwrite values
And at runtime, you can
optionally overwrite values
as needed if your
game world changes.
So I realize that this
is a lot to take in.
I think the best way
to illustrate this is
with a visual example.
Let's say I wanted to randomly
generate a world for my game,
and I want to model it
after Earth's biomes.
I want it to have
a realistic feel.
Deserts, forests, arctic
zones, things like that.
One, this is one method you
might use to accomplish that.
Here I've generated
two Perlin noise maps,
and the one on the left I'm
going to call a moisture map.
At any point in my game world,
I can look into this map
and determine how wet or
how dry my game world is.
And on the right, I have what
I'm calling a temperature map.
At any point in my game world,
I can look up into this map
and decide how hot or
cold my game world is.
So some things to note here,
and we'll come back to this.
On the moisture map, you
see I have a very dry spot
on the right.
That's that black smudge.
And a very wet area on the left.
And again, these
colors correspond
to that output I
was talking about.
Here black is my negative 1's
and white is more
of my positive 1's.
And on the right, notice I
have an extremely cold spot
at the top.
That's that black smudge again.
And a very warm sort of
right side of my noise map.
So I'm going to specify
some rules
on how I actually combine these
in some meaningful
way for my game.
Here I just have
a simple 2D graph.
On the vertical axis,
I have moisture,
and on the horizontal
axis, I have temperature.
So I can use these rules
to decide the intersection
of those two maps,
so if I have a spot
that has a really
high temperature
but really low moisture,
I'm going to end
up with something like a desert.
Or if I have something with
really high temperature
and really high moisture,
I'm going to end
up with something
like a rainforest.
And so there, and sort of
on the colder end of things,
I have tundras and arctic zones.
And then in the middle,
I have things
like the more temperate
biomes, things like forests,
savannahs, and grasslands.
So using those two maps I made
and combining them
using these rules,
I get something like this.
You see it has a nice,
realistic feel to it,
You see it has a nice,
realistic feel to it,
and some things to note.
On the right, you see we have
a really big desert that sort
of bleeds into some grasslands
and then bleeds into
a forest area.
That corresponds with that
dry spot on our moisture map
and that really warm spot
on our temperature map.
And sort of on the upper left,
you see we have a really big
tundra with some arctic spots.
Then on the upper right
and the lower left,
we have some small
rainforests corresponding
with really high temperatures
and really high moisture.
So this is just a really
basic example of some
of the cool stuff you can do
with procedural generation.
Here we just used two simple
noise maps, combined them
with some really simple rules,
and got some pretty
decent output.
Now, I'd like to call my
colleague Michael Brennan
up to tell you about
what's new in game AI.
Michael?
[ Applause ]
>> Thank you, Bruno.
Hey, everyone.
I'm Michael Brennan.
I'm a game technologies
engineer here at Apple,
and I'm really excited to share
with you today what
we're bringing
to GameplayKit this
year for game AI.
to GameplayKit this
year for game AI.
Last year with GameplayKit,
we introduced the
Minmax strategist.
This is a great AI solution
for all kinds of games
that guarantees an optimal
search for your game state.
It guarantees this by
having an exhaustive search
of that state space combined
with the scoring function you
provide for every given state
in the game to give you the best
possible move for your entity
to make at a certain point.
The exhaustive nature
of the Minmax strategist does
make it a little bit prohibitive
to use for games with larger
state spaces, however.
Games like Go or
chess, for example.
That's why this year
I'm excited to bring
with you the Monte
Carlo strategist.
Monte Carlo strategist
is a best first search
of the state space combined
with the random sampling
of that state space
to give a great move
for you opponent to make.
It does this by first selecting
a player move using exploration
versus exploitation to choose
that move, then simulating
versus exploitation to choose
that move, then simulating
out new games from that move
until it reaches
an end condition --
either a win or a
loss or a draw --
which then propagates
back up the tree.
It doesn't guarantee the optimal
move quite like Minmax does,
but it does converge
on that optimal move.
Monte Carlo strategist is fast.
It guarantees a good
performance for even games
with incredibly large state
spaces like Go, for example.
And given that it only
needs that end condition,
something your games
probably already provide,
it's very simple to
implement in your game.
And it is approximately
optimal, so while it may miss
that optimal move Minmax would
find, it is approximate to it
and it will converge on
that move given the time.
Let's go over some of the
elements you need to use
to incorporate this
into your game.
With GKMonteCarloStrategist,
you need to provide a budget.
This is the amount
of times it'll do
that four steps we
mentioned earlier.
And you need to provide
an exploration parameter.
Now, this is a value between 0
and 1 that states whether or not
on selecting a move you want
to explore unvisited nodes
or whether you want it to
exploit nodes it's visited
and found to be very winning.
And you need to provide
the game model of course.
This is something
you're familiar
with if you've used
GKMinmaxStrategist in the past.
Now let's look at a
brief code example.
So here we've got our
game model, GoGameModel,
which we're going to
hold a reference to,
and our Monte Carlo strategist,
which we're going to initialize
and hold a reference to.
We're first going to set the
Monte Carlo strategist game
model to point to our
game model, and next,
we're going to give it a budget.
We're going to say around 100.
This means it'll do
that four steps --
the simulating [inaudible]
propagating -- 100 times.
And then we're going to set
the exploration parameter to 1.
This means we want it
to be very explorative.
And then we're just simply
going to call for the best move
And then we're just simply
going to call for the best move
for our active player in that
game state, find that best move,
and apply it back
to our game model.
It's just that easy.
This year, I'm excited to tell
you that we're also allowing you
to make your own
custom strategist.
We implemented a new
protocol called GKStrategist,
and you simply conform to
it, giving the game model,
game model update,
game model players,
and implementing find best move
for player, and you can use this
like you would any other
strategist we provide for you.
So that was what we were
bringing to strategist.
Now let's talk about something
else -- decision-making.
There are many ways to
model logic in your game,
many of which GameplayKit
already provides support for.
Your enemies need to be able
to make decisions considering
the vast amounts of state,
and they need to be able to
make these decisions quickly.
As you can see here, we have
this little button jumping game.
Just in this simple game, your
opponent would need to consider
where you are, where every other
enemy is, where the buttons are,
where you are, where every other
enemy is, where the buttons are,
who owns the buttons at
the current point in time,
whether they're jumping,
whether the enemies are jumping,
where they are in the level.
It's quite a lot of
state to consider.
Decision trees are a simple
method for making decisions.
They're a tree-like data
structure which makes them easy
to visualize and debug, and
they can be either handmade
or learned.
GKDecisionTree gives
you a low overhead
for determining your action.
It's completely serializable and
it's very flexible, allowing you
to make nodes that will
make decisions that random
with certain weights
for branches or by value
if a certain branch is a
value like true or false
or by satisfying
predicates even.
It's extremely flexible,
allowing you to do a lot.
Let's go over a brief
code example.
So as you can see here,
we've got our tree.
We're going to initialize
it with a root attribute,
asking if we're near the button.
And then we're going
to grab a reference
to that root node for later.
After that, we're simply
going to create branches off
of that root node -- one for
if we are near that root,
that button, in which
case we're going to jump,
and one if we're not
near that button,
in which case we're
going to want to wander.
And we're going to
grab a reference
to that wander node as well.
With that wander node,
we're going to create
a few branches --
one with a weight of 9 we're
going to move left at that point
and one with a weight of 1
we're going to move right.
Now, this is additive,
which means that for the left
branch we move with the weight
of 9, that means it
has a 90% chance given
that there's a total
weight of 10 there,
and the right move has a
10% chance of occurring.
Then we're simply going to pack
the state into a dictionary
and pass it into
findActionForAnswers method
on the tree to get our action.
Decision trees can
also be modeled.
You simply supply
the gameplay data,
and it will find the
decision-making behavior
in that data and
fit a decision tree
to that decision-making
behavior.
As you can see here
in our matrix,
we have a top row
which is dark gray.
That's the attributes.
And the interior
matrix is our examples.
That's what various points of
the game look like for gameplay.
And on the right-hand side, we
have the actions we performed.
That's simply what we did
at those various
points in the gameplay.
You pass this into the
constructor for GKDecisionTree
and it will fit a decision tree
to that gameplay
data you've recorded.
Let's look at what this
might look like in game.
So here we have my
player, the light green
to turquoise colored player,
playing against the
dark blue player using
that handmade decision
tree we showed earlier.
As you can see, it's missing
out on some of the things
that we're doing that
make us perform well.
Let's look at a different
example.
Here you'll see it
behaves quite a lot more
like how we were
behaving earlier.
Like I said, you just simply
record your gameplay data,
pass it in, and you can model
behaviors like you would use.
So that's what we're
bringing this year
to game AI for GameplayKit.
It's awesome and I'm
excited to share it with you,
and now I'd like to invite to
the stage my colleague Sri Nair
to tell you more
about GameplayKit
integration into Xcode.
Sri?
[ Applause ]
>> Thank you, Michael.
Hello, everyone.
My name is Sri Nair, and I'm a
game technologies engineer here
at Apple.
So when we introduced
GameplayKit last year,
it was exclusively
driven by code.
You had to create the
constructs, do the hook-up,
and tweak the properties and
their values all in code.
And that can be inefficient
for many obvious reasons,
so I'm happy to say that we
are improving that situation
by introducing a more
data-driven workflow
for GameplayKit by
integrating it
with Xcode and SpriteKit Editor.
As you know, editor integration
helps with [inaudible]
As you know, editor integration
helps with [inaudible]
on your game features
much faster.
They also help to separate
your engineering workflow
from the design workflow.
So here are the four main
features coming to the editor
to help accelerate your
GameplayKit development.
Number one, entity
and component editor.
Number two, navigation
graph editor.
Number three, scene
outline view.
And number four, state
machine quick look.
Let's dive deeper into
each one of these features.
What's the component editor?
Let's recall that an entity
and component system
is a design pattern
where a game object is
represented by entities
and their behavior is
represented by smaller
and independent components.
And this provides for
better structuring
and usability of code.
And they also tend to be easier
to maintain and to extend.
And they also tend to be easier
to maintain and to extend.
So now with the component
editor, you can assign entity
and components to your
nodes right in the editor
and tweak the properties all
in the editor that's providing
for an editor-based,
data-driven workflow.
The editor is closely
integrated with code
and supports auto-discovery
of component classes
and properties.
For example, let's say you
wrote a movement component class
that's derived from GKComponent,
added a few properties,
and annotated with the newly
introduced GKInspectableKeyword
for them to show up in the UI.
And the component editor will
automatically detect these
components that you have
added and show up in the UI,
and now you can simply select
those components of your choice
and assign to the nodes.
Once you add the component, the
properties are auto-populated
with the corresponding
type of [inaudible],
and now you can adjust
these properties
and now you can adjust
these properties
and preview the changes right
in the editor without having
to quit the editor or recompile
the code that's leading
to a much faster iteration.
And all of these updates
are saved in a JKC
and under the SKS file.
And all the unchanged
property values use the default
setting code.
The GKEntity to the
nodes connection is made
under the hood through
a GKSKComponent.
And the UI supports all
the common property types,
such as float, int,
bool, et cetera,
and that's component editor.
Now, let's move on to the
navigation graph editor.
As Bruno mentioned earlier,
navigation graphs known
as GKGraphs are used
in pathfinding
to find an optimal way
for an object to get
from point A to point B.
And with the navigation
graph editor,
now you can create GKGraphs
right in the editor.
now you can create GKGraphs
right in the editor.
You can add or edit
nodes, make the connections
between them just by clicking
and dragging in the same window.
And these GKGraphs are
saved in the GKScene
that you can later retrieve in
code and use for pathfinding.
I also want to mention a highly
useful feature of we introduce
to SpriteKit Editor
that's quite useful
for GameplayKit development as
well called scene outline view.
It outlines the scene elements,
their parent-child hierarchy.
Most standard operations are
supported there like add, edit,
rearrange, delete, et cetera.
The navigation graphs you add
in your scene also show up there
in the scene outline view.
It also can be used
for locking the nodes
and changing the visibility.
It also comes with
the context menu
for more selection-specific
operations.
Quite handy.
And last but not least,
state machine quick look.
So just to refresh,
we introduced GKStateMachine
last year, and it allows you
to represent some kind of
execution flow in your game.
And it has many applications in
games such as in AI, animation,
UI, level sequencing, et cetera.
And up until this
point, you had no way
of previewing what these
state machine looked like.
It was quite hard to
understand the connection
between the states,
the execution flow,
or what state it is in
currently, so to help with that,
we are integrating a state
machine preview tool right
into Xcode Debugger's
quick look feature.
This allows you to put break
point in code where you want
to see the state machine and
click on the quick look icon,
to see the state machine and
click on the quick look icon,
and it pops up a
visual representation
of for your current
state machine.
And it shows the states,
the connection between them,
and the current state
is highlighted as well.
Here are a few more
examples of state machine
as seen in quick look.
And with that, I would like to
demonstrate the editor-based
workflow of GameplayKit.
[ Applause ]
So here I have a, we're going
to try to build a simple game
where a player picks up
balloons, paint balloons,
and throws at an enemy
that's simulated by game AI,
and enemy can do the same.
So we have a basic scene here.
As you can see in the
scene outline view,
we have a background and a
player, a bunch of balloons.
we have a background and a
player, a bunch of balloons.
So first, we will try to add,
it's a pretty basic,
no actions going on.
It's very static scene, so we'll
start with adding some movement
to the player using
the keyboard.
So for that, I have added a few
components here, but we'll look
at the movement component
that we talked about earlier
as a few properties that helps
with movements such as speed,
friction, acceleration,
et cetera.
And you have annotated
that with the GKInspectable
so that you can treat those
properties in the UI later.
Similarly, I have a
player input component,
and we will assign these to the
player by going to the scene
and looking in the
component editor.
So on the inspector area
on the right-hand side,
I have the newly
introduced component editor.
Now, I can select the player
and click on that plus button
to add these components
to the node.
to add these components
to the node.
So we'll go ahead and add
the player input component
and the movement component.
And we'll see what
we get with that.
So I would expect
the player to be able
to move with the keyboard.
That's great, so can
move in all directions.
And except you can, you'll
notice that it doesn't stop
at the boundaries because I
haven't added any collision
to the player yet, but I do have
a collision component added,
which essentially adds the
physics body to the player node.
So I will go ahead and
assign that to the player.
And while we are at it, I will
also assign a fight component
that I added which allows you
to pick up balloons and throw.
So let's see what
that looks like.
Yay, now I can pick
up the balloons,
and he's stopped
at the boundaries.
and he's stopped
at the boundaries.
That's awesome.
We'll go ahead and do the same
to the enemy, so for that,
I had a game object, enemy
object created in the scene,
but I had set it to invisible,
so I'm going to enable it
in the scene outline view.
And go ahead and assign the
components to the enemy.
So in this case, the difference
is it's a enemy input component
so that it picks up the game AI
rather than use the keyboard,
and similarly, movement
component, collision component,
as well as the fight component.
And with that, I would
expect the enemy also to pick
up the balloons and,
yeah, he got me.
That was quite easy for
him, but we're going
to make the gameplay a
little bit more interesting
by dropping some balloons into
the scene using a drawn object.
So I have a drawn object that I
will make visible in the scene
So I have a drawn object that I
will make visible in the scene
and go ahead and add
a drawn component,
which basically does the
dropping of the balloon as well
as follow a certain path.
So I want to add a
navigation graph to the scene,
and that's as simple as
going into the object library
and typing in "nav graph."
Now, you can just simply drag
and drop the nav
graph to the scene.
So we'll just make the
navigation graph a little bigger
to demonstrate the feature.
So here's the navigation
graph editor.
And while we are at it, we'll
also change some properties.
We'll set the health to be 2
and the movement for the player,
speed up a little,
give some advantage
to the player a little bit,
just a level 1, so, hey,
to the player a little bit,
just a level 1, so, hey,
you got some advantage.
And enemy will get
health of 2 as well.
And let's see what we have.
You know, you can see that the
drone is dropping more balloons
that I can pick up and throw.
He got me and I got him
once, but let's see.
Yay. All right.
I'm sure my son will have a lot
of fun playing this
game [applause].
That pretty much demonstrates
the new editor-based workflow
for GameplayKit.
Let's switch back to the slides.
So to recap the session,
this year,
we have introduced
many compelling
and useful features
to GameplayKit.
In the beginning, Bruno talked
about the next spatial
partitioning system
about the next spatial
partitioning system
for efficient spatial
queries in your game.
The new procedural
generation system
for using various
noise functions
to create more dynamic
content in your game,
as well as improvements
to existing systems
such as pathfinding and agents.
And Michael talked about
the additions to game AI
with gameplay strategists
and decision trees.
And I finally covered the newly
introduced editor-based workflow
of GameplayKit for
faster iteration.
I hope you find these features
useful and we can't wait
to see what you come
up with next.
And here is the URL for this
session to check out for later.
Number 608 is the
session number.
And here are a few sessions
on related technologies
that you might be
interested in attending.
What's New in SpriteKit,
SceneKit, Rendering,
Game Center, and Game
Technologies for Apple Watch.
Thank you for coming,
and we hope you enjoy the
rest of your conference.
[ Applause ]