23 / 06 / 26

如何在 Unity 中接入 Google Play Integrity

嗨!大家好,我是小蚂蚁。

最近,我们的光明重现 游戏需要在海外的 Google Play 商店上架,作为苦逼的技术对接的我就又要干活了。其他的事项都比较顺利,在接入 Google Play Integrity 的时候遇到麻烦了,一下子就卡了两天。

Play Integrity 是 Google Play 提供的一种正版验证服务,主要的作用是防止游戏被盗版或者破解,接入了这个功能后,每次启动游戏的时候都会检查游戏是否是通过正规途径(Google Play App)购买下载的,如果是的话可以正常进入游戏,如果不是的话,可以采取一些措施,例如提示玩家当前使用的是非正常渠道的游戏包,鼓励玩家前往商店购买支持正版游戏。

为什么这个东西不好接入呢?我认为有这样的两点原因:

  1. Google Play Developer 没有提供详细完整的该组件在 Unity 中的接入文档,也没有提供任何示例 demo。

  2. 在整个互联网上几乎找不到在 Unity 中接入 Play Integrity 的任何有价值的参考内容。

我不是一个专业的安卓开发者,为了接入一个 sdk 好像也没有必要去深入研究安卓的相关知识,所以就只能靠网上仅有的一点儿信息,边猜边尝试。

好在最后总算是跑通了,记录在我的博客中,以后再有人搜索"Unity Google Play Integrity"的时候,至少能够看到多一点儿有价值的信息了。


首先,要下载适用于 Unity 的 Play Integrity 包,并导入你的 Unity 项目工程中。(点击下方链接可前往下载)

探索适用于 Unity 的 Google 软件包  |  Google for Developers

导入的 Play Integrity 包主要包含两个部分: ExternalDependencyManagerGooglePlayPlugins

GooglePlayPlugins 文件下包含了我们需要用到的 integrity 组件,以及这个功能所需要依赖的其他组件(common,core等)。

ExternalDependencyManager 这个文件夹中的作用是外部的依赖管理,啥意思呢?就是处理各种谷歌组件需要依赖的各种库,如果是安卓平台,就会添加安卓平台的依赖库,如果是 ios 平台,就会添加 ios 平台的依赖库。

在这里有个特别需要注意的地方(就是这个问题让我卡了很久)。当你将 Play Integrity 包导入到 Unity 项目工程中后会自动弹出下方这个解决 Android 依赖的对话框。

如果依赖能够正常处理完成的话,在 Plugins/Android 目录下你将能看到下面的这些文件,这些应该就是 play integrity 功能所需依赖的所有安卓文件。

如果没有安装这些依赖,你后续在测试的时候,会报错,出现找不到与 integrity 相关 class 的问题,如果你遇到缺少某个 android 类的问题,那也大概率是因为没有导入这些依赖文件的原因。

以上是第一个卡住我的地方,我在第一次向 Unity 中导入 Play Integrity 包的时候,出现了解决依赖的对话框,但是没有成功的安装依赖(提示出现了问题),我没在意,直接把它关掉了,后续就遇到了找不到相关 class 的问题。

这个解决依赖的功能不是很人性化,因为没有任何入口或者按钮可以再打开这个对话库,重新导入依赖(在Unity菜单栏中加个重新导入的菜单多好呢)。那怎么办呢?我竟然靠猜测找到了一个解决方法。

打开 PlaySettings ,找到 Build 中的 Custom Main Gradle Template 选项,把它勾选上,然后再关掉勾选。

这样你就能看到解决依赖的对话框出来了,然后看着它添加完所有的依赖。原理是什么呢?我也不知道,对安卓开发熟悉的朋友应该会知道原理,感兴趣的话可以去搜索一下 Gradle 这个东西,或许就能知其所以然了(我有点儿懒呢)。

代码部分没什么大的问题,官方提供的仅有的一点儿与 Unity 相关的内容基本上就足够了。

using System.Collections; using System.Collections.Generic; using UnityEngine.SceneManagement; using UnityEngine; using UnityEngine.UI; using Google.Play.Integrity; public class Check : MonoBehaviour {     public Text tips;     // Start is called before the first frame update     void Start()     {         StartCoroutine(RequestIntegrityTokenCoroutine());     }     IEnumerator RequestIntegrityTokenCoroutine()     {         Debug.Log("开始进行正版验证");         // Receive the nonce from the secure server.         var nonce = "jHeij27868JekjhGMCX387100djiIOOdjH";         // Create an instance of a manager.         var integrityManager = new IntegrityManager();         // Request the integrity token by providing a nonce.         var tokenRequest = new IntegrityTokenRequest(nonce);         var requestIntegrityTokenOperation =             integrityManager.RequestIntegrityToken(tokenRequest);         // Wait for PlayAsyncOperation to complete.         yield return requestIntegrityTokenOperation;         // Check the resulting error code.         if (requestIntegrityTokenOperation.Error != IntegrityErrorCode.NoError)         {             // AppendStatusLog("IntegrityAsyncOperation failed with error: " +             //         requestIntegrityTokenOperation.Error);             //tips.text = "error: " ;             Debug.Log("error:" + requestIntegrityTokenOperation.Error);                          //未连接网络             if(requestIntegrityTokenOperation.Error == IntegrityErrorCode.NetworkError)             {                 Debug.Log("网络未连接");                 tips.gameObject.SetActive(true);                //AppendStatusLog("Please connect to the network and try again");             }             /*未安装Google Play 应用               未找到Play商店账号               未安装发起调用的应用               Play服务不可用或需要更新               发起调用的应用UID与软件包管理器中的UID不符             */             if(requestIntegrityTokenOperation.Error == IntegrityErrorCode.PlayStoreNotFound             || requestIntegrityTokenOperation.Error == IntegrityErrorCode.PlayStoreAccountNotFound             || requestIntegrityTokenOperation.Error == IntegrityErrorCode.AppNotInstalled             || requestIntegrityTokenOperation.Error == IntegrityErrorCode.PlayServicesNotFound             || requestIntegrityTokenOperation.Error == IntegrityErrorCode.AppUidMismatch)             { //如果当前的游戏不是正规渠道购买,跳转到应用商店的游戏界面                 Application.OpenURL("https://play.google.com/store/apps/details?id=com.xxx.xxxxx");             }             yield break;         }         else         {             Debug.Log("完整正版验证,进入游戏");             //tips.text = "完成正版验证,进入游戏";             yield return new WaitForSeconds(2);             SceneManager.LoadScene("Menu");         }         // Get the response.         //var tokenResponse = requestIntegrityTokenOperation.GetResult();     } }

因为我们的游戏是付费单机游戏,重点只需要检查玩家是否是通过正规渠道购买的游戏即可,如果你的游戏有前后台的数据传输,想使用 Play Integrity 防止虚假的数据传输,那你还要仔细研究 Integrity 其它的功能,可惜的是官方文档里只提供了 Java 和 Kotlin 语言的实现方式,没有提供 Unity 中的实现方式,你需要花点儿时间自己研究了。

另外还有一个需要注意的地方,在后台创建 Google Play Integrity API 服务的时候,注意上方会提示你“要使用此 API,您可能需要凭据”,这个提示有点儿小,你要自己观察,你需要创建一个凭据,创建完凭据后,API 服务才能跑的通。

为什么呢?我也不知道,我就是个做游戏的,奈何如此多的与游戏无关的 API 和 SDK 要处理,着实让人头疼,这也是为什么我觉得现在做微信小游戏特别爽的原因。你可以把全部精力用于游戏开发中,微信生态为你准备好了一切。

最后,如果你能够在 Google Play Console 后台中的 Integrity API 报告中看到数据了,就证明你的 API 接入成功了。

祝你好运!

参考资料:

Play Integrity API 概览  |  Google Play  |  Android Developers

Google.Play.Integrity Namespace  |  Android Developers

Powered by Gridea