[Из песочницы] Простой лидерборд на Unity3D с facebook-ом
После участия в Ludum Dare 31 у нас появилась игра, в которой можно соревноваться с друзьями и мы решили добавить к ней лидерборд, с авторизацией через Facebook. Какие сложности могут возникнуть и как сделать подобный в своей игре читайте под катом.
FacebookПервое что мы сделали — подключили Facebook SDK. Его можно скачать бесплатно с Asset Store. Стоит отметить, что SDK написан достаточно давно и не слишком активно обновляется. В частности для совместимости с Unity 4.6 необходимо кое что поправить. Открываем файл FB.cs и там меняем в 411 строке UNITY_4_5 на UNITY_4_6. Будет работать теперь. Ну или перепишите этот дефайн на правильный, чтобы работал и на 4.5, и на 4.6 и на всех последующих тоже.Далее нужно создать и настроить приложение на https://developers.facebook.com. После этого вы получаете App ID, который вписываете в настройки через инспектор.
Далее необходимо инициализировать Facebook в Unity. Для этого необходимо вызвать функцию FB.Init ().В документации сказано, что ее нужно вызвать один и только один раз при первом запуске игры. И вот тут могут возникнуть первые сложности. Дело в том, что в нее нужно передать 2 колбека. На окончание инициализации и на сворачивание игры библиотекой. Возникает вопрос где эту функцию вызвать. Если у вас вся игра на одной сцене и эта сцена никогда не перезагружается, то проблем, нет. Просто вызываете в Awake () какого-нибудь GameController-а.
В противном случае лучше делать либо статические функции, либо сразу синглтон. Мы использовали вот эту его реализацию. Получилось достаточно просто.
public class SocialController: Singleton
private void FacebookInited () { Debug.Log («FacebookInited»); if (FB.IsLoggedIn) { Debug.Log («Already logged in»); OnLoggedIn (); } }
private void OnHideUnity (bool isGameShown)
{
Debug.Log («OnHideUnity»);
if (! isGameShown)
{
GameController gameController = FindObjectOfType
Id пользователя Имя пользователя Список друзей — для реализации лидерборда друзей Исходя из этого и формируем список разрешений в функции FB.Login (). Нам сейчас нужно только user_friends.Залогинившись, можно запросить необходимую нам информацию: void OnLoggedIn () { Debug.Log («Logged in. ID:» + FB.UserId); FB.API (»/me? fields=name, friends», Facebook.HttpMethod.GET, FacebookCallback); }
void FacebookCallback (FBResult result) { if (result.Error!= null) { return; } string get_data = result.Text; var dict = Json.Deserialize (get_data) as IDictionary; _userName = dict[«name»].ToString (); friends = Util.DeserializeJSONFriends (result.Text);
GotUser (); GetBestScoresFriends (); } Функция Util.DeserializeJSONFriends () взята из официального примера и доступна вот здесь. В итоге в переменной _userName у нас будет имя игрока, а в friends — список его друзей.Parse Следующий шаг — сохранение очков и списка игроков. Так как от Facebook Score API мы отказались, то нам потребуется сервер. Самый простой способ его получить — использовать Parse. У него есть библиотека специально для Unity и доступна здесь. Впрочем, библиотека явно создавалась не специально для Unity, а была взята просто .NET версия, что еще вызовет определенные трудности.Настройка Parse больших сложностей не вызывает, благо официальный гайд написан достаточно хорошо. Отмечу лишь, что Parse Initialize Behaviour стоит добавлять именно к новому объекту, а не к геймконтроллеру, так как с ним объект не будет уничтожаться при перезагрузке сцены.
Parse предоставляет разработчикам большие возможности, но что же может понадобиться нам? Первое что мы думали использовать — это ParseUser — пользователь в терминологии Parse. Его можно создать нового или обновлять существующего. Нужны они для того чтобы унифицировать пользователей разных типов и связывать разные профили одного пользователя. Скажем, если пользователь сначала логинился к вам через email, а потом решил указать еще и аккаунт Facebook. Тогда вы можете добавить информацию о FB аккаунте игрока в его профиль. Однако, повозившись немного с ParseUser мы поняли, что они нам не особо то и нужны, так что дальше мы их оспользовать не будем.
А вот что нам точно понадобится, так это ParseObject. Каждый такой объект по сути — это запись в таблице данных. В какой таблице задается названием при создании объекта. Соответственно пишите new ParseObject («DataTable») — получите новую запись в таблице DataTable после того как вызовите метод Save (). Получается простой алгоритм для лидерборда. Ищем в таблице запись с текущим пользователем, если не нашли создаем новую. В любом случае у нас будет ParseObject с текущим пользователем. Записываем в него имя игрока, его рекорд и сохраняем.
private void GotUser ()
{
var query = ParseObject.GetQuery («GameScore»)
.WhereEqualTo («playerFacebookID», FB.UserId);
query.FindAsync ().ContinueWith (t =>
{
IEnumerable
if (! result.Any ())
{
Debug.Log («UserScoreParseObject not found. Create one!»);
_userScoreParseObject = new ParseObject («GameScore»);
_userScoreParseObject[«score»] = 0;
if (string.IsNullOrEmpty (_userName))
_userScoreParseObject[«playerName»] = «Player»;
else
_userScoreParseObject[«playerName»] = _userName;
_userScoreParseObject[«playerFacebookID»] = FB.UserId;
_userScoreParseObject.SaveAsync ();
}
else
{
Debug.Log («Found score on Parse!»);
_userScoreParseObject = result.ElementAt (0);
int score = _userScoreParseObject.Get
public void SaveScore (int score) { if (_userScoreParseObject == null) return;
Debug.Log («Save new score on Parse!» + score);
int oldScore = _userScoreParseObject.Get
public void GetBestScoresOverall () { var query = ParseObject.GetQuery («GameScore») .OrderByDescending («score») .Limit (5);
query.FindAsync ().ContinueWith (t =>
{
IEnumerable
string leaderboardString = »;
foreach (ParseObject parseObject in result)
{
leaderboardString += parseObject.Get
GameController.overallLeaderboardString = leaderboardString; }); }
public void GetBestScoresFriends ()
{
if (friends!= null && friends.Any ())
{
List
if (friendIds.Any ()) { string regexp = FB.UserId; for (int i = 0; i < friendIds.Count; i++) { regexp += "|"; regexp += friendIds[i]; } var queryFriends = ParseObject.GetQuery("GameScore") .OrderByDescending("score") .WhereMatches("playerFacebookID", regexp, "") .Limit(5);
queryFriends.FindAsync ().ContinueWith (t =>
{
IEnumerable
string leaderboardString = »;
foreach (ParseObject parseObject in result)
{
leaderboardString += parseObject.Get
GameController.friendsLeaderboardString = leaderboardString; }); } } } Если с функцией получения глобального лидерборда все более-менее понятно, то вот написание функции выбора друзей может быть не тривиально. В данном случае мы составляем регулярное выражение, которое выбирает из таблицы нас и наших друзей. Мало того, что функция WhereMatches () по документации может работать медленно, так еще и регулярное выражение может получиться достаточно длинным (зависит от количества друзей, тоже играющих в эту игру). Думаю, этот способ не подойдет для сколько-нибудь крупных проектов, но для небольшой игры пока работает замечательно и проблем не вызывало. Впрочем, буду благодарен, если кто то опишет как в этом случае надо поступать «по уму».Бонус После всего этого мы получили рабочий лидерборд с авторизацией через Facebook. А раз уж у нас уже есть авторизация, то почему бы не сделать возможность поделиться результатом с друзьями. Самое классное, что если не делать это автоматически, а предлагать пользователю стандартный диалог, то вам не потребуются специальные разрешения. public void ShareResults () { string socialText = string.Format («I scored {0} in Sentinel. Can you beat it?», GameController.BestScore);
FB.Feed ( link: «https://apps.facebook.com/306586236197672», linkName: «Sentinel», linkCaption: «Sentinel @ LudumDare#31», linkDescription: socialText, picture: «https://www.dropbox.com/s/nmo2z079w90vnf0/icon.png? dl=1», callback: LogCallback ); } Спасибо, что дочитали до конца. Делайте хорошие игры и подталкивайте игроков к соревнованию с лидербордами.Кому интересно как это в итоге работает — итоговый результат. А вот здесь можно поиграть в изначальную версию.