谷歌商店全攻略_Google Play 应用商店 上架流程

目录—谷歌N部曲:
1.设置Splash Image
2.如何打安卓包才能上架google play?
3.Google Develop 后台创建APP
4.如何接入谷歌SDK,登录以及排行榜?
5.如何接入谷歌支付?

QA:
1.设置Splash Image

谷歌商店全攻略_Google Play 应用商店 上架流程_第1张图片

这个图片就是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用的)

谷歌商店全攻略_Google Play 应用商店 上架流程_第2张图片

unity2019有个坑,就是指定ndk后,说版本对不上

谷歌商店全攻略_Google Play 应用商店 上架流程_第3张图片

这时候不要慌,只需要打开文件夹下的 source.properties

谷歌商店全攻略_Google Play 应用商店 上架流程_第4张图片

修改这里面的Revision的数值即可。

第二步,勾选IL2CPP
谷歌有规定,必须要支持目标架构64位的,那么唯一选择就只能是IL2CPP了
这样做也有好处,可以防破解
(不过现在下载的中国增强版都有混淆功能,不知道啥时候能给个热更)

谷歌商店全攻略_Google Play 应用商店 上架流程_第5张图片

如何配置呢?

谷歌商店全攻略_Google Play 应用商店 上架流程_第6张图片

注意:目标架构ARMv7和ARM64都要勾选。

第三步,签名

谷歌商店全攻略_Google Play 应用商店 上架流程_第7张图片

需要创建一个keystore,并且一直用这个keystore签名打包
(什么是keystore?怎么创建?可以百度一下)

上传到谷歌商店会对这个进行二次签名与验证,所以必须要保证每次上传的包都是这个签名才行

(注:这里需要勾选 Custom Gradle Template,接sdk需要配置gradle文件)

第四步,设置为aab

aab是谷歌推荐上传的文件,可以自动优化包体大小
(非要用apk也是可以的)

谷歌商店全攻略_Google Play 应用商店 上架流程_第8张图片

这样导出的包 就能上传到google play里面了。


3.Google Develop 后台创建APP

首先需要创建一个谷歌开发者账号,需要刷信用卡 25美刀,没有的可以TB代刷,不过有手续费~

谷歌商店全攻略_Google Play 应用商店 上架流程_第9张图片

后台界面如图:
所有应用,是用来看你的app的
游戏服务,是用来接比如成就排行榜啥的

首先需要创建应用:

谷歌商店全攻略_Google Play 应用商店 上架流程_第10张图片

输入默认语言和名称,没有ios那么坑爹,直接输入创建就可以
创建后界面如图:

谷歌商店全攻略_Google Play 应用商店 上架流程_第11张图片

若要发布,需要先填写左边四个灰色的对号那几个选项
都填写完了应该就变成绿色了
填表 大家应该都会的吧 就不给演示了

(坑:隐私权可以百度一下,有个五分钟搞定谷歌隐私权的文章)
(其他的都还好吧,有问题可以留言)

主要说一下应用版本:

谷歌商店全攻略_Google Play 应用商店 上架流程_第12张图片

正式版,就是对所有谷歌用户都能下载
开放式渠道,就是能在谷歌商店通过连接下载,可以设置最大测试人数,属于开放式测试
(推荐用这个测试啊)
封闭式渠道,就是只能对特定的测试id下载
内部测试渠道,估计也是特定的设备才能下载测试吧,没用过忘了

推荐使用开放式渠道啊,方便快捷,除非你项目这么叼,一般发布了也没人去下载你的

填写完版本后,然后填好测试用户的邮箱啥的,就点保存
然后就出现了查看按钮,点击就跳转个界面,就有个发布按钮,点击发布按钮就发布了

谷歌商店全攻略_Google Play 应用商店 上架流程_第13张图片

就是上面的两个界面,我都发布了不能截图了,不过很简单的啊~~

审核:
第一次发布需要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就可以

谷歌商店全攻略_Google Play 应用商店 上架流程_第14张图片

在PackangeManager里面下载,导入 In App Purchasing 插件
另外还需要点开Service窗口,需要开启Analytics和In-APP Purchasing

谷歌商店全攻略_Google Play 应用商店 上架流程_第15张图片

坑总结:
1.Service窗口打不开,可能是unity的版本不行,我之前2019.3.0就不行,2019.3.2就可以
2.Analytics需要先点一下运行开 传一下假数据,如果不行请多试几次

谷歌商店全攻略_Google Play 应用商店 上架流程_第16张图片

成功后就不是这个界面了,那就代表成功了

3.添加In-App Purchasing信息

谷歌商店全攻略_Google Play 应用商店 上架流程_第17张图片

注:必须要先开启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,那就需要设置一个标识位,在判断)

商品信息是在谷歌后台配置的,如图:

谷歌商店全攻略_Google Play 应用商店 上架流程_第18张图片

商品按类型分为三种:受管制、订阅、奖励

我用到了受管制的物品,大概有两种类型:Consumable 和 NonConsumable

如果是 Consumable 就是可重复购买,消耗型的道具,比如+生命值
如果是 NonConsumable 就是不能重复买,买完就一直存在状态,比如无限生命

谷歌会自己做一步操作:
重装APP的时候,会自动根据账号去检测 NonConsumable类型的 是否购买过了,如果买过了,就会调用 Restore 保证道具能拉回来,重新走一遍你的获奖逻辑,需要自行判断一下~

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

你可能感兴趣的:(SDK相关,游戏)