[Перевод] Связываем две мобильные платформы в едином коде на 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
 {
     /// 
     /// Symbols that will be added to the editor
     /// 
     public static readonly string [] DefineKeywords = new string[] {
        //"TEST_VERSION",
        "HMS_BUILD",
        //"GMS_BUILD",
     };
  
     /// 
     /// Add define symbols as soon as Unity gets done compiling.
     /// 
     static AddDefineSymbols ()
     {
         List allDefines = new List();
         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 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
{
        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.

 

Источник

android, c#, Huawei, unity

Читайте также