Unity3D + Google Services: мультиплеер для вашего проекта на Android и iOS
В этой статье я хочу рассказать об использовании игровых сервисов Google в вашем приложении на Unity. На написание данного материала меня сподвигло достаточно большое количество проблем, встретившихся во время разработки нашего приложения, а также отсутствие каких-либо материалов на русском языке по этой теме. Да и собственно, на английском тоже. Описание использующегося плагина на гитхабе очень краткое и не дает ответа на возможные проблемы с работой сервисов. Думаю, здесь не стоит пояснять, что мультиплеер и рейтинги игроков зачастую повышают интерес пользователей, а следовательно и вашу возможную прибыль. А благодаря данной статье начинающие разработчики смогут начать использовать данные преимущества.
Плагин
Мы использовали бесплатный плагин Play Games For Unity. В нем содержатся библиотеки для работы с сервисами Google. Плагин включает в себя авторизацию пользователя в Google+, возможность использования достижений и рейтинга для игроков, облаков Google для хранения данных и организации мультиплеера как в реальном времени, так и в пошаговом режиме. Установка плагина не вызывает никаких трудностей: в Unity нужно выбрать импорт ассета (Assets->ImportPackage->CustomPackage) и в открывшемся окне выбрать ассет, находящийся в папке «current-build».
Далее вам нужно ввести id своего приложения: откройте появившийся выпадающий список «Google Play Games» и выберете пункт «Android Setup». Id приложения вы можете узнать в Google Developer Console. Он выдается после добавления нового приложения в «Игровые сервисы», вы можете видеть его рядом с названием вашей игры. После добавления id можно переходить непосредственно к коду.
Для инициализации плагина используйте следующий код:
PlayGamesPlatform.Activate(); достаточно вызывать лишь один раз после запуска вашего приложения. После инициализации вы можете получить доступ к платформе при помощи Social.Active.
Чтобы реализовать авторизацию пользователя используйте следующий код:
Переменная success принимает значения true при удачном входе и false, соответственно, при неудачном. В нашем случае при удачном входе вызывается метод загрузки необходимых данный пользователя из облака Google. И сразу же первая проблема, встретившаяся у нас на пути: метод вызывается, но при неудачной попытке не возвращает false, а следовательно авторизация не проходит и нет возможности при (!success) вызвать метод еще раз. Пришлось написать костыль, который вызывает метод с определенным интервалом, пока авторизация не пройдет (при условии, что пользователь подтвердил запрос на авторизацию перед этим).
После авторизации пользователя мы получаем возможность использовать сервисы Google.
Поднимаем мультиплеер
Кое-что из этого описано на гитхабе, кое-что на developers.google. Здесь я собрал полезную выжимку.
В плагине доступно 4 режима работы мультиплеера:
- Создание комнаты со случайными игроками (быстрая игра)
- Создание комнаты с экраном приглашения (позволяет приглашать в игру друзей из кругов Google+)
- Обзор приглашений (позволяет видеть, кто из друзей в Google+ хочет пригласить вас в игру)
- Приглашения по id (его рассматривать не будем, т.к. не использовали данный режим в нашем приложении; интересующиеся могут прочитать о нем по ссылке на Github)
Для того, чтобы облегчить работу со следующими функциями ваш класс должен наследоваться от интерфейса RealTimeMultiplayerListener.
Создать «быструю игру»/соединиться
Создается комната, куда набираются случайные оппоненты, или же происходит автоматическое присоединение к уже созданной комнате.
Очевидно, что минимальное и максимальное количество игроков определяется переменными MinOpponents, MaxOpponents. В нашей игре MaxOpponents = 1, это означает, что в мультиплеере у вас будет только один противник.
Если ваш класс наследуется от RealTimeMultiplayerListener, то вместо listener вам нужно написать this.
Создание комнаты с экраном приглашения
Почти идентично предыдущему. Игрок может пригласить друзей из Google+ или добавить случайных оппонентов.
Обзор приглашений
Откроется меню, в котором игрок может видеть свои приглашения из Google+.
Соединение с комнатой
Следующий метод позволяет показать пользователю загрузку во время создания или соединения с комнатой:
В нашем случае мы просто выводим переменную progress.
Когда соединение с комнатой произошло удачно (или нет) вызывается следующий метод:
В нашей игре для каждой онлайн гонки препятствия генерируются рандомно, и соответственно их положение должно быть одинаково на обоих телефонах. При успешном подключении из списка участников выбирается хост и на его телефоне генерируются препятствия. После того как они сгенерировались, сразу же начинается передача сообщений с параметрами уровня на другой телефон, и как только принимающий телефон загрузил последнее препятствие, он отправляет сообщение о том что готов и игра начинается. Также передается порядковый номер, используемого космического корабля, чтобы на экране другого игрока отображалась верная модель. Более того передаются различные служебные переменные, необходимые для определения готовности всех необходимых для игры параметров.
id участников
Чтобы узнать id всех участников, включая ваш, вы можете использовать следующий код (может выполняться только после соединения с комнатой).
Для всех участников лист будет отсортирован одинаково.
Для того чтобы узнать свой Id воспользуйтесь следующим методом:
Отправка сообщений:
Плагин поддерживает 2 типа сообщений, надежное и ненадежное. Надежное сообщение медленнее, но гарантирует доставку, ненадежное быстрее, но проверка доставки не осуществляется.
Для отправки надежного сообщения используется следующий код:
Соответственно для ненадежного следует изменить переменную reliable на false. Сообщение отправится всем участникам комнаты, кроме вас.
Вы так же можете отправить сообщение конкретному участнику:
Этим способом вы можете отправить сообщение сами себе, указав в participantId свой id.
Максимальная длина одного надежного сообщения – 1400 байт, ненадёжного 1168 байт.
Здесь также возникла проблема: даже если отправлять по одному сообщению за фрейм, то они не отправляются. Мы так и не выяснили с чем это связано, вероятно они просто не успевают формироваться (возможно кто-то в комментариях поправит меня). Поэтому был сделан счетчик фреймов и сообщения отправлялись через строго определенный интервал, измеряемый во фреймах. Тогда все стало работать великолепно. Во время игры наше приложение подразумевает постоянную отправку сообщений (координаты и угол поворота космического корабля), поэтому используются ненадежные сообщения, потому что скорость передачи выходит на первый план, а если пара пакетов и потеряется, то ничего страшного, при достаточно частой передаче человек практически не заметит этого. Но при присоединении игроков к комнате все отправляемые сообщения с параметрами участников и их кораблей являются надежными, так как отправляются только один раз и их значения критически важны для начала гонки.
Проверяем получение всех необходимых сообщений для начала игры:
Наконец-то после десятков билдов и тестов все начало работать:
Получение сообщений
Когда вы получаете сообщение, вызывается следующий метод:
Полученное сообщения полностью идентично отправленному, как по длине, так и по содержанию.
Так как в нашей игре используется множество различных сообщений (будь то координаты корабля, либо сообщение о выигрыше одно из участников и т.п.), чтобы понять какое сообщение принято, мы пошли на достаточно простой и очевидный шаг: первым байтом шло число, определяющее, что за сообщение и какой метод вызвать при его получении, а со второго байта начинались передаваемые данные.
События соединения
Если пользователь отключается от комнаты, вызывается следующий метод:
Важно: При сворачивании игры игрок отключается от комнаты. Возможно, для некоторых приложений это будет проблемой. Но в нашем случае сворачивание неминуемо привело бы к поражению, в виду специфики игры. Поэтому при отключении от комнаты одного из участников вызывается метод OnPeersDisconnected(), описанный далее.
Если кто-то подсоединяется или отсоединяется от комнаты, будут вызваны следующие методы.
Участники могут коннектиться в любое время во время игры, если есть пустые слоты, и тут надо следить, чтобы участник не подсоединился после того, как игра уже началась. Можно сделать ожидание, пока все слоты не заполнятся и только тогда начинать игру.
В нашей игре при досрочном выходе одно из участников отправляется сообщение, определяющее победу соперника и игра заканчивается, а следовательно нужно вызвать нижеследующий метод.
Выход из игры
После того, как ваша игра окончена, вам нужно выйти из комнаты:
Источник
Платформер под Android на Unity3D
Пожалуй, даже после выхода нового UI, создание интерфейса для Android’а осталось больной темой для многих.
Новая система “UI”, которая появилась в Unity 4.6 сильно упростила жизнь разработчикам, но все же, хотелось бы прояснить некоторые моменты, которые относятся к Android’у.
Из плюсов:
-Мощный набор инструментов
-Корректное масштабирование на разных разрешениях, что в свою очередь устраняет кучу лишней работы
-Поддержка сенсорного управления без дополнительных настроек
-Гибкость и простота в использовании
Статья будет разделена на две части.
Первая (базовая) — для тех, кто еще только начинает пользоваться Unity3D. Вторая — собственно, сама реализация управления для платформера под Android.
Часть первая
1) Для начала, создайте новую сцену File > New Scene.
2) В папке “Assets” создайте еще две папки: “Scripts” и “Sprites”.
3) Далее, спрайты, которые я подготовил, добавляем в папку “Sprites”.
Спрайты:
Ставим Filter Mode на пункт “Point” платформе и персонажу, поскольку они выполнены в пиксель арте.
Перетаскиваем на сцену спрайт персонажа, пару спрайтов платформ и фон(предварительно увеличив его в размере).
Должно получиться что-то похожее на:
Добавим нашему персонажу такие компоненты как:Rigidbody2D,CircleCollider2D и заморозим возможность вращения по оси Z.
Всем платформам добавим компонент: BoxCollider2D.
4) Добавим на сцену три Image’а через GameObject > UI > Image.
Это и есть наш будущий интерфейс(кнопки: вправо, влево, прыжок).
Подгоняем размеры и идем дальше:
Image’ам слева ставим “привязку” к левому нижнему краю, а правому — к правому нижнему.
Для каждого Image’а в Source Image перетаскиваем свой спрайт.
В результате должно получиться:
Вот и конец первой части.
Во второй части, мы перейдем к скриптингу и добавим кнопкам функционал.
Часть вторая
Теперь перейдем к основному:
1) Создайте новый скрипт в папке “Scripts” и назовите его “CharController” (писать будем на C#).
Вставляем в него код:
“Простота — залог успеха” — в нашем случае так и есть. Передвижение и прыжки персонажа были реализованы через отдельные void’ы.
Повесьте данный скрипт на персонажа. Склеить все это дело вместе, нам поможет Event System.
2) Для удобства переименуйте “кнопки” чтобы не запутаться.
Например:”leftButton”,”rightButton”,”jumpButton”.
Добавим каждой кнопке компонент Event Trigger.
Теперь, кнопке “Влево” в компоненте Event Trigger создайте два новых события — PointerDown и PointerExit.
В PointerDown и PointerExit создайте по одному событию, перетащите на каждый нашего персонажа(на котором обязательно должен висеть скрипт).Кликаем по выпадающему меню и находим наш скрипт “CharController” > void “Move(int)”.
Аналогичные манипуляции проведем с кнопкой “Вправо”.
Кнопке “Прыжок” добавим только PointerEnter > CharController > Jump(bool).
3) Пришло время выставлять значения.
Для кнопки “Вправо” тоже поменяем значение в PointerDown, но на “1”.
4) Запустим наш проект:
Конечно назвать данную статью “Разработка от А до Я” нельзя, но думаю многим теперь будет проще сделать управление для Android’а. Всем спасибо за внимание.
Источник
Основы многопользовательской игры на Unity3D
Я, как и многие из вас, большой поклонник многопользовательских игр. В них меня прельщает в основном дух соревнования и возможность приобретать улучшения, накапливая достижения. Да и сама идея выхода в свет все большего количества игр данного типа побуждает к действию.
С недавнего времени я и сам взялся за разработку собственного проекта. И поскольку на Хабрахабре статей на эту тематику не нашел – решил поделиться своим опытом написания многопользовательской игры на движке Unity3D. Также хочу рассказать о компонентах Network и NetworkView, атрибуте RPC и встроенных методах-ивентах. В конце статьи подан пример игры и, разумеется, сам проект для Unity. Итак…
Класс Network
Данный класс нужен для организации соединения «клиент-сервер». Основные функции: создание сервера, подключение к серверу, создание сетевого экземпляра префаба.
Основные методы:
Network.Connect (string host, int remotePort, string password = «») – выполняет подключение к серверу host с портом remotePort и паролем password. Метод возвращает перечисление NetworkConnectionError.
Network.InitializeServer(int connections, int listenPort, bool useNat) – создает сервер с максимально разрешенным количеством подключений connections; порт входящих подключений listenPort, а также useNat: использовать либо нет NAT . Также возвращает перечисление NetworkConnectionError.
Network.InitializeSecurity() – вызывается перед Network.InitializeServer() для защиты от читерства. Подробности в официальной документации. Не вызывать на клиенте!
Network.Instantiate(Object prefab, Vector3 position, Quaternion rotation, int group) – создает экземпляр префаба prefab в сети в позиции position с поворотом rotation и группой group. Возвращает весь созданный объект, с которым после создания можно выполнить дополнительные действия. Подробности – далее в статье.
Основные свойства:
bool Network.isClient и bool Network.isServer – определяют, является ваша игра сервером либо клиентом. Оба свойства являются false, если не был создан сервер или не было подключения к серверу.
string Network.incomingPassword – свойство задает пароль для входящих подключений.
NetworkPlayer Network.player – возвращает экземпляр локального игрока NetworkPlayer.
NetworkPeerType Network.peerType – возвращает текущее состояние подключения: Disconnected (отключен), Server (запущен как сервер), Client (подключен к серверу), Connecting (попытка, в процессе подключения).
NetworkPlayer[] Network.connections – возвращает всех подключенных игроков. На клиенте возвращает только игрока сервера.
Основные ивенты (для унаследованного от MonoBehaviour):
OnConnectedToServer() – вызывается на клиенте при успешном подключении к серверу.
OnDisconnectedFromServer(NetworkDisconnection info) – вызывается на клиенте при отключении от сервера и на сервере при завершении подключений Network.Disconnect(). В info содержится причина отключения: LostConnection (потеря связи) и Disconnected (при успешном отключении).
OnFailedToConnect(NetworkConnectionError error) — вызывается на клиенте при ошибке подключения. error содержит ошибку типа NetworkConnectionError.
OnNetworkInstantiate(NetworkMessageInfo info) — вызывается на клиенте и сервере, если был создан новый экземпляр методом Network.Instantiate(). Содержит info типа NetworkMessageInfo.
OnPlayerConnected(NetworkPlayer player) — вызывается на сервере при успешном подключении клиента и содержит player типа NetworkPlayer.
OnPlayerDisconnected(NetworkPlayer player) — вызывается на сервере при отключении клиента и содержит player типа NetworkPlayer.
OnServerInitialized() — вызывается на сервере, после того как сервер был успешно создан.
OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) — важный ивент для синхронизации компонента с сетью. Подробности – далее в статье.
Класс NetwokView
Данный класс существует также и как компонент для Unity, и предназначен он для синхронизации компонентов в сети и для вызова RPC .
Обладает такими свойствами синхронизации NetworkStateSynchronization:
- Off — не выполняет синхронизацию объекта, однако позволяет вызывать удаленные процедуры.
- ReliableDeltaCompressed — выполняет передачу пакетов поочередно и проверяет, доставлен ли пакет (подобно протоколу TCP).
- Unreliable — выполняет быструю отправку пакетов, не гарантируя доставки (подобно протоколу UDP).
Основные методы:
networkView.RPC(string name, RPCMode mode, params object[] args) — вызывает удаленную процедуру name, mode определяет получателей, args – аргументы для передачи процедуре.
networkView.RPC(string name, NetworkPlayer target, params object[] args) – то же, что и предыдущий метод, однако выполняет отправку конкретному игроку NetworkPlayer.
Основные свойства:
bool networkView.isMine – свойство, определяющее, является ли объект локальным. Весьма часто используется для проверки владельца объекта.
Component networkView.observed – компонент, который будет синхронизироваться. Если это скрипт, то он должен содержать метод OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info), упомянутый выше.
NetworkPlayer networkView.owner – свойство, возвращающее владельца объекта.
NetworkStateSynchronization networkView.stateSynchronization — тип синхронизации: Off, ReliableDeltaCompressed, Unreliable.
NetworkViewID networkView.viewID — уникальный идентификатор в сети для NetworkView.
Атрибут RPC
Метод OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
Данный метод используется для синхронизации компонента в сети. Он вызывается всякий раз при получении либо отправке данных по сети.
Вот типы данных, которые могут быть получены/отправлены методом Serialize: bool, char, short, int, float, Quaternion, Vector3, NetworkPlayer, NetworkViewID.
Для проверки, идет ли прием либо передача, используются свойства isReading или isWriting.
Привожу пример использования:
Данный пример не идеален, поскольку при его работе наши объекты будут «дергаться». Чтобы избежать этого, нужно воспользоваться интерполяцией. Подробнее – далее в статье.
Интерполяция
Подробнее о методах оптимизации синхронизации по сети смотрите на сайте разработчиков: Valve Developer Community — Source Multiplayer Networking
Пример многопользовательской игры
Итак, имея представления об основах, можно приниматься за написание небольшой многопользовательской игры. В качестве примеров я использую разные способы применения NetworkView. Вам остается лишь выбрать для себя наиболее удобный способ.
Создаем скрипт ServerSide.cs и пишем туда следующее:
Теперь создаем скрипт клиента ClientSide.cs:
Таким образом, клиентская и серверная логика есть, теперь для нее нужно сделать управление MainMenu.cs:
Управление сетью создано. Далее пишем управление игроком PlayerControls.cs. В данном примере я использую другой способ применения компонента NetworkView:
Знаю, что синхронизация и управление должны находиться раздельно, но для примера я решил объединить их. Как вы заметили, здесь NetworkView создается во время инициализации скрипта. На мой взгляд, это более удобный способ для защиты от возможного «забыл добавить» (разумеется, если не написано RequireComponent( typeof( Rigidbody ))), а также уменьшает в инспекторе количество компонентов на объекте.
К примеру, у меня был случай: когда, на первый взгляд, все было сделано правильно, однако мой скрипт не делал интерполяцию, и все мои действия в синхронизации игнорировал. Так вот ошибкой оказалось то, что Observed был не моим скриптом, а трансформ объекта.
Итак, теперь у нас есть все необходимые скрипты для написания мини-игры.
Создаем пустой объект и назначаем ему скрипты MultiplayerMenu, ServerSide, ClientSide.
Создаем плоскость и немного опускаем.
Создаем префаб игрока (в моем примере это будут шары). Создаем объект «сфера», назначаем ему скрипт PlayerControls и добавляем в префаб. Префаб перетягиваем на ClientSide в поле Player Prefab.
На этом все, компилируем проект (не забывая в настройках игрока включить Run in background) и запускаем несколько раз. В одном из окон жмем сервер, на остальных – клиент, и смотрим на результат.
Ссылка на проект.
*В проекте могут быть логические ошибки, но на суть данной статьи они не влияют.
Всех благодарю за внимание!
Желаю успехов в создании многопользовательских игр!
Источник