Graphics and Games • iOS, macOS, tvOS • 41:12
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.
Speakers: Bruno Sommer, Sri Nair, Michael Brennan
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
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 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.
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 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. 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 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. 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, 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 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. 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. 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 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] 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 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. 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, 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.
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. 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 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 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 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 ]