WWDC2010 Session 408

Transcript

>> Welcome.
I'm glad to see so many people interested in Game Center.
We're going to talk about some techniques
on how you enable your Game Center game.
I'm guessing most of you are around for
Mike's session that was just before.
But I'll go over a simple review of what Game Center is.
And then I'll go into some details about how you actually
turn your games, your applications, into Game Center.
I'm Gabriel.
I'm part of the team.
We're here all week.
We have a lab.
Tomorrow we have another session in this room right
after this where we'll be going into some more details.
So, Game Center.
Game Center is Apple's social gaming community.
It provides a whole bunch of features:
friends, friends' lists.
It allows you to play with your friends;
compete with your friends offline and online
through leaderboards, achievements, and multiplayer gaming.
Peer-to-peer multiplayer gaming is really neat.
You'll see more of that later today.
I'm going to be focusing more on
the first three of those slides --
bullet points: friends, leaderboards, achievements.
What you're going to learn today, Game Center API basics.
I'll go over a little bit about how our API is
structured, how we can use this in some simple cases,
how to authenticate, how you can remember.
You can use the player ID, which was mentioned before.
It's persistent.
You can use this to remember your
players across different sessions.
You can store data.
I'll show you how you can report high scores, how
you can access and show a leaderboard in game,
how you can access the data from a leaderboard
if you want to use custom UI, and achievements.
So I'll also show you how you can report achievements
from your game; how you can show achievements
in your game using the standard UI; how to query data
from the achievements, your users' achievements; and,
lastly, how to reset a user's achievements.
We've seen this slide.
It's an overview of what Game Center actually looks like.
There's a Game Center application.
Obviously, there's Your Games.
And the GameKit framework.
The GameKit framework is comprised
of three different pieces.
There's parts that are only available
from the Game Center application.
There's the piece in the middle which we're calling General,
which is Authentication, Friends,
Leaderboards, and Achievements.
That's what I'm going to be focusing on in this session.
And, lastly, there's the Multiplayer features
which we'll go over in the next session.
So I'm going to be focusing on these four things today, and
I'll be going through each of these sections in more detail.
To start off with, I want to take a
little digression and talk about blocks.
Blocks are new in iOS4.
They were introduced in Snow Leopard.
A block is essentially an anonymous function.
In other languages, you might have
heard them called closures or lambdas.
You can see here we have declared a block.
It's like any other C variable.
It follows the definition of a function pointer.
Instead of using an asterisks, it uses a caret.
One of the interesting things is that it
actually captures enclosing variables.
So you'll notice in the block where
we make reference to x, y, and z,
z being passed in as a parameter, x and y are captured.
So when we get to this last line,
it will print out 123, 456, 789.
A note that I want to make, we're
only using static types here.
But you can also use objects.
And when you use objects, when you reference
enclosing objects, it will automatically retain them.
And there's a particular note that I want to
make about using instance variables because,
when you reference an instance variable,
you're making an implicit reference to self.
And so self will be retained inside the block.
If you're also retaining the block inside
the object, then you have a retain loop.
And this is something that you need to be
concerned about or you should watch for.
There's a lot more information on blocks.
There's another session.
I'll give you some details at the end of
here where you can find out about blocks.
But we use blocks quite extensively.
And I'll show you exactly how we use blocks.
So this is not a real call but it's
a template of how we're using blocks.
We use basically blocks as callbacks.
Most of the functions in GameKit are asynchronous, which
means that an action is performed and you will be notified
at the end once said action has completed.
And the way that we do that is we do that using blocks.
Here are some of the errors that you'll see.
GKErrorCancelled will happen if the user decides that
they were going to cancel the event which you started.
CommunicationsFailure, if we have
trouble; if the network is unresponsive.
InvalidCredentials, if the user that is using your
game doesn't remember his user name and password.
NotAuthenticated, Mike mentioned authentication before.
I'll mention again right after here, all the features
of Game Center require that your user is authenticated.
AuthenticationInProgress.
Since authentication is asynchronous, you might be tempted
to try authentication if you're not already authenticated.
And you might actually try again.
If the user is in the middle of authentication and
we receive a call to some function, we'll notify you.
ScoreNotSet, you'll see when we're dealing with
leaderboards if you try to report a score that has no value.
ParentalControlsBlocked, something that
you'll get back if there are restrictions.
We have -- built into the iOS there's restrictions
that can be placed on which features are available.
And the last one is a MatchRequestInvalid.
So when you see matching, which we'll do in the next
session, there are some parameters which you can set
up with matches, and match has to -- has certain
restrictions about how you can set up a match request.
And Nate will go over that.
So here we are.
We're at this list.
I'm going to focus on authentication, and this
is the first class that I'm going to talk about.
This is GKLocalPlayer.
GKLocalPlayer represents the player
on the device who's playing your game.
It has three pieces of information
associated with that player.
He's got a player ID that is unique and persistent.
You can use this to store data about your players.
You can use this to extend the
community, extend our feature set.
You have an alias, which is the
nickname that Mike talked about.
It is unique but it's not persistent.
The user always has the ability to change their nickname.
And, then, underage flag to give you an indication that this
user is underage and you might want to behave appropriately.
You also use GKLocalPlayer to perform authentication --
I'll show you that in just a moment
-- and access your friends' list.
So here's the first piece of real code
that we see to perform authentication.
Authentication is required to use any of the other
features for -- any of the other features of Game Center.
So you need to do this as early as possible in your game.
It may present UI.
It will give the ability for the user to present their
credentials if they haven't already been authenticated.
We share authentication across all games in the Game Center.
So it might be that you call authentication
and your user is authenticated.
You'll notice this block here, indication of an error.
First thing that we need to do is we
need to allocate the local player.
We use this -- we do this using a class method called
localPlayer on GKLocalPlayer, and we store it for later use.
On this object,
we now call authenticateWithCompletionHandler,
and we give it a parameter, this block.
This block is going to get run sometime in the future.
It's a callback.
We're using them as callbacks instead of delegates.
We find this to be much easier
to do and much easier to write.
So the actual block of the code gets run at a later time.
It might look quite linear as your reading it,
but this code actually gets run in the future.
So here we have the response coming
back from authentication with an error.
If there was not an error, that
means your user is authenticated.
You should enable your GameKit features.
You should allow the user to use invites and leaderboards.
Otherwise, if there has been an error, you
should turn off Game Center Enable Features.
Doesn't mean that you should stop your game.
Please continue your game.
Maybe the user has decided that they don't want to play
online or they don't want to use Game Center and features.
And they've notified this -- they've
told you this by canceling the operation.
But your game should continue on.
So this is what it will look like to your user.
If he's not already authenticated, he'll be given this
opportunity to either sign in using an existing account
that he's already created or to create a new account.
If he signs in using an existing
account, it's the iTunes account.
And he's given a place to sign in.
If the user's already signed in, has already
been authenticated through another application
or having used your game earlier, then you'll
see this welcome back message that comes in.
Then you can enable the buttons to do invites, matching,
leaderboards, etc. Now I'm going to talk a little bit
about friends, how you access your friends'
list, what you can do with the friends' list.
And we hit our second class.
This one's called GKPlayer.
GKPlayer represents everybody else in the
community who's not the user on your device.
We have two pieces of information
that we have about this player.
We have a player ID, which we can see is persistent.
You can use it to keep track of data for those users,
and you can keep track of those players over time.
The alias should be used in your
game to display the user's name.
So, when you're in a match and you get matched
with somebody, you should use the alias to show -
hey, you're now playing with this person.
This is who's blue, this is who's red, that kind of thing.
This is how you get your friends' list.
The first thing that we need to do
is, again, start with localPlayer.
If you've already done this, you could use the same object.
We call loadFriendsWithCompletionHandler.
This block is a little bit different than the last one
that we saw, because it has two parameters in it now:
one for the array of friends, which gets
returned to you; and one for an error.
You'll notice in the block where I'm actually
handling both cases, if, for instance,
there is a communications failure, we
might have that friends' list cached.
So we'll return that to you, even
though you can't access the server.
It might be slightly out of date, but
it's the best information that you have,
as well as also return you an indication
that there was an error.
You should handle both cases.
So, in this case, the friends' list that's
returned to you is an array of GKPlayers.
[ Pause ]
What to do with the friends' list.
I've already told you that you can associate custom
data on your own servers with the player IDs.
And here are some ideas that you can do, you
can use to extend the Game Center functionality.
Virtual goods.
If you want to gift things to other friends, you can
use the friends' list to find out who your friends are.
You can give them things.
This is outside of the features that we provide you.
Cross-session messaging.
If you have virtual worlds, you have friends, you can
leave messages for other player IDs on your servers.
When the user starts your game and they log
in, they can have this information to them.
You can use a social graph as part of your gameplay.
You can have your users visit each other.
This doesn't have to happen online.
And you can use the connections to
enhance the character's virtual abilities.
[ Pause ]
Now I'm going to go into leaderboards.
A little bit longer than the last
section but still quite easy to do.
So there are three classes now that we
need to talk about for leaderboards.
GKScore is the actual score.
A GKLeaderboard is a class that you can use
to access the data inside the leaderboard.
And there's a GKLeaderboardViewController.
The view controller is the standard
UI that we provide to you so --
to make it very easy for you to show leaderboards
inside your game in a few lines of code.
I'll go through each of the different
things that you can do.
First of all, what is a leaderboard?
There's a screenshot of a leaderboard.
A leaderboard's a list of high scores.
There's filters -- Mike alluded to those --
friends, all players, different time possibilities.
We're also providing categories.
Categories are a way to provide multiple leaderboards.
This is something that a lot of people had asked for.
So you can separate your leaderboards
into easy, medium, and hard.
You can have different tracks or different levels.
In addition to that, you can have one global leaderboard,
where any score to any of the categories that you provide,
you'll have one leaderboard which shows you overall scores.
Steps to using leaderboards.
You have to set it up first.
So there's a server-side setup component.
There's reporting scores.
There's showing the scores using the
standard UI, showing the leaderboards.
And there's querying leaderboard data.
You would want to do this if you
want to build your own custom UI
so it's a little bit more integrated
into your game and the style.
First I'll talk about server-side setup.
This is done in iTunes Connect.
You need to enable leaderboards for your
game, and then you need to set them up.
And a leaderboard consists of a few different
properties that you set in iTunes Connect.
There's a leaderboard identifier, which is a string.
There's a sort order, ascending or descending.
And this is the order that shows up in the leaderboard.
So, if I have a descending, it means that higher scores are
better because, as you go down the list, they get lower.
Ascending, the other way around.
So if you have a golf game, for
instance, lower scores would be better.
And you can localize this in many different languages.
There's a format for your score:
integer values or time values,
how you want to represent the score;
and a suffix in many languages.
More languages are better.
It helps sell your game.
More people are interested.
More people will be able to play it.
This is a screenshot of iTunes Connect.
You can see there's a place for a leaderboard ID.
This is an ascending one.
You can set the sorting.
And all the languages are below.
In this case, it's only in English and
Spanish using integer values for gold coins.
But I suggest you use as many languages as you possibly can.
This will show based upon the locale of the
user who's using the game on their device.
[ Pause ]
I want to talk a little bit about
formatting because it's interesting.
I have a few different selected formatters here and some
examples, the first one being an integer value, 123,456.
It will format it with a comma.
Depending upon your locale, we have different
formats that you can choose for an integer value.
Time values, I've shown two here.
The first one is in minutes.
So the value that you submit would be 792
as an integer, but it's represented --
it's interpreted as the number of minutes.
So, when it's presented, it actually
shows up as 13 hours and 12 minutes.
Similarly, when you have a second value, you can
choose to have your value represented as seconds.
So you have here 3,723 seconds, which shows
up as an hour 2 minutes and 3 seconds.
And money, 1,042 shows up with a
dollar sign and comma, as appropriate.
There's about 17 formatters in all.
You can see these on iTunes Connect
when you choose to do the localization.
So reporting a score.
It's quite easy.
You allocate a GKScore.
You set a value, you set a category if you're using
multiple leaderboards, and you report the score.
A note: If you're offline and there's an error in
communicating with the server, it's your responsibility
to save that score and attempt to report it at a later
time when the network conditions are more favorable.
We make it easy for you.
I'll show you that when I get to the code.
These are the properties of a GKScore.
Some of them are read only.
They'll be returned to you when
you ask for leaderboard data.
A value is the integer value of the score.
A formatted value is a string formatted by the server based
upon the properties that you've set in iTunes Connect,
how that score is interpreted,
and the optional language suffix.
Category, if you're using multiple leaderboards.
A date, which is the date, the
time that the score was created.
The player who actually created that score.
And a rank where it fits on the leaderboard.
This is how you report a score.
You start off allocating a player
score -- allocating a GKScore.
Here we call it myPlayerScore.
We set a value for the score, 12345.
And we report the score using this
function, reportScoreWithCompletionHandler.
There's no result returned, just an indication of the error.
If there was an error, that would make it easy for you.
It's an encodable object so you can use NSKeyedArchiver.
So, to serialize this, you can store it
offline so that you can try again either
when the next time your user plays
the game or later in the play session.
If there was no error, you're done.
The score was submitted successfully.
You can see it.
Which brings me to the next point: How
do you show these to the user, right?
You have a list of scores.
This is using the leaderboard standard UI.
There's a class called GKLeaderboardViewController.
It's a modal view controller.
There's a button at the top right which says done
so that the user can indicate, I'm
finished looking at the leaderboard.
And so there's a delegate method
which you have to implement.
I'll start off with that.
leaderboardDidPressDismiss.
All we're doing is we're dismissing the modal view
controller, which will take it off the screen.
This will appear on top of your view.
So, to show the leaderboard, we've created
this function called showLeaderboard.
The first thing it does is it allocates
GKLeaderboardViewController.
It initializes it with some filters.
In this case, we're interested in all the
scores over the last week for my friends.
And we set the delegate to ourselves so
that we'll know when they're finished,
and we present the -- we present the view controller.
I think that's fairly simple.
We also give you the ability if you want to create
your own custom UI to query the leaderboard.
To do this, you use the GKLeaderboard class.
You set the scopes similarly to how
we just -- how I just showed you.
You set a range of ranks that you're interested in.
As Mike said, you don't want to load the whole leaderboard.
There might be thousands or millions of entries.
It's an asynchronous operation
that communicates with our server.
It could take a fair amount of time, especially if the
list is really long; and that's a poor user experience.
Lastly, you can set a category if you're using
multiple leaderboards; and you can query the data.
It returns an array of GKScores.
We've seen this before.
But just to reiterate, these are
the ones that you're interested in.
The player who scored that score, you can get their player
ID and their alias; the rank where it fits in the list;
the value, the formatted value which comes from
the iTunes Connect properties; and the date.
So here we actually see some code,
how to query the leaderboard.
So we start off with allocating a GKLeaderboard object.
We set the properties that we're
interested in -- again, friends only --
and the scope is week, so all the scores over the last week.
And I'm just going to take a look
at the top of the leaderboard.
I'm using a range here.
Normally, ranges start with index values;
but leaderboard values start with 1.
So, in this case, we're looking at
25 values starting at the value 1.
So it will show you the top 25 list.
And then we call loadScoresWithCompletionHandler.
It returns to you an NSArray of scores,
GKScores, to be precise; and an error.
Again, we might pass you back some cached
data in the case of a communications error.
So you should be prepared for both
those values to be returned to you.
If there's scores using -- in this case, I'm
just doing a fast enumeration across the list,
and you'll want to do something with
it to show it in your own custom UI.
If there's an error, you might
want to indicate that to the user.
You might want to try again, depending on what the error is.
That's querying a leaderboard.
So I have a few screenshots of
what the leaderboard looks like.
We provide both landscape and portrait views.
So this is what it looks like in landscape.
You can see here there's two sections.
There's a friends section, an all player section, and,
across the top, you have the ability to change the filters.
This is would what it would look like in your game.
If you choose to do a custom UI,
you could do something like this.
This is from Onward.
It's a game that we've been using inside for testing.
And here you can see they've done
something slightly different.
What they do is they actually show two different time scopes
at once, scores over the last week and all time scores.
It's chosen not to separate that between friends
but, as a developer, you can do whatever you want.
And I'm really interested to see what you do.
Landscape, it looks slightly different
but pretty much the same.
You have that two sections, one for friends and
one for all players and the filters across the top.
And, likewise, you can easily do custom.
This comes from Quest.
That's leaderboards.
I think I'm going a little fast.
Here we go.
We're going to talk about achievements.
Achievements, coming soon.
Not all of it is available.
Particularly what's missing is the server-side setup.
The APIs are there if you want to test your code.
There are three classes involved:
GKAchievementDescription is a list
of all the achievements for your game across all users.
There's a GKAchievement, which is the list of achievements
recorded by the user, the GKLocalUser in this case.
And GKAchievementViewController, which is the standard UI.
So what is an achievement?
An achievement is something of significance,
something your player's done inside your game
that you want to record, maybe reward them for.
Examples: finishing a level really fast;
finding all the treasure in a dungeon.
Come into our labs tomorrow.
Please come to our labs tomorrow.
It increases the engagement.
Players will want to come and complete all the achievements.
They'll want to get all the achievements
that you have possible.
They'll want to finish your game.
They want to get to 100 percent complete.
It also encourages competition.
Inside the Game Center application, you've seen you
can compare the achievements between your friends.
You want to say to your friends, I got this achievement.
You should go get this achievement.
Helps engage your game.
Helps keep your game last.
People want to play the game.
These are the steps involved in using achievements.
There's a server-side setup.
I'll go over that a little bit.
You can report achievements.
You can access the descriptions for your achievements.
You can query your achievements for custom UIs.
You can show your achievements to your users.
There's a standard UI.
I'll show you that.
And there's the ability for you
to create your own custom UI.
And, lastly, there's a way to reset the achievements.
Somebody in the earlier session asked,
"What happens if I add more achievements?"
If you add more achievements and somebody's already finished
your game and they want to go back and play it again,
this is a perfect opportunity for
them to reset their achievements.
The server-side setup is done in iTunes Connect.
You can enable achievements in your game.
And you describe each of your achievements.
This is what you need to do for each achievement.
There's an achievement ID.
It must be unique within your game.
We suggest using an inverse DNS, something
like com.myCompany.myGame.achievementX.
There's a title for the achievement.
There are two descriptions for the achievement,
one that shows up before the user's achieved it
and one that shows up after the user's achieved it.
There's a point value associated with each achievement.
There's a hidden flag -- I'll talk
about that in a minute -- and an icon.
The hidden flag, as mentioned before, will allow
you to set some of your achievements as hidden.
So they might be secret achievements that you
want your users to discover by playing your game.
They might be achievements that you don't want them to
see because they haven't bought the rest of your game
if you have some in-app purchase which has extra content.
You can unhide those achievements once
the user has purchased that content.
But it's fairly flexible.
This is what you'll see inside the code, right?
It's similar to what you put into iTunes Connect, but
it's a list of -- each achievement has a description.
And these are the properties that
you'll see from the framework.
So there's a unique ID we talked about
is a string, preferably inverse DNS.
You'll use this to reference your achievements,
either reporting them or visualizing them.
There's a title.
There's an icon.
The icon only shows up once the user's achieved it.
You'll see in this case there's a few different cases,
the question mark where he doesn't have
the achievement, a partial achievement.
There's two descriptions, again, one
which you'll see before you've achieved it
and one which you'll see after you've achieved it.
There's point values associated with each one,
and there's a flag to hide the achievement.
[ Pause ]
A GKAchievement, this is the record of the achievements
that are made by the user, the GKLocalPlayer.
The unique ID is the same.
Percentage complete, how far along that achievement has
the user progressed; the hidden flag; a completed flag,
which is set once the user has completed the
achievement; the date that this record has been reported;
and the number of points that the
user's been given for this achievement.
So first we'll talk about reporting an achievement.
Similarly to leaderboards, it's very easy to do.
We use GKAchievement using the
achievement ID to identify it.
We set a percentComplete and we report it.
As the developer, you're responsible for
retrying if there's communications failure.
The object GKAchievement is a codable object.
It's quite simple to store for later use.
And this is actually how we do it.
We start allocating a GKAchievement.
We initialize it with the identifier.
More explicitly in this case, we see
com.myCompany.myGame.achievementX.
We set a percentComplete.
So I'm 50 percent done; I'm halfway
through this achievement.
And we report it.
Again, this is a block.
The body of the block, although it looks
like it's linear, it's actually a callback.
There's an error if there's an indication of an error.
If there was an error, in this case, what you
want to do is you want to archive the achievement
so that you can reuse it and you can try to report it.
If there was no error, the achievement was submitted
successfully and the hidden flag will be unset.
If you hide an achievement and the user reports the
achievement, that means they found out about it.
And so we unhide it.
[ Pause ]
Achievements Standard UI.
This is a GKAchievementViewController.
It's a modal view controller.
It requires a delegate.
There's a done button.
This is what the delegate look
like, achievementsDidPressDismiss.
In this case, we just dismissed the modal view controller.
We have a function here called showAchievements.
So the first thing that we do is we
allocate the GKAchievement controller.
There's no properties that we need to initialize it with.
We need to set the delegate to ourself and then we
show the delegate -- we show the modal view controller.
It's quite simple.
[ Pause ]
Getting Achievements Descriptions.
So now I'll talk a little bit about
achievements and how you would want
to create your own custom achievements
or custom UI for achievements, rather.
Achievement descriptions, as I mentioned before, is
basically the list of achievements for your game.
So, in this case, they're the same
for all the users of the game.
I want to start off by loading up this achievements
description table so that I can use it later on in my game.
It's a dictionary.
We start off by loading the achievements.
It's a class method on GKAchievementDescription,
loadAchievementDescriptionsWithCompletionHandler.
And it returns to us in the form of a block callback an
array of descriptions and an indication of the error.
In this case, what we're doing is I'm
just loading up this description table.
Now, I'm using the IDs as keys into
this dictionary so I can refer to them.
When I ask for the achievements, you'll see that
the achievements come back with an achievement ID.
The achievement ID is the same
ID that we set in iTunes Connect.
It's the same ID that's used here, so it will
allow me to cross-reference the achievements
that my user has used with the achievement descriptions.
And I can use both the data in the UI that I'm creating.
If there's an error, you'll want to
indicate this to your user somehow.
You'll want to try again, something like that.
Once we have those descriptions in the description table,
then we can actually look at the achievements for that user.
You'll notice that the first line is the
same first line as was on the last screen.
In fact, it's just a repeat; but it's the same object.
It's a dictionary that I loaded with the descriptions
so that I can reference that in
the body of the block coming up.
So here we see we're using now a GKAchievement
as opposed to a GKAchievementDescription,
because I want to look at the user's achievements.
So I start off with a loadAchievementsWithCompletionHandler,
and it returns to me a list of achievements and an error.
This will happen in the future.
It's an asynchronous call.
All of the calls we've seen today are asynchronous.
They all look quite similar.
If there are achievements, I'm
going to loop over the achievements.
I'm using fast enumeration here.
You'll see first I pull out the
description from that dictionary.
And, then, in this case, I'm just printing out a log.
What you'll want to do is create a UI object, an entry in
a table view or something like that where I'm using some
of the values from both the description and the achievement.
So here I'd be printing out how many points out of how
many maximum points and I'm printing out the title.
There's lots of other properties there.
There's a percent complete.
There's descriptions.
You might want to hide them yourself
for the ones that are hidden.
You can do lots of interesting things.
[ Pause ]
And, lastly, we get to resetting user achievements.
Again, I mentioned that you want to give
your users the ability to start over.
Maybe they missed something.
They missed something on level 2 and, in order to get
back to that, they want to reset their achievements.
There's a simple method here called
resetAchievementsWithCompletionHandler with a block.
You see it like this.
The block returns an error, and an indication
of whether or not the list was actually reset.
If there was an error, you might want to try
again or indicate to your user in some fashion
that there was an error, that their
achievements were not actually reset.
Otherwise, there's nothing for us to do.
Thank you.
I'm going to show you a few screenshots now.
This is the standard UI.
You'll notice various different styles.
There's one at the top which is 70 percent complete.
There's one in the middle, Sailor's Mark, which
you have not achieved point values for each one.
And also we can see what it might look like inside a game.
This comes from Quest.
They've done a pretty job.
You'll see the Demon Slayer one,
which is partially achieved.
You'll see what they did with the
hidden one is they marked it as secret.
So, in summary, what we talked about, blocks.
Blocks are an important part of
GameKit, Game Center's framework.
Most of the calls that you've seen are asynchronous, which
means you make the call so you ask for a leaderboard.
You say get leaderboards with -- leader scores with
completion handler and your block is run asynchronously.
So we'll communicate with the server and we'll
report back to you when we have that data.
So it may happen at any time, and
you need to be aware of this.
Authentication is really important.
All the other features rely on your user being
authenticated, so you should do this early in your game.
However, your game should continue if the user has
chosen not to authenticate or can't authenticate.
You just won't be able to use any
of the Game Center features.
We've seen how to report a high score.
We've seen how to access the leaderboard data, either
in the form of a standard UI or the data itself
so that you can create your own custom UI.
We've seen, similarly, for achievements, a
little bit more detailed in achievements.
But we've seen how to record achievements; we've seen
how you can use the standard UI for achievements;
how you can access the data from the achievements
so you can create your own standard UIs;
and, lastly, how to reset achievements.
A note about achievements.
I mentioned previously, the API is in the developer
seats, but you won't actually have the ability to set
up your achievement lists until later this year
when they add that functionality to iTunes Connect.
I'm sorry I didn't take more time.
But if you want more information, Allan is available.
Here's his email address.
There's lots more documentation both on blocks
and on Game Center at the iPhone Dev Center.