Unity developers are already used to managing game streams and services on platforms such as iOS and Android. However, after Huawei mobile services appeared in the ecosystem, now you need to support another version of the game if you want to reach players who have Huawei devices.
This article is just about how to manage dependencies between several Android and Huawei mobile services in one codebase with minimal effort.
Figure 1. The difference between support for different platforms and mobile services
As you can see in the figure above, Huawei mobile phones use the Android operating system, so your Unity project should use it when building for devices from this company. However, now you need to develop 2 different APKs for Android or a separate package for Huawei AppGallery.
What's the difference between these APKs?
The first and foremost difference is mobile services. These are services such as in-app purchases, advertising, game services, analytics, etc. You cannot use the GMS on Huawei mobile devices. Because of this, it becomes necessary to create two APKs: for release in Huawei AppGallery and in Google Play.
The second important difference is the package name. Since both ecosystems run on Android, in order to avoid inconsistencies and overrides, Huawei App Gallery has a rule that your package name must end in .huawei or .HUAWEI. This approach is used to separate Huawei builds from all other Android devices.
But don't worry: we can handle these differences in one codebase.
Here are two small tricks to help solve these problems.
1. Have you ever heard of #defines?
Thanks to the defines (defines) we can control our threads at build time, and thanks to a single development environment - and during coding.
What streams are we talking about?
Imagine that you need to operate two types of gaming services: Google Play and Huawei. To create an application for them in one code, you can separate it using definitions. Let's look at a small example:
internal static class GameServiceFactory
{
public static IGameServiceProvider CreateGameServiceProvider()
{
#if HMS_BUILD
return new HMSGameServiceProvider();
#else
return new GooglePlayGameServiceProvider();
#endif
}
}
If you add the "HMS_BUILD" keyword to your definition list, the game will call HMSGameServiceProvider. This way we can manage our threads in one code.
The script below can be used to manipulate definitions before assembly. After you change and save the DefineKeywords, the IDE will update the code flow according to the keywords you specified.
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 before and after the build (pre-build and post-build)
So, as mentioned earlier, we need to change the package name of our game for the Huawei AppGallery version.
However, if you are using Google Play services at the same time, all configurations will be tied to your existing package name and changing it will affect the configuration. In addition, the Unity editor in a pop-up window will warn you from time to time to correct the application package name, because now the package name and mobile service configuration are different. Closing this popup over and over again is very tedious.
To solve this problem and manage two different package names at the same time, a workaround can be used with pre-build and post-build scripts with definitions.
Take a look at this code:
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
}
}
As you can see, everything is simple. With the help of definitions, we can change the package name during pre-build for HMS_BUILD only. Then, after building, you can return the package name to the original one, and then Unity will no longer warn us about the package name mismatch.
That's all. We are now ready to develop a game in one code for Huawei and Google Play at the same time.
Application development example
Let's create an application in the same codebase for Android and Huawei, including game services, in-game purchases and advertising.
We will not implement all of the functionality for each service as this is just a demo. You can extend each feature and functionality yourself.
Service module structure
Figure 2. Scheme of service modules operation
For each service we will have our own;
- Manager : This class includes the game logic for the services and manages the general functionality. Different games may need to change this class to suit your requirements.
- Factory class (Factory) : this class includes the logic for choosing a provider. In our approach, we will use definitions, but you can change the provider selection mechanism to your liking.
- Provider : for each service, we need to create its own provider. All dependencies between your project and mobile services must remain within this class.
- Provider Interface : To unify the use of various mobile services, we need common providers, and for this purpose, provider interfaces are created. As per the requirements of your game, you need to define all the methods that you will use in your game from mobile services.
If necessary, you can also enable:
- Generic entities : You may need generic entities to abstract some services from your game. To keep the dependency only on the provider classes, you can use generic entities as per your requirement.
- Generic listeners : Similar to generic entities, if you want generic listeners, you can create those as well.
Figure 3. Example structure for a game service module
Now let's look at examples and try to understand what we can do with the method described in the article.
To abstract mobile services from game logic, we will use managers. Managers communicate with providers through fabrics. This way we can use providers like plugins. They need to implement a common interface containing the methods we want to access.
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();
}
Then we need at least one provider that implements the service interface. As mentioned earlier, all dependencies between the game and third-party mobile services must remain within this class. Let's create two providers: for Huawei and Google Play.
Here are some code examples. You can find them in the project on GitHub at the end of the article.
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();
}
....
}
Now we need to create a manager and a factory class. Then we get the provider according to our definition.
public class GameServiceManager : Singleton<GameServiceManager>
{
public IGameServiceProvider provider;
protected override void Awake()
{
base.Awake();
provider = GameServiceFactory.CreateGameServiceProvider();
}
.....
}
This is what our factory class looks like:
internal static class GameServiceFactory
{
public static IGameServiceProvider CreateGameServiceProvider()
{
#if HMS_BUILD
return new HMSGameServiceProvider();
#else
return new GooglePlayGameServiceProvider();
#endif
}
}
That's it: we now have a GameManager class that can manage the functionality of a multi-provider game service.
To initialize GameServices, use the following lines of code where appropriate:
public class MainSceneManager : MonoBehaviour
{
void Start()
{
GameServiceManager.Instance.Init();
....
}
....
private void OnClickedScoreBoardButton()
{
GameServiceManager.Instance.provider.ShowLeaderBoard();
}
private void OnClickedAchievementButton()
{
GameServiceManager.Instance.provider.ShowAchievements();
}
....
}
We will not go into the work of each service module, since they all have the same logic and structure. You can see them in action by copying the code from the project to GitHub.
Also, if you need guidance on how to set up Huawei plugin in Unity, this is described in this post .
Let's move on to the conclusions.
By changing the definitions between HMS_BUILD and GMS_BUILD in DefineConfig.cs:
- we can get two different APKs or packages for Huawei AppGallery and Google Play;
- between HMS and GMS, Login & Logout functions, leaderboards, achievements, in-game purchases are changing.
Below are short videos of both assemblies.
In case of "HMS_BUILD":
In case of "GMS_BUILD":
Demo project and prepared APKs for HMS and GMS can be found at the link on GitHub .