谷歌商店GOOGLE PLAY全攻略

目录---谷歌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 保证道具能拉回来,重新走一遍你的获奖逻辑,需要自行判断一下~

如何测试呢?
如果是测试账号,就会变成测试订单,不会扣钱的,直接买就可以了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容