赞
踩
目录—谷歌N部曲:
1.设置Splash Image
2.如何打安卓包才能上架google play?
3.Google Develop 后台创建APP
4.如何接入谷歌SDK,登录以及排行榜?
5.如何接入谷歌支付?
QA:
1.设置Splash Image
这个图片就是unity2019.3以后的Project Setting界面,且为安卓平台。
Virtual Reality Splash Image,这个应该是VR开机的画面,不用管
Splash Screen,开机画面设置选项
a.Splash Stype,一个是白色一个是黑色
b.Animation,设置动画模式,一个是没有动画,一个是慢慢放大
c.Show Unity Logo,顾名思义,不过个人版不能设置
(这里推荐不要取消啊,unity就是很牛逼的,就像你穿衣服前面有nike一样~)
d.Draw Mode,就可以设置显示的方式,一个是同屏上下,一个是依次显示
Background,就是设置背景图的,需要可以设置,我没用过觉得黑白背景就够了
Static Splash Image,静态开机图片,可以自己设置缩放模式,也不错
2.如何打安卓包并上架google play?
第一步
打安卓包需要先下载三个DK:JDK、SDK、NDK(打IL2CPP用的)
unity2019有个坑,就是指定ndk后,说版本对不上
这时候不要慌,只需要打开文件夹下的 source.properties
修改这里面的Revision的数值即可。
第二步,勾选IL2CPP
谷歌有规定,必须要支持目标架构64位的,那么唯一选择就只能是IL2CPP了
这样做也有好处,可以防破解
(不过现在下载的中国增强版都有混淆功能,不知道啥时候能给个热更)
如何配置呢?
注意:目标架构ARMv7和ARM64都要勾选。
第三步,签名
需要创建一个keystore,并且一直用这个keystore签名打包
(什么是keystore?怎么创建?可以百度一下)
上传到谷歌商店会对这个进行二次签名与验证,所以必须要保证每次上传的包都是这个签名才行
(注:这里需要勾选 Custom Gradle Template,接sdk需要配置gradle文件)
第四步,设置为aab
aab是谷歌推荐上传的文件,可以自动优化包体大小
(非要用apk也是可以的)
这样导出的包 就能上传到google play里面了。
3.Google Develop 后台创建APP
首先需要创建一个谷歌开发者账号,需要刷信用卡 25美刀,没有的可以TB代刷,不过有手续费~
后台界面如图:
所有应用,是用来看你的app的
游戏服务,是用来接比如成就排行榜啥的
首先需要创建应用:
输入默认语言和名称,没有ios那么坑爹,直接输入创建就可以
创建后界面如图:
若要发布,需要先填写左边四个灰色的对号那几个选项
都填写完了应该就变成绿色了
填表 大家应该都会的吧 就不给演示了
(坑:隐私权可以百度一下,有个五分钟搞定谷歌隐私权的文章)
(其他的都还好吧,有问题可以留言)
主要说一下应用版本:
正式版,就是对所有谷歌用户都能下载
开放式渠道,就是能在谷歌商店通过连接下载,可以设置最大测试人数,属于开放式测试
(推荐用这个测试啊)
封闭式渠道,就是只能对特定的测试id下载
内部测试渠道,估计也是特定的设备才能下载测试吧,没用过忘了
推荐使用开放式渠道啊,方便快捷,除非你项目这么叼,一般发布了也没人去下载你的
填写完版本后,然后填好测试用户的邮箱啥的,就点保存
然后就出现了查看按钮,点击就跳转个界面,就有个发布按钮,点击发布按钮就发布了
就是上面的两个界面,我都发布了不能截图了,不过很简单的啊~~
审核:
第一次发布需要3-5个工作日左右吧,不算周末
(不过现在疫情严重需要推迟一周,祝疫情早日过去)
之后每次小版本更新发布就快了~
谷歌不像苹果那么多事情,一般都会审核通过的~
4.如何接入谷歌SDK,登录以及排行榜?
首先需要下载谷歌SDK,unity这么牛逼,当然有支持unitypackage的包了,省了不少事情
下载地址:谷歌插件的GIT地址
GIT里面已经有很详细的教程了,在这里就记录几个坑就行了:
前提:必须要从谷歌商店下载的包才能正常登录,以及获取排行榜啥的,所以就需要你上传一个内测版本再测试
1.最新谷歌服务都采用了androidX了,和原来的android Suppoer库不能共存,假如你接了别的sdk,需要统一使用androidX的包才行
提供几个代码仅供参考:
gradle内:
([rootProject] + (rootProject.subprojects as List)).each {
ext {
it.setProperty("android.useAndroidX", true)
it.setProperty("android.enableJetifier", true)
}
}
或者
项目的Asset/Plugins/Editor下:
using System.IO; using UnityEditor.Android; using UnityEngine; public class SupportAndroidXGradlePropertiesBuildProcessor : IPostGenerateGradleAndroidProject { public int callbackOrder { // 同种插件的优先级 get { return 999; } } public void OnPostGenerateGradleAndroidProject(string path) { Debug.Log("Bulid path : " + path); string gradlePropertiesFile = path + "/../gradle.properties"; if (File.Exists(gradlePropertiesFile)) { File.Delete(gradlePropertiesFile); } StreamWriter writer = File.CreateText(gradlePropertiesFile); writer.WriteLine("org.gradle.jvmargs=-Xmx4096M"); writer.WriteLine("android.useAndroidX=true"); writer.WriteLine("android.enableJetifier=true"); writer.Flush(); writer.Close(); } }
2.赠送一个排行榜的代码,之前接的时候写的
using GooglePlayGames; using GooglePlayGames.BasicApi; using System.Collections; using System.Collections.Generic; using UnityEngine; public interface ILeaderboardControl { bool authenticated { get; } void Authenticate(); void HandleAuthenticated(bool success); void ShowLeaderboardUI(); void ReportScore(int score, int levelId); void HandleScoreReported(bool success); } public class LeaderboardControl : Singleton<LeaderboardControl>, ILeaderboardControl { public LeaderboardControl() { } public bool successed { get; private set; } public bool authenticated { get { return Social.localUser.authenticated; } } public void Authenticate() { #if UNITY_EDITOR return; #endif #if UNITY_ANDROID PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder().Build(); PlayGamesPlatform.InitializeInstance(config); #if TEST PlayGamesPlatform.DebugLogEnabled = true; #endif PlayGamesPlatform.Activate(); #endif Social.localUser.Authenticate(this.HandleAuthenticated); } public void HandleAuthenticated(bool success) { if (success) { string userInfo = "Username: " + Social.localUser.userName + ", User ID: " + Social.localUser.id; Logger.Log("*** HandleAuthenticated: success , userInfo : " + userInfo); #if UNITY_ANDROID ((GooglePlayGames.PlayGamesPlatform)Social.Active).SetGravityForPopups(Gravity.TOP); #endif } else { Logger.Log("*** HandleAuthenticated: failed !!!"); } this.successed = success; } public void ShowLeaderboardUI() { #if UNITY_EDITOR return; #endif if (!successed) { Logger.Log("*** Try to Authenticate..."); Social.localUser.Authenticate(this.HandleAuthenticated); } if (this.authenticated && this.successed) { #if UNITY_ANDROID ((PlayGamesPlatform)Social.Active).ShowLeaderboardUI(); #else Social.ShowLeaderboardUI(); #endif } } public void ReportScore(int score, int levelId) { #if UNITY_EDITOR return; #endif string boardId = string.Empty; #if UNITY_ANDROID string[] googleBoardIds = { GPGSIds.leaderboard_challenge_room_1, GPGSIds.leaderboard_challenge_room_2, GPGSIds.leaderboard_challenge_room_3 }; boardId = googleBoardIds[levelId]; #elif UNITY_IOS string appleBoardIds = "challenge"; boardId = appleBoardIds + levelId; #endif if (this.authenticated && this.successed) { Social.ReportScore(score, boardId, this.HandleScoreReported); } } public void HandleScoreReported(bool success) { Logger.Log("*** HandleScoreReported: " + success); } }
5.如何接入谷歌支付?
谷歌和苹果支付只需要用Unity的IAP就可以
在PackangeManager里面下载,导入 In App Purchasing 插件
另外还需要点开Service窗口,需要开启Analytics和In-APP Purchasing
坑总结:
1.Service窗口打不开,可能是unity的版本不行,我之前2019.3.0就不行,2019.3.2就可以
2.Analytics需要先点一下运行开 传一下假数据,如果不行请多试几次
成功后就不是这个界面了,那就代表成功了
3.添加In-App Purchasing信息
注:必须要先开启Analytics,并且成功传输数据了,就是先满足条件2才可以,才能填写Google Public Key
然后点击Import即可。
给个代码参考一下:
using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.Purchasing; using UnityEngine.UI; public class IAPTools : MonoSingleton<IAPTools>, IStoreListener { private static IStoreController m_StoreController; // 存储商品信息; private static IExtensionProvider m_StoreExtensionProvider; // IAP扩展工具; private bool m_PurchaseInProgress = false; // 是否处于付费中; public enum EProductType { xx, ITEM_COUNT = 1 } public void Init() { if (m_StoreController == null && m_StoreExtensionProvider == null) InitUnityPurchase(); } private bool IsInitialized() { return m_StoreController != null && m_StoreExtensionProvider != null; } // 初始化IAP; public void InitUnityPurchase() { if (IsInitialized()) return; // 标准采购模块; StandardPurchasingModule module = StandardPurchasingModule.Instance(); // 配置模式; ConfigurationBuilder builder = ConfigurationBuilder.Instance(module); // 注意ProductType的类型,Consumable是可以无限购买(比如水晶),NonConsumable是只能购买一次(比如关卡),Subscription是每月订阅(比如VIP); // 这里初始化没有添加平台信息,因为平台信息有的时候还存在bug,如果必须添加,也可以添加,没有问题,确保平台信息添加正确就行了,如下: // builder.AddProduct(C_ITEM_0, ProductType.Consumable, new IDs // { // {"yourProductName", AppleAppStore.Name}, // {"yourProductName", GooglePlay.Name}, // }); builder.AddProduct(你的id, ProductType.NonConsumable); builder.AddProduct(你的id, ProductType.Consumable); //初始化; UnityPurchasing.Initialize(this, builder); } #region Public Func //根据类型购买商品: public void BuyProductByID(IAPTools.EProductType productType) { if (IsInitialized()) { if (m_PurchaseInProgress == true) return; string productId = productType.ToString(); Product product = m_StoreController.products.WithID(productId); if (product != null && product.availableToPurchase) { Logger.Log(string.Format("Purchasing product asychronously: '{0}'", product.definition.id)); m_StoreController.InitiatePurchase(product); m_PurchaseInProgress = true; } else { Logger.Log("BuyProductID: FAIL. Not purchasing product, either is not found or is not available for purchase"); } } else { Logger.Log("BuyProductID FAIL. Not initialized."); } } // 确认购买产品成功; public void DoConfirmPendingPurchaseByID(string productId) { Product product = m_StoreController.products.WithID(productId); if (product != null && product.availableToPurchase) { if (m_PurchaseInProgress) { m_StoreController.ConfirmPendingPurchase(product); m_PurchaseInProgress = false; } } } // 恢复购买; //此物品已经购买成功了,但是苹果没有接受到app的消息 这个时候需要执行 //他会把你没有解决的订单和一次性购买项目再次拉去一遍,他会执行 ProcessPurchase 这个回调函数 public void RestorePurchases() { if (!IsInitialized()) { Logger.Log("RestorePurchases FAIL. Not initialized."); return; } if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.OSXPlayer) { Logger.Log("RestorePurchases started ..."); var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>(); apple.RestoreTransactions((result) => { // 返回一个bool值,如果成功,则会多次调用支付回调,然后根据支付回调中的参数得到商品id,最后做处理(ProcessPurchase); Logger.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore."); }); } else { Logger.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform); } } #endregion #region IStoreListener Callback // IAP初始化成功回掉函数; public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { Logger.Log("OnInitialized Succ !"); m_StoreController = controller; m_StoreExtensionProvider = extensions; // 这里可以获取您在AppStore和Google Play 上配置的商品; //ProductCollection products = m_StoreController.products; //Product[] all = products.all; //for (int i = 0; i < all.Length; i++) //{ // Logger.Log(all[i].metadata.localizedTitle + "|" + all[i].metadata.localizedPriceString + "|" + all[i].metadata.localizedDescription + "|" + all[i].metadata.isoCurrencyCode); //} #if UNITY_IOS m_AppleExtensions.RegisterPurchaseDeferredListener(OnDeferred); #endif } // IAP初始化失败回掉函数(没有网络的情况下并不会调起,而是一直等到有网络连接再尝试初始化); public void OnInitializeFailed(InitializationFailureReason error) { switch (error) { case InitializationFailureReason.AppNotKnown: Logger.Log("Is your App correctly uploaded on the relevant publisher console?", Logger.LogLevel.Error); break; case InitializationFailureReason.PurchasingUnavailable: Logger.Log("Billing disabled! Ask the user if billing is disabled in device settings."); break; case InitializationFailureReason.NoProductsAvailable: Logger.Log("No products available for purchase! Developer configuration error; check product metadata!"); break; } } //外部监听回调事件 public Action<IAPTools.EProductType> onPurchaseSuccess; public Action onPurchaseFail; // 支付成功处理函数; public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e) { Logger.Log("Purchase OK: " + e.purchasedProduct.definition.id); // 消息结构 : Receipt: {"Store":"fake","TransactionID":"9c5c16a5-1ae4-468f-806d-bc709440448a","Payload":"{ \"this\" : \"is a fake receipt\" }"}; Logger.Log("Receipt: " + e.purchasedProduct.receipt); IAPTools.EProductType productType; if (Enum.TryParse<EProductType>(e.purchasedProduct.definition.id, out productType)) { this.onPurchaseSuccess?.Invoke(productType); } // 我们自己后台完毕的话,通过代码设置成功(如果是不需要后台设置直接设置完毕,不要设置Pending); return PurchaseProcessingResult.Complete; } // 支付失败回掉函数; public void OnPurchaseFailed(Product item, PurchaseFailureReason r) { Logger.Log("Purchase OK: " + r.ToString()); this.m_PurchaseInProgress = false; this.onPurchaseFail?.Invoke(); } // 恢复购买功能执行回掉函数; private void OnTransactionsRestored(bool success) { Logger.Log("Transactions restored."); } // 购买延迟提示(这个看自己项目情况是否处理); private void OnDeferred(Product item) { Logger.Log("Purchase deferred: " + item.definition.id); } #endregion }
(ps:某些SDK是其他线程返回的,如果需要在接入回调做些什么,是不能直接在其他线程里操作unity的一些东西的,比如操作gameobject,那就需要设置一个标识位,在判断)
商品信息是在谷歌后台配置的,如图:
商品按类型分为三种:受管制、订阅、奖励
我用到了受管制的物品,大概有两种类型:Consumable 和 NonConsumable
如果是 Consumable 就是可重复购买,消耗型的道具,比如+生命值
如果是 NonConsumable 就是不能重复买,买完就一直存在状态,比如无限生命
谷歌会自己做一步操作:
重装APP的时候,会自动根据账号去检测 NonConsumable类型的 是否购买过了,如果买过了,就会调用 Restore 保证道具能拉回来,重新走一遍你的获奖逻辑,需要自行判断一下~
如何测试呢?
如果是测试账号,就会变成测试订单,不会扣钱的,直接买就可以了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。