Conectando dos plataformas móviles en un solo código en Unity





Los desarrolladores de Unity ya están acostumbrados a administrar transmisiones de juegos y servicios en plataformas como iOS y Android. Sin embargo, después de que aparecieran los servicios móviles de Huawei en el ecosistema, ahora debe admitir otra versión del juego si desea llegar a los jugadores que tienen dispositivos Huawei.



Este artículo trata solo sobre cómo administrar las dependencias entre varios servicios móviles de Android y Huawei en una base de código con un esfuerzo mínimo.







imagen

Figura 1. La diferencia entre la compatibilidad con diferentes plataformas y servicios móviles



Como puede ver en la figura anterior, los teléfonos móviles de Huawei usan el sistema operativo Android, por lo que su proyecto Unity debería usarlo al crear dispositivos de esta empresa. Sin embargo, ahora necesita desarrollar 2 APK diferentes para Android o un paquete separado para Huawei AppGallery.





¿Cuál es la diferencia entre estos APK?



La primera y más importante diferencia son los servicios móviles. Estos son servicios como compras dentro de la aplicación, publicidad, servicios de juegos, análisis, etc. No puede usar el GMS en dispositivos móviles Huawei. Debido a esto, se hace necesario crear dos APK: para su lanzamiento en Huawei AppGallery y en Google Play.



La segunda diferencia importante es el nombre del paquete. Dado que ambos ecosistemas se ejecutan en Android, para evitar inconsistencias y anulaciones, la Galería de aplicaciones de Huawei tiene una regla de que el nombre de su paquete debe terminar en .huawei o .HUAWEI. Este enfoque se utiliza para separar las compilaciones de Huawei de todos los demás dispositivos Android.



Pero no se preocupe: podemos lidiar con estas diferencias en una base de código.



Aquí hay dos pequeños trucos para ayudar a resolver estos problemas.



1. ¿Alguna vez has oído hablar de #defines?



Gracias a define (define) podemos controlar nuestros hilos en el momento de la compilación y gracias a un único entorno de desarrollo, y durante la codificación.



¿De qué corrientes estamos hablando?



Imagine que necesita operar dos tipos de servicios de juegos: Google Play y Huawei. Para crear una aplicación para ellos en un código, puede separarlo usando definiciones. Veamos un pequeño ejemplo:



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

      
      





Si agrega la palabra clave "HMS_BUILD" a su lista de definiciones, el juego llamará a HMSGameServiceProvider. De esta manera podemos administrar nuestros hilos en un código.



El siguiente script se puede utilizar para manipular definiciones antes del ensamblaje. Después de cambiar y guardar DefineKeywords, el IDE actualizará el flujo de código de acuerdo con las palabras clave que especificó.



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. Scripts antes y después de la compilación (



precompilación y poscompilación ) Entonces, como se mencionó anteriormente, debemos cambiar el nombre del paquete de nuestro juego para la versión de Huawei AppGallery.



Sin embargo, si está utilizando los servicios de Google Play al mismo tiempo, todas las configuraciones estarán vinculadas al nombre de su paquete existente y cambiarlo afectará la configuración. Además, el editor de Unity en una ventana emergente le advertirá de vez en cuando que corrija el nombre del paquete de la aplicación, porque ahora el nombre del paquete y la configuración del servicio móvil son diferentes. Cerrar esta ventana emergente una y otra vez es muy tedioso.



Para resolver este problema y administrar dos nombres de paquetes diferentes al mismo tiempo, se puede utilizar una solución alternativa con scripts previos y posteriores a la compilación con definiciones.



Eche un vistazo a este código:



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
    }
}

      
      





Como ves, todo es sencillo. Con la ayuda de las definiciones, podemos cambiar el nombre del paquete durante la compilación previa solo para HMS_BUILD. Luego, después de la construcción, puede devolver el nombre del paquete al original, y luego Unity ya no nos advertirá sobre la falta de coincidencia del nombre del paquete.



Eso es todo. Ahora estamos listos para desarrollar un juego en un código para Huawei y Google Play al mismo tiempo.





Ejemplo de desarrollo de aplicaciones



Creemos una aplicación en la misma base de código para Android y Huawei, que incluye servicios de juego, compras dentro del juego y publicidad.



No implementaremos todas las funciones para cada servicio, ya que esto es solo una demostración. Puede ampliar cada característica y funcionalidad usted mismo.





Estructura del módulo de servicio





imagen

Figura 2. Esquema de funcionamiento de los módulos de servicio



Para cada servicio tendremos el nuestro propio;



  • Administrador : esta clase incluye la lógica del juego para los servicios y administra la funcionalidad general. Es posible que diferentes juegos deban cambiar esta clase para adaptarse a sus necesidades.
  • Clase de fábrica (Factory) : esta clase incluye la lógica para elegir un proveedor. En nuestro enfoque, usaremos definiciones, pero puede cambiar el mecanismo de selección de proveedores a su gusto.
  • Proveedor : para cada servicio, necesitamos crear su propio proveedor. Todas las dependencias entre su proyecto y los servicios móviles deben permanecer dentro de esta clase.
  • Interfaz de proveedor : para unificar el uso de varios servicios móviles, necesitamos proveedores comunes y, para ello, se crean interfaces de proveedor. Según los requisitos de su juego, debe definir todos los métodos que utilizará en su juego desde los servicios móviles.


Si es necesario, también puede habilitar:



  • Entidades genéricas : es posible que necesites entidades genéricas para abstraer algunos servicios de tu juego. Para mantener la dependencia solo en las clases de proveedor, puede usar entidades genéricas según sus requisitos.
  • Oyentes genéricos : similar a las entidades genéricas, si desea oyentes genéricos, también puede crearlos.


imagen

Figura 3. Estructura de ejemplo para un módulo de servicio de juegos



Ahora veamos ejemplos e intentemos entender qué podemos hacer con el método descrito en el artículo.



Para abstraer los servicios móviles de la lógica del juego, usaremos administradores. Los gerentes se comunican con los proveedores a través de tejidos. De esta forma podemos utilizar proveedores como complementos. Necesitan implementar una interfaz común que contenga los métodos a los que queremos acceder.



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();
}

      
      





Entonces necesitamos al menos un proveedor que implemente la interfaz de servicio. Como se mencionó anteriormente, todas las dependencias entre el juego y los servicios móviles de terceros deben permanecer dentro de esta clase. Creemos dos proveedores: para Huawei y Google Play.



A continuación, se muestran algunos ejemplos de código. Puede encontrarlos en el proyecto en GitHub al final del artículo.





Proveedor de servicios de juegos de Huawei



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();
      }

      ....
        
}
      
      







Proveedor de servicios de juegos de Google



public class GooglePlayGameServiceProvider : IGameServiceProvider
{
    private static string TAG = "GooglePlayServiceProvider";

    private PlayGamesPlatform _platform;

    public CommonAuthUser commonAuthUser = null;

    public void Init()
    {
        InitPlayGamesPlatform();
    }

    ....
}

      
      





Ahora necesitamos crear un administrador y una clase de fábrica. Luego obtenemos el proveedor de acuerdo con nuestra definición.



public class GameServiceManager : Singleton<GameServiceManager>
{
        public IGameServiceProvider provider;

        protected override void Awake()
        {
            base.Awake();
            provider = GameServiceFactory.CreateGameServiceProvider();
        }
        
        .....
        
}

      
      





Así es como se ve nuestra clase de fábrica:



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

      
      





Eso es todo: ahora tenemos una clase GameManager que puede administrar la funcionalidad de un servicio de juegos de múltiples proveedores.



Para inicializar GameServices, use las siguientes líneas de código cuando corresponda:



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

    private void OnClickedAchievementButton()
    {
        GameServiceManager.Instance.provider.ShowAchievements();
    }
    
    ....
}


      
      





No entraremos en el trabajo de cada módulo de servicio, ya que todos tienen la misma lógica y estructura. Puede verlos en acción copiando el código del proyecto a GitHub.



Además, si necesita orientación sobre cómo configurar el complemento de Huawei en Unity, esto se describe en esta publicación .



Pasemos a las conclusiones.



Al cambiar las definiciones entre HMS_BUILD y GMS_BUILD en DefineConfig.cs:



  1. podemos obtener dos APK o paquetes diferentes para Huawei AppGallery y Google Play;
  2. entre HMS y GMS, las funciones de inicio de sesión y cierre de sesión, las tablas de clasificación, los logros y las compras en el juego están cambiando.


A continuación se muestran videos cortos de ambas asambleas.



En el caso de "HMS_BUILD":



imagen



En el caso de "GMS_BUILD": el



imagen



proyecto de demostración y los APK preparados para HMS y GMS se pueden encontrar en el enlace de GitHub .



All Articles