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
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.
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.
[Laughter] One more to go.
Oh no! [Laughter] This is not going well.
[Laughter] One more.
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
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.
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
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
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.
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
But there are other assets we don't necessarily need all
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
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.