GameplayKit provides developers a collection of essential tools and techniques used to implement modern gameplay algorithms. Learn what's new in GameplayKit and check out advances in pathfinding, autonomous agents, and game AI, as well as many enhancements supporting GameplayKit in Xcode. Tap into new capabilities for 2D and 3D spatial partitioning, and explore noise-based procedural data generation useful for height maps, natural textures, and more.
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
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
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.
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
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.
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
A number of our graphs automatically instantiate
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.
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?
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
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.
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.
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.
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.
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
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,
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,
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
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,
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.
Thank you, Bruno.
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.
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
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
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,
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
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.
Thank you, Michael.
My name is Sri Nair, and I'm a game technologies engineer here
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]
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.
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 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
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.
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.
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,
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.
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.
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.
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.
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
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,
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
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
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.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.