Связываем две мобильные платформы в едином коде на Unity

от автора

Для Unity-разработчиков уже привычно управление игровыми потоками и сервисами на таких платформах, как iOS и Android. Однако после того, как в экосистеме появились мобильные сервисы Huawei, теперь нужно поддерживать и еще одну версию игры, если вы хотите охватить игроков, у которых есть девайсы этой фирмы.

Эта статья — как раз о том, как минимальными усилиями управлять зависимостями между несколькими мобильными сервисами Android и Huawei в одной кодовой базе.

image
Рисунок 1. Разница между поддержкой разных платформ и мобильных сервисов

Как видно на рисунке выше, мобильные телефоны Huawei используют операционную систему Android, так что и ваш Unity-проект должен использовать ее же при сборке для девайсов этой фирмы. Однако теперь вам нужно разработать 2 разных APK на Android или же отдельный пакет для Huawei AppGallery.

В чем разница между этими APK?

Первое и главное различие — мобильные сервисы. Речь идет о таких сервисах, как внутриигровые покупки, реклама, игровые сервисы, аналитика и т. д. Вы не можете использовать GMS в мобильных устройствах Huawei. Из-за этого возникает необходимость в создании двух APK: для релиза в Huawei AppGallery и в Google Play.

Второе не менее важное отличие — имя пакета. Поскольку обе экосистемы работают на Android, чтобы избежать несогласованности и переопределения, в Huawei App Gallery заведено правило: имя вашего пакета должно заканчиваться на .huawei или .HUAWEI. Такой подход используется для отделения сборок для Huawei от всех остальных девайсов на Android.

Но не волнуйтесь: мы можем справиться с этими различиями в одной кодовой базе.

Расскажем о двух небольших приемах, которые помогут решить эти проблемы.

1. Вы когда-нибудь слышали о #defines?

Благодаря определениям (defines) мы можем управлять нашими потоками во время сборки, а благодаря единой среде разработки — и во время кодирования.

О каких потоках речь?

Представьте, что вам нужно оперировать двумя видами игровых сервисов: Google Play и Huawei. Чтобы создать для них приложение в одном коде, можно разделить его при помощи определений (defines). Рассмотрим небольшой пример:

internal static class GameServiceFactory {     public static IGameServiceProvider CreateGameServiceProvider()     {         #if HMS_BUILD             return new HMSGameServiceProvider();         #else             return new GooglePlayGameServiceProvider();                                 #endif     } } 

Если вы добавите ключевое слово «HMS_BUILD» в ваш список определений, игра вызовет HMSGameServiceProvider. Так мы сможем управлять нашими потоками в одном коде.

Для управления определениями перед сборкой можно использовать приведенный ниже скрипт. После изменения и сохранения DefineKeywords интегрированная среда разработки обновит поток кода в соответствии с заданными ключевыми словами.

public class ManageDefines : Editor  {      /// <summary>      /// Symbols that will be added to the editor      /// </summary>      public static readonly string [] DefineKeywords = new string[] {         //"TEST_VERSION",         "HMS_BUILD",         //"GMS_BUILD",      };         /// <summary>      /// Add define symbols as soon as Unity gets done compiling.      /// </summary>      static AddDefineSymbols ()      {          List<string> allDefines = new List<string>();          allDefines.AddRange ( DefineKeywords.Except ( allDefines ) );          PlayerSettings.SetScriptingDefineSymbolsForGroup (              EditorUserBuildSettings.selectedBuildTargetGroup,              string.Join ( ";", allDefines.ToArray () ) );      }  } 

2. Скрипты до и после сборки (pre-build и post-build)

Итак, как уже упоминалось ранее, нам нужно изменить имя пакета нашей игры для версии Huawei AppGallery.

Однако, если вы в то же время используете сервисы Google Play, все конфигурации будут привязаны к вашему существующему имени пакета, и его изменение повлияет на конфигурацию. Кроме того, редактор Unity во всплывающем окне будет из раза в раз предупреждать вас об исправлении имени пакета приложения, ведь теперь имя пакета и конфигурация мобильного сервиса отличаются. Закрывать это всплывающее окно снова и снова очень утомительно.

Чтобы решить эту проблему и управлять двумя разными именами пакетов одновременно, можно использовать обходной путь с помощью pre-build и post-build сценариев с определениями.

Взгляните на этот код:

class MyCustomBuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport {     public int callbackOrder { get { return 0; } }      public void OnPostprocessBuild(BuildReport report)     {         PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, "your.package.name");     }      public void OnPreprocessBuild(BuildReport report)     {         #if HMS_BUILD             PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, "your.package.name.huawei");         #elif GMS_BUILD             PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, "your.package.name");         #endif     } } 

Как видите, все просто. С помощью определений мы можем изменить имя пакета во время pre-build только для HMS_BUILD. Затем после сборки можно снова вернуть имя пакета к исходному, и тогда Unity больше не будет предупреждать нас о несовпадении имени пакета.

Вот и все. Теперь мы готовы разрабатывать игру в одном коде для Huawei и Google Play одновременно.

Пример разработки приложения

Создадим приложение в одной кодовой базе для Android и Huawei, включающее в себя игровые сервисы, внутриигровые покупки и рекламу.

Мы не будем реализовывать все функции для каждого сервиса, поскольку это всего лишь демонстрационный пример. Каждую фичу и функциональность вы можете расширить самостоятельно.

Структура сервисных модулей

image
Рисунок 2. Схема работы сервисных модулей

Для каждого сервиса у нас будут свои;

  • Менеджер (Manager): этот класс включает в себя игровую логику для сервисов и управляет общими функциональностями. Для разных игр может потребоваться изменение этого класса в соответствии со своими требованиями.
  • Фабричный класс (Factory): этот класс включает в себя логику выбора провайдера. В нашем подходе мы будем использовать определения, но вы можете изменить механизм выбора провайдера на свой вкус.
  • Провайдер (Provider): для каждого сервиса нам нужно создать свой провайдер. Все зависимости между вашим проектом и мобильными сервисами должны оставаться в рамках этого класса.
  • Интерфейс провайдера (Provider Interface): для унификации использования различных мобильных сервисов нам нужны общие провайдеры, и с этой целью заводятся интерфейсы провайдеров. В соответствии с требованиями вашей игры вам нужно определить все методы, которые вы будете использовать в своей игре из мобильных сервисов.

При необходимости можно еще включить:

  • Общие сущности (entities): для абстрагирования некоторых сервисов от вашей игры вам могут понадобиться общие сущности. Чтобы сохранить зависимость только от классов провайдеров, можно использовать общие сущности в соответствии с вашими требованиями.
  • Общих слушателей (listeners): аналогично общим сущностям, если вам нужны общие слушатели, вы можете создать и их.

image
Рисунок 3. Пример структуры для модуля игрового сервиса

Теперь давайте рассмотрим примеры и попытаемся понять, что мы можем сделать с помощью описанного в статье метода.

Чтобы абстрагировать мобильные сервисы от игровой логики, мы будем использовать менеджеров. Менеджеры связываются с провайдерами через fabrics. Таким образом, мы можем использовать провайдеров как плагины. Им необходимо реализовать общий интерфейс, содержащий методы, к которым мы хотим получить доступ.

public interface IGameServiceProvider {     void Init();     bool IsAuthenticated();     void SignOut();     void AuthenticateUser(Action<bool> callback = null);     void SendScore(int score, string boardId);     void ShowLeaderBoard(string boardId = "");     void ShowAchievements();     void UnlockAchievement(string key);     CommonAuthUser GetUserInfo(); } 

Затем нам понадобится хотя бы один провайдер, реализующий интерфейс сервиса. Как уже говорилось ранее, все зависимости между игрой и сторонними мобильными сервисами должны оставаться в пределах этого класса. Создадим два провайдера: для Huawei и Google Play.

Приведем примеры кода. Их вы можете найти в проекте на GitHub в конце статьи.

Huawei Game Service Provider

public class HMSGameServiceProvider : IGameServiceProvider  {       private static string TAG = "HMSGameServiceProvider";        private HuaweiIdAuthService _authService;       private IRankingsClient _rankingClient;       private IAchievementsClient _achievementClient;        public AuthHuaweiId HuaweiId;        public CommonAuthUser commonAuthUser = null;        public void Init()       {           InitHuaweiAuthService();       }        ....          }

Google Game Service Provider

public class GooglePlayGameServiceProvider : IGameServiceProvider {     private static string TAG = "GooglePlayServiceProvider";      private PlayGamesPlatform _platform;      public CommonAuthUser commonAuthUser = null;      public void Init()     {         InitPlayGamesPlatform();     }      .... } 

Теперь нам нужно создать менеджер и класс factory. Тогда мы получим провайдера в соответствии с нашим определением.

public class GameServiceManager : Singleton<GameServiceManager> {         public IGameServiceProvider provider;          protected override void Awake()         {             base.Awake();             provider = GameServiceFactory.CreateGameServiceProvider();         }                  .....          } 

Так выглядит наш класс factory:

internal static class GameServiceFactory {     public static IGameServiceProvider CreateGameServiceProvider()     {         #if HMS_BUILD             return new HMSGameServiceProvider();         #else             return new GooglePlayGameServiceProvider();                                 #endif     } } 

Вот и все: теперь у нас есть класс GameManager, который может управлять функциями игрового сервиса с несколькими провайдерами.

Чтобы инициализировать GameServices, используем приведенные ниже строки кода там, где это нужно:

public class MainSceneManager : MonoBehaviour {     void Start()     {         GameServiceManager.Instance.Init();                  ....              }          ....          private void OnClickedScoreBoardButton()     {         GameServiceManager.Instance.provider.ShowLeaderBoard();     }      private void OnClickedAchievementButton()     {         GameServiceManager.Instance.provider.ShowAchievements();     }          .... }  

Не будем вдаваться в работу каждого сервисного модуля, поскольку все они имеют одинаковую логику и структуру. Посмотреть на них в деле можно, скопировав код из проекта на GitHub.

Кроме того, если вам нужно руководство по настройке плагина Huawei в Unity, это описано в данном посте.

Перейдем к выводам.

С помощью изменения определений между HMS_BUILD и GMS_BUILD в DefineConfig.cs:

  1. мы можем получить два разных APK или пакета для Huawei AppGallery и Google Play;
  2. между HMS и GMS меняются функции входа и выхода (Login&Logout), доски лидеров, достижения, внутриигровые покупки.

Ниже приведены короткие видеозаписи обеих сборок.

В случае «HMS_BUILD»:

image

В случае «GMS_BUILD»:

image

Демо-проект и подготовленные APK для HMS и GMS можно найти по ссылке на GitHub.

ссылка на оригинал статьи https://habr.com/ru/company/pixonic/blog/532496/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *