Dive into the tools and technologies used to construct the DemoBots developer sample. Gain a practical understanding of how DemoBots implements its gameplay logic with GameplayKit and visuals using SpriteKit. See how the sample integrates On Demand Resources and other system services.
DAVE ADDEY: Hello. Welcome to Deeper into GameplayKit with DemoBots.
We created a new game sample this year called DemoBots.
If you would like to play along at home, you can download the sample today from developer.apple.com/spritekit.
This game takes advantage of lots of the things you learned about in the What's New in SpriteKit session and Introducing GameplayKit. Check it out if you couldn't. We couldn't make a game called DemoBots without giving you a demo. Let's take a look and see how the game plays.
We start a new game, you're PlayerBot, this guy here.
Your job is to find and fix all of the bad TaskBots on each level before the clock runs out.
Here we have two good TaskBots, you can tell they're good, because they've got green faces.
If I run around the corner here, we find the first bad TaskBot. He's a GroundBot in this case. When he spots me, he will charge toward me and attack me.
I lose some charge.
That's okay. I have a beam to zap him with. After I zapped him for a few seconds he's fixed and green. Let's see where he's going.
Here we have another bad TaskBots, I have to fix him as well.
There are two more on this level that I need to fix before the timer runs out.
Unfortunately they have a tendency to attack one another as well.
They can turn each other bad.
That's what happened there.
Once all of them are fixed the level is complete.
There's a level 2.
Level 2 introduces a new character, FlyingBot, here is a FlyingBot on the side hanging out, doing his thing.
If you find a bad FlyingBot, they have a different attack.When we bump into him, he does a blast attack affecting everybody within a certain radius.
The good news, if you fix one, they do a good attack that will cure any other FlyingBot within range.
Follow him over here.
He's almost certainly going to bump into another bad one. He avoided him. Good. I can cure this one as well.
He's almost certainly going to do a really, really silly thing, to go all the way around here to a great big nest of the things around the corner.
This is almost a certainty.
We can use his benevolent blast after he's been made bad, he can cure one, two, all of them in one go. One more to go.
Oh no! This is not going well.
Then the level is complete.
So that's DemoBots.
I would like to take a look at tools and technologies we have used to make this game a reality. You saw our TaskBots had lots of animation states they went through. A thing we tried to do is keep the textures, images we needed for those to a minimum to keep the app size realistic. We decided to keep the new action editor in SpriteKit to create the actions as -- animations as actions rather than textures.
The zap animation you see, the zap beam, that's a reference action we have made and applied to both the GroundBot and FlyingBot.
If we click into that action we can see it is made of lots of small move actions one after another.
Because it is a reference action we can create it once and apply it to all TaskBots we have regardless of the orientation.
Because it is a reference action, we can create it once and then go and change it, the source, seeing that change going everywhere, we don't have to change it in multiple places.
We use the action editor to create the beam animation.
It is only fired for so many seconds, we want that beam to decay over time and we create that actions editor too so that we can see it visually and use it in the game. All of the assets in the game have been created in asset catalogs and out of Xcode 7 these asset catalogs are converted to texture atlases for us.
That means that we get the best possible draw performance when used in a game. We have a lot of these images. It also gives us a way to specify the right images to use for different devices.
This helps us to optimize the size further.
When we were designing our levels we used a reference height of 768 points, the yellow box there, that's our reference for how much level we wanted to be visible onscreen.
At that size our PlayerBot is 120 points, and now if we use that level on an iPad, the iPad is also 768 points, so we'll know the size of the PlayerBot on screen, and he's going to be 120 points and we can work out the image size we need to get a sharp image of him onscreen.
If we scale that level down to an iPhone with, say, a 320 point height, he actually is a bit smaller, he'll be 50 points high.
We don't need such a high resolution texture as on the iPad and we can save space.
If we look at all of the devices that we support with DemoBots, iPad, iPhone and Mac, as that Scene height scales we can work out the corresponding player height.
The iPhone 6 is slightly bigger than the 4S through the 5S, we used the same assets, it is so small, you don't notice.
This then means you work out the 1X, 2X, 3X assets we need to make them look sharp without using any more pixels than we needed to. We rounded the iPhone 6 Plus down to 180 pixels just to keep things simple. We can set up the assets we want for each device.
We have a lot of these in the game.
I really, really do mean a lot of them. There are thousands.
All of these character assets are actually generated from a 3D rendering output.
Which we can also connect.
We automated that creation of the asset catalogs too.
The JSON file here, the one frame of the PlayerBot's walk animation, and we can create these automatically, this is what Xcode creates with the asset catalogs and we can automate the process of picking the right images for the right device and we'll release the Asset Catalog format reference in a future seed to make it easier for you to create these yourself as well. Another SpriteKit feature that we used in the game was SKCameraNode, before that, if we wanted to move the view around the level, we actually had to move the world, we had to move the level itself beneath the view.
with SKCameraNode things are much simpler, the camera is a node in the scene. It has a position.
Because it has a position, we can move it around just by changing that position. Same as any other node. It is easier to change where the view is currently looking.
Because it is a node we can do node-like things with it.
We can apply constraints to that node for example.
We can use this to constrain the node's Camera position to a PlayerBot position, when he runs to the corner of the screen, we have a lot of black space around him, not as much level as we would like on the screen. We can make it better to use a second restraint to make sure we're never too near the edge of the level. That's how that looks.
We follow the PlayerBot generally speaking but if he's near we stop following him and keep more level onscreen.
This is easier to see when the enemy bots attack you.
How do we do that? We start by working out the banding rectangle of the entire level.
We then make a smaller rectangle in set from that based on the screens width and height that they're viewing the game on.
We make sure that the Camera can never move outside of that box. We constrain it to that rectangle.
Then when the Camera moves around following the player it never gets too near to the edge of the level. Because the Camera is a node we can also add child nodes to it, which is good for heads-up displays like our timer label we have in the game.
We don't want this label to move, we want it to stay top and center.
So we add it as a child of the Camera rather than had a child of the Scene, that way, as the Camera moves around the label moves with it and it is easier to keep the permanent features onscreen in the right place.
Those are some of the SpriteKit features that we have used in the game.
Let's look at some if of the GameplayKit ones we used.
the first is GKStateMachine, you would have seen from the GameplayKit talk how you can use state machines to track what's going on with characters and levels and other elements of your game.
We use this amongst other things for the PlayerBot, this is the states he has. He starts in his Appear state where he teleports into the game.
The nice thing about using custom states for this is that they can make sure that the right thing happens. They start and it starts a timer, after the right amount of time passes, it moves straight to the player control state and turns on the input use so you can control it.
If he's hit we move into the Hit state, when we have this, we enter a different animation, the jump animation and the player can't be controlled when hit and it turns off input, tracks the state, when the time has passed, move him back to the player control state. If we get hit enough times we have to recharge and instead the Hit state moves to the Recharging state.
That tracks how long we have been recharging for.
The state actually does the job of adding more charge back on to the player.
Eventually when recharged, it goes back to the PlayerControlled state. That's a reasonably complex set of behaviors that the player can have.
By using a state machine to not only codify the states, make them happen, define the right things to happen moving from state to state it is easier to make sure that only the right thing could happen in game. Our player Bot can't do something we don't want him to. We use this also for the game itself.
We have an active state when you play a level, if we hit pause we go into a pause state.
This handles overlay, by removing that overlay when you move out of the pause state.
If I complete the level, we have a success state.
Again, this handles all of the tasks of showing the buttons and overlay for when we succeeded. The state knows the right things to do in these situations.
Another aspect of GameplayKit we have used is entities and components.
Now components are a really good way to package up self-contained pieces of functionality that are shared amongst different entities in the game.
We have three entities, we have PlayerBot, GroundBot and FlyingBot.
They have some things in common.
They all need to be rendered into the Scene and they all need a shadow. So we have a render component and a shadow component to do these things.
They also all have animation, physics and intelligence, which is the name we've given to the state machine tracking .
At this point you may think they look very similar.
Why not have a base Bot class providing all of the functionality to all three? They're not actually that similar after all.
The PlayerBot needs input and we can control that from a game controller or keyboard or from touch control.
The GroundBot and FlyingBot don't need that, you never control those characters.
They're driven instead by an agent. You will see later how we use agents to drive the characters around the level.
Player Bot doesn't need an agent, he's powered by your input.
Likewise, GroundBot and FlyingBot have rules telling the what they should do in a given situation and we'll look at those later on as well.
Player Bot doesn't, you tell him what to do. PlayerBot and GroundBot do have one thing in common, they both have a movement component.
This component's job is to say if I'm here and I need to move this distance at a certain angle make that happen within the Scene.
Player Bot uses that input to render it in the Scene, move him around.
GroundBot uses it for a charge forward attack, he moves from here to charge forward.
FlyingBot doesn't need that capability. He doesn't move so he doesn't have that component.
These components, it is a good way to break up bits of functionality and assign them only to the entities that need them.
So these are some of the ways we have used GameplayKit functionality in the game. I would like to invite Dave on stage to tell you more about how we have used GameplayKit to create our games Logic and Gameplay.
DAVE SCHAEFGEN: Thank you, Dave.
So when we think about Logic in Gameplay, one of the most immediate things that jump to my mind when I think about logic in gameplay is the actual intelligence of my adversaries.
a big piece of that intelligence is going to be what I can actually do in that space? As we saw earlier in the demo, if I'm a good Bot I'm moving around the level, patrolling circuits, keepings things moving.
If I'm bad, I may want to attack the PlayerBot. Got him that time.
I may also wasn't to turn other TaskBots bad so I have help when it comes to getting the player Bot.
Got that one too.
Finally, I may move around the level opposite direction messing with induction, currents in the circuits, causing problems.
Not as interesting, but now that we know what we can do the question becomes how do we decide what we are going to do.
In DemoBots we chose to implement a fuzzy logic system with GKRuleSystem. The advantages of this were we could still take in a lot of information about the current state of the level, what was going on, and have our characters react to that information.
We could do that without having if else trees that went on for hundreds of lines of code. We have simple rules and rely on the simple rules to interact with each other and allow complex and interesting behaviors to emerge.
So if you're unfamiliar with fuzzy Logic, I'll give you a bit of an idea of what we're talking about here.
The fuzzy that we're referring to is actually the fact that everything is not black and white, true and false. Our rules aren't mutually exclusive.
We modeled our rules on natural concepts and tried to think of them like sentences that you would say to a colleague.
The PlayerBot is nearby.
. These are implemented in the fuzzy TaskBots rule class.
It is a subclass of GKRule. We have actually tied the fact of the rule to the grade here.
Only we assert the fact if the grade is greater than zero.
This is interesting because we made grade a function of the actual state information available in the level rather than it being something that you set up at the moment that you create the GKRule. Let's look at what those -- what that proximity rule looks like. Thinking about being near or far, and let's actually use a graph to take a look at this and see how these functions work.
Up here near the origin you can see that we have a PlayerBot and out about as far away as we can get them you have the TaskBot, if I bring in a graphical representation of the far rule it is pretty simple to see that the grade for the far rule is going to be one in this case.
If I pull in the medium and near rules, it is clear that they're going to have a zero grade in this situation.
Now that doesn't lineup seeming all that interesting, it does kindof seem black and white and a bit true/false. If we move our task Bot closer you can see what I'm talking about clearly. Here the grade for the far rule is. 75 and for the medium rule, about. 25. If we move him still closer, things switch and we have membership in both the near and medium rules. Now that you have seen how the functions that we have calculate our grades, let's look at how we actually make decisions once we have calculated those grades for each of our rules.
So here are the rules that we actually have in DemoBots.
Our first step, like I said, is to go ahead, evaluate them, calculate the values.
The next thing we're going to do is combine a few of them to decide these are contributing factors that we would want to pay attention to when hunting the PlayerBot.
Read like a sentence, you can tell how this is working like a telling of a story.
If a percentage of bad TaskBots is high and the PlayerBot is a medium distance away and the good TaskBot is also a medium distance away, I want to hunt the PlayerBot.
The reason I would want to do that in this case, the way I think about it, is there already are a lot of bad TaskBots on this level, I don't need to turn them, I may as well hunt the player at this point.The trouble I have is I have 3 different grades to represent this idea of hunting the PlayerBot now. So we're going to use GKRuleSystems minimum grade for facts to grab the minimum grade for each of the facts that we want to combine. Now, the reason why we pick the minimum is our mandate for hunting the PlayerBot based on that information is only as strong as the weakest fact contributing to it. We could combine those rules in any number of ways and get several different indicators saying to hunt the PlayerBot or to hunt TaskBots.
This is the stage where we get to needing to actually defuzz-ify the rules to get one simple number to really represent the idea of hunting the PlayerBot.
In this case we just take our facts, we use the reduce function in Swift and the max function to combine things. In this case we actually -- we want hunting the PlayerBot or hunting the TaskBots to be represented by the strongest grade available of the ones we have.
Looking at these numbers, it is pretty obvious we'll hunt the PlayerBot. Now that we know we want to hunt the PlayerBot the first problem we've got is how do I actually get to the PlayerBot.
Often that's pretty straightforward, just move in a straight line and you'll eventually get there. Obstacles present a challenge, here you see the FlyingBot getting hung up there. You remember from a sample from a couple years ago, Adventure, the goblins in that sample, were really fond of this behavior.
We decided to do something about that, and in GameplayKit we have made it really easy for you to take advantage of pathfinding in a world and get your Bots or your enemies moving easily like this. It has great conveniences for working with things when you're using SpriteKit for a game and it is really easy to get things up and running.
Only a few lines of code. Let's take a look at what those lines look like.
So the first thing you're going to do is actually get the polygon obstacles making up the level. In our case we'll first search for our nodes within our Scene. They have all been named obstacles. When we have an array of those nodes we'll pass it to a convenience function that SKNodes has in iOS 9, obstacles for node physics bodies to take the actual physics body you defined to use that to define the obstacle.
When those obstacles, when we have the obstacles we're going to construct an obstacle graph using them, and also a buffer radius parameter.
This parameter is a little bit of extra spacing around the actual obstacle.
A good way for you to think about it might be as you're walking towards a doorway and you're walking through the doorway you're not actually going to walk at one corner or the other of the door frame.
You're going to aim for a point in the middle of the doorway where no part of your body is actually going to contact the door when you walk through it. This radius helps set that spacing around the obstacles.
Next we'll take the TaskBot and PlayerBot positions and connect them to the graph.
Finally we will ask the graph to give us a path from the start node to the end node.
We'll get back in array of the individual nodes that are necessary to get from point A to point B and it really is that simple to turn things from walking into memory chips and along them to actually walking around them.
We have a path, we have points, but how am I actually going to get there? How do I actually get there and make it look nice? Well, GameplayKit has an answer for us again. This time it is GKAgent 2D. This is one of my favorite classes in GameplayKit.
This is a GKComponent, so it works very nicely with the Entity/Component system that Dave talked about earlier.
It is very easy to get things set up.
You have a GKBehavior that describes what it is that you want this agent to do, it is kind of a container for GKGoals.
GKGoal, lucky for us in this case actually has a couple of different constructions that allow us to work with paths.
Because the agent world and the world of GameplayKit is different from the world of SpriteKit the delegate here makes it very simple to integrate the two of them. Let's take a look at what it actually -- you know, what the code looks like.
We'll take that array of path nodes that we had from before. We're going to initialize them. We're going to pass them to an initializer to create our path.
There is another parameter here and that is the radius. What I want you to think about here is how do you want this path to be defined for your agent. Very small values will result in your agent treating your path like a tight rope.
Larger values, you know, could have them treating it like an 8-lane highway, moving all over the place.
You'll want to fine tune that for what works best for your game.
Next we'll create a behavior.
Here it is just an empty behavior, nothing really going on yet. Then we'll add goals to it. These are the two path-related goals I was talking about from before.
The first one on the screen, our goal to follow path is going to establish the direction in which we'll travel our array. In this case we'll move forward in a forward direction from the start position, our TaskBots to the end position, which was our PlayerBot.
The stay on path goal is there to motivate our agent, to actually remain within the bounds of the path that we defined earlier.
Now that we have a working behavior we assign it to our agent to get him moving. the agent works like many of the other components within GameplayKit. It is updated on a cycle.
When you've added it into your update loop it will notify you before and after changes, and this delegate notification before and after changes is the ideal place to actually get things working and hooked up with SpriteKit.
In WillUpdate, this is a position that you want to take the information from your SpriteKit node that represents your agent in Scene and pull that information back over and update the agent because we're doing this at the front of the SpriteKit update loop and the end of the last SpriteKit update loop would have involved simulating physics, applying constraints to your node which may have caused its position to move from where the agent thought it was at the last time through.
In AgentDidUpdate, we'll take the information from the agent, its position, its rotation, things of that nature and pull those back over into the SpriteKit world updating our node before it goes into physics simulation and constraint application this time around. Really, that is what got us to this point with DemoBots and an intelligence that works nicely, moving readily around the screen, smoothly interacting with you. This is something extra I guess, we left debug drawing in the sample that you can download.
You can enable it by hitting the slash key.
The nice thing about this, it helps you visualize some of the parameters we talked about earlier. The orange boxes are buffer radius around the obstacles and the thick lines that you can see emanating from the player, the task Bots are a representation of that path that we talked about, the path radius that was there. Now that we have a game, I would like to invite my colleague Michael up on stage. He's going to discuss with you how to get your users playing your game that much faster.
MICHAEL DEWITT: Thank you very much. We'll talk about taking a fun game and making a great overall experience for your users. I'll boil it down to one phrase and that is, of course, time to fun. How long does it take for your users to start really enjoying your game? The first barrier to entry there is really the initial download.
If you're shipping an app that's too big some users won't be able to download it over cellular connection and it can take a while even on Wi-Fi.
This is the biggest latency between your user finding the app on the App Store and deciding to get into it. But we know this 100 mega download limit is hard for games to stay under. We'll go back to DemoBots and see how we handle this.
If you focus on the PlayerBot you notice we're not looking strictly top-down on the character.
It is not a 2D game but an isometric feel here.
We pulled that off by giving this character multiple orientations.
As you move the character around the map, we'll swap out the texture representing the character to get this perspective.
When you add all of the extra frames in there, it takes space. It is only 6 megabytes, that's small for a game. When you consider it is 6 megabytes times the three bots we have times the different actions they can perform, we need the orientation frames for the Player Bot when idling, hit, walking around, so this starts to add up. Traditionally, that means that all of the assets plus the 1X, 2X, 3X versions, they're bundled in the app. We have a better story for this now. You may have heard about it earlier this week.
If you use asset catalogs you will take advantage of a new feature called app slicing.
What app slicing will do is breakdown the 1X, 2X, 3X for the actual device that they're used on.
This is going to save us a ton of space right off the bat just by using the asset catalogs and letting the App Store slice it for us. It is not only that we now save a bunch of space, it is what can we do with the extra room on our app. I'm looking at the graph and it is empty.
In DemoBots, it has practical implications.
We started with 8 orientations and I'll show you a video here. Watch closely. The PlayerBot, it looks like it is facing directly forward, you will notice, watch the movement closely, see if you notice something. So especially when the character is moving back towards the top of the map there, we term that skating.
While we only have the eight orientations to represent the characters movement, the player is supplying 360° of input, we can be slightly off of that orientation while not triggering the next and you get the strafing behavior.
More frustrating than that though was using the eight orientations means you could end up in a situation like this where the PlayerBot is facing directly forward, looks like it should be easily hitting that TaskBot but isn't because of the debug drawing, you see the player is aiming to the side and the user has no way to see this.
With that extra space we got from app slicing we bumped it up a notch.
We went to 16 orientations, that makes movement feel a lot smoother throughout the game so you can see we have many more animation frames here to represent that.
Then when it comes to aiming, now it is much more granular. The direction where the character faces almost corresponds exactly with where the beam's going to hit. You click through there, it is easier for the user to know what's going on with the game. That's app slicing, we use that in DemoBots simply by putting our texture atlases within Asset Catalogs, it helps us to greatly decrease the size of the app overall, but it's more than that, we can actually improve the gameplay now because we have the extra space. This feature works super well for assets that you need in your game all the time. DemoBots is not much of a game unless we keep the PlayerBot around.
But there are other assets we don't necessarily need all the time. For that we have another technology that you may have heard about earlier this week, on demand resources.
The mild overview of this, it lets you tag resources just with a simple string for download later on. I'll talk about how we use this in DemoBots.
The first place, maybe it is obvious, we have multiple levels.We can tag those level 1, level 2, and level 3 .
The advantage here, it is now that we have tagged these with ODR we can actually say if the user initially downloads the game, we know they'll go to level 1, there is no reason to include the other two levels there.
It gets even more interesting as the game progresses, because we can predict with this linear flow that the user will proceed on to level 3 so we'll start downloading that level early and it is unlikely that they'll replay level 1 to we can recycle some of those resources. Let's take it a step further.
If we look closely at level 2 and you will notice from the demo and from this small picture that only the FlyingBot shows up in this level and shows up again in level 3.
Contrast that with a GroundBot who is in levels 1 and 3.
When we tag the characters individually it allows us to breakdown our resources even further. If we know the user is downloading the app for the first time we only ship with the GroundBot in level 1 and we can do the FlyingBot later on.
And if you're on a device that's tight for storage, maybe when the user's playing level 2 you can purge the GroundBot and then bring them both back for level 3.
You can see how we laid out the tags in the Project Navigator under resource tags.
You will see that the assets for level 1 are tagged as prefetched, those are coming down shortly after the app has been installed, but not included in the bundle size, whereas the other resources can be tagged for download when we request them.
That's on-demand resources. We used it in DemoBots to tag the resources for later download which gives us a faster initial download time. We can cut a bunch of stuff out that we don't need immediately.
Overall it helps us keep the storage footprint small too. That's the bigger message here. You can make a more rich game and have many more assets because you are still keeping the same footprint on device but accessing everything else on demand. We recognize this does add complication.
When you talk about presenting the next Scene traditionally you know it is in local storage, you can prepare those resources, and when the user requests it, you can present.
Now we're adding an extra complication. Maybe you need to download those resources, and of course with network connection that download can fail.
If you want to get the space savings, you will have to purge the resources at some point and rinse and repeat that whole cycle.
This can get complicated and I want to focus on how we solved it in DemoBots.
Specifically going back to a technology that Dave mentioned at the top of the talk, the GKStateMachine.
If we use that to model this, we call it our SceneLoader and it has six associated states.
You will notice that only two of the states, the downloading resources and download failed state, they're actually associated with ODR because this is a full pipeline to model bringing your resources into memory whether they're from local storage or need to be downloaded first.
the real advantage we're getting from using the state machine is how we model the transition from states. If we look closely at the preparing resources state we can enforce what are the valid next states by overriding IsValidNextState in our GKState subclass and we can say the state machine can only move on to the ready state if the Scene is indeed loaded, or we can move back to the available state if the user's canceled this request. We're not going to go back to downloading while trying to prepare our resources because we're able to enforce it with this IsValidNextState and so it leads to much more determininistic behavior.
Alright, so to wrap things up, I'll share a final few tips we learned developing this game.
the first, if you're using on-demand resources, put that download request in early. If you have a predictable progression in the game, you can start downloading level 2 as soon as the player begins level 1.
Don't forget about the tools for ODR that are within Xcode. You can look at the disk report tool and specifically under the on-demand resources see if your tags have been downloaded or are currently in use or have already been purged. This is really useful.
Additionally, if the player is coming up to a junction when they'll need additional resources and you haven't downloaded to prepare them yet. You can modify the priority of your request.
This means that you can bump up the loading priority on the bundle resource request, it is a scale between 0 and 1 and there is even a constant for urgent if the user is blocked and you're trying to download.
We modeled preparing with an NSOperation queue you can bump up quality of service there. Overall in DemoBots we wanted to do a ton of things, we really wanted to ship a sample that showcased a bunch of different aspects of developing games that we thought you would all be interested in. So Dave spoke to you initially about fine tuning your assets for every device, including special assets for the Mac. We talked about elegant character navigation, without having to write a ton of movement code yourself and finally adding additional assets to improve gameplay because now we're slimming down our app. I'm excited to say GamePlayKit has ton of great features and iOS 9 in general to help you do these things.
To check out how they're used, you can download the sample right now from this link, I encourage you to do so.
Here are additional links for documentation, you can contact our evangelist Allan.
The related sessions we mentioned throughout this talk, they have already happened, you can catch them online.
Thank you all so much.
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.