Unity项目添加广告,内购测试并上线APP Store

Ⅰ.添加广告并真机测试

1.用示例广告ID演示Admob Unity 插件

参考(admob官方文字教程)

  • 导入插件包

Unity项目添加广告,内购测试并上线APP Store_第1张图片

  • 设置Admob应用ID

Unity项目添加广告,内购测试并上线APP Store_第2张图片

Unity项目添加广告,内购测试并上线APP Store_第3张图片

Unity项目添加广告,内购测试并上线APP Store_第4张图片

  • 示例广告id(测试时务必用示例广告id)

Unity项目添加广告,内购测试并上线APP Store_第5张图片

注意导出的app包主文件已变,不要打开错了。

(即在xcode内打开的主文件由.xcodeproj变成.xcworkspace)

  • 添加一个示例激励广告

...
using GoogleMobileAds.Api;
...
public class GoogleMobileAdsDemoScript : MonoBehaviour
{
    private RewardBasedVideoAd rewardBasedVideo;
    ...

    public void Start()
    {
        #if UNITY_ANDROID//示例广告的appID
            string appId = "ca-app-pub-3940256099942544~3347511713";
        #elif UNITY_IPHONE
            string appId = "ca-app-pub-3940256099942544~1458002511";
        #else
            string appId = "unexpected_platform";
        #endif

        // Initialize the Google Mobile Ads SDK.初始化
        MobileAds.Initialize(appId);

        // Get singleton reward based video ad reference.激励广告是单例模式,把脚本关联上单例
        this.rewardBasedVideo = RewardBasedVideoAd.Instance;

        this.RequestRewardBasedVideo();//尽早加载广告资源
    }

    private void RequestRewardBasedVideo()
    {
        #if UNITY_ANDROID//示例广告ID
            string adUnitId = "ca-app-pub-3940256099942544/5224354917";
        #elif UNITY_IPHONE
            string adUnitId = "ca-app-pub-3940256099942544/1712485313";
        #else
            string adUnitId = "unexpected_platform";
        #endif

        // Create an empty ad request.创建一个新广告请求
        AdRequest request = new AdRequest.Builder().Build();
        // Load the rewarded video ad with the request.加载
        this.rewardBasedVideo.LoadAd(request, adUnitId);
    }
}

hello world示例里创建的广告需求

  • 广告事件

...
using GoogleMobileAds.Api;
...
public class GoogleMobileAdsDemoScript : MonoBehaviour
{
    private RewardBasedVideoAd rewardBasedVideo;
    ...

    public void Start()
    {
        // Get singleton reward based video ad reference.
        this.rewardBasedVideo = RewardBasedVideoAd.Instance;

        // Called when an ad request has successfully loaded.
        rewardBasedVideo.OnAdLoaded += HandleRewardBasedVideoLoaded;
        // Called when an ad request failed to load.
        rewardBasedVideo.OnAdFailedToLoad += HandleRewardBasedVideoFailedToLoad;
        // Called when an ad is shown.
        rewardBasedVideo.OnAdOpening += HandleRewardBasedVideoOpened;
        // Called when the ad starts to play.
        rewardBasedVideo.OnAdStarted += HandleRewardBasedVideoStarted;
        // Called when the user should be rewarded for watching a video.
        rewardBasedVideo.OnAdRewarded += HandleRewardBasedVideoRewarded;
        // Called when the ad is closed.
        rewardBasedVideo.OnAdClosed += HandleRewardBasedVideoClosed;
        // Called when the ad click caused the user to leave the application.
        rewardBasedVideo.OnAdLeavingApplication += HandleRewardBasedVideoLeftApplication;

        this.RequestRewardBasedVideo();
    }

    private void RequestRewardBasedVideo()
    {
        #if UNITY_ANDROID
            string adUnitId = "ca-app-pub-3940256099942544/5224354917";
        #elif UNITY_IPHONE
            string adUnitId = "ca-app-pub-3940256099942544/1712485313";
        #else
            string adUnitId = "unexpected_platform";
        #endif

        // Create an empty ad request.
        AdRequest request = new AdRequest.Builder().Build();
        // Load the rewarded video ad with the request.
        this.rewardBasedVideo.LoadAd(request, adUnitId);
    }

    public void HandleRewardBasedVideoLoaded(object sender, EventArgs args)
    {
        MonoBehaviour.print("HandleRewardBasedVideoLoaded event received");
    }

    public void HandleRewardBasedVideoFailedToLoad(object sender, AdFailedToLoadEventArgs args)
    {
        MonoBehaviour.print(
            "HandleRewardBasedVideoFailedToLoad event received with message: "
                             + args.Message);
    }

    public void HandleRewardBasedVideoOpened(object sender, EventArgs args)
    {
        MonoBehaviour.print("HandleRewardBasedVideoOpened event received");
    }

    public void HandleRewardBasedVideoStarted(object sender, EventArgs args)
    {
        MonoBehaviour.print("HandleRewardBasedVideoStarted event received");
    }

    public void HandleRewardBasedVideoClosed(object sender, EventArgs args)
    {
        MonoBehaviour.print("HandleRewardBasedVideoClosed event received");
    }

    public void HandleRewardBasedVideoRewarded(object sender, Reward args)
    {
        string type = args.Type;
        double amount = args.Amount;
        MonoBehaviour.print(
            "HandleRewardBasedVideoRewarded event received for "
                        + amount.ToString() + " " + type);
    }

    public void HandleRewardBasedVideoLeftApplication(object sender, EventArgs args)
    {
        MonoBehaviour.print("HandleRewardBasedVideoLeftApplication event received");
    }
}

Unity项目添加广告,内购测试并上线APP Store_第6张图片

  • 展示广告&重新加载一段新的广告

Unity项目添加广告,内购测试并上线APP Store_第7张图片

2.使用自己创建的真实广告ID并真机测试

参考来源-admob测试广告

上面说的是用示例广告测试,下面我们说真实广告真机测试。

Unity项目添加广告,内购测试并上线APP Store_第8张图片

3.问题爬坑:

1.编译时

bug:framework not found iosurface for architecture arm64

bug:framework not found fileprovider for architecture arm64

解决方法:参考

Unity项目添加广告,内购测试并上线APP Store_第9张图片

点击上面的地址,下个9.0以上的版本,下完解压后不用安装,直接打开包内容。按照路径拷贝(拷贝到iPhoneOS.sdk而不是iPhonexx.x.sdk下面)。拷贝完在xcode打开的项目里清除,然后编译就OK了。

2.注意不要搞混id

Start()里是该应用注册的admob id,Request()里是该应用的广告单元id。

3.注册代理来监听事件的位置

激励广告常用两个事件:OnAdRewarded (发放奖励),OnAdClosed(关掉了视频就再请求加载新广告,暂停音效和游戏进程在这里恢复)。

之前看油管教程说注册和取消注册放在OnEnabled()和OnDisabled()里,但我试了没有反应,最后按照官方指导的把注册放在Start()里,取消还是放在Ondisabled()里(能否注销还未测试)。

4.怎样发放奖励

在创建广告单元时要填写reward的类型(type)和数量(amount),所以发放奖励不要再自己另写了。

Unity项目添加广告,内购测试并上线APP Store_第10张图片

5.编译时崩溃并xcode日志提示The Google Mobile Ads SDK was initialized incorrectly.

参考上面在unity内对admob adID的设置。也可以参考

6.build出的xcode工程里没有scworkspace文件

是cocoapods的版本问题,大概流程是 ①卸载已有的cocoapods - ②安装新版cocoapods - ③拉取cocoapods依赖的库 - ④更新。参考来源

①打开终端,先卸载

sudo gem uninstall cocoapods -n/usr/local/bin

命令窗口中,遇到对话,输入y,并回车

②卸载完毕后,继续执行命令安装新版

sudo gem install cocoapods -n/usr/local/bin

如果成功安装就进行下一步,如果提示错误

ERROR: Could not find a valid gem ‘cocoapods’ (>= 0), here is why:
Unable to download data from https://rubygems.org/ — SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (https://api.rubygems.org/specs.4.8.gz)”

则输入下面命令

gem source -r https://rubygems.org/
gem source -a http://rubygems.org/
sudo gem install cocoapods

可成功安装。参考来源

如果提示ruby版本过低,则输入以下命令查看ruby源

gem sources -l //查看ruby源

默认情况下会显示

https://rubygems.org/

如果科学的使用了上网,则掠过下一步,如果没有:

//***********************//

ruby源在墙内是访问不到的,需要置换为国内,原来一直用的是淘宝的,由于淘宝源不再更新,所以不在使用,现在用下面这个

//删除原始的源
gem sources --remove  https://rubygems.org/
//添加新源(国内Ruby镜像)
gem source -a https://gems.ruby-china.com

只有在终端中出现下面文字才表明你上面的命令是成功的:

https://gems.ruby-china.com added to sources

为了验证你的Ruby镜像是并且仅是gems.ruby-china,可以用以下命令查看:

gem sources -l        //检测是否成功

//*************************//

然后升级gem,gem是管理ruby的标准包

sudo gem update --system   //升级gem

目前查看gem版本为

gem -v           //查看版本

正常的话就重新安装cocoapods

sudo gem install cocoapods -n/usr/local/bin

③拉取cocoapods依赖的库

打开访达Command + Shift + G,输入~/.cocoapods,到达该目录,删除该目录下所有内容,然后在该.cocoapods目录下创建repos文件夹。

在终端里一个一个执行下面三个命令(一个执行完了再输入下一个)

cd ~/.cocoapods/repos
git clone --depth 1 https://github.com/CocoaPods/Specs.git master
pod repo update

第二个命令会执行的久一些,如果出现拉取失败的各种提示,比如我的提示

error: RPC failed; curl 56 LibreSSL SSL_read: SSL_ERROR_SYSCALL, errno 54

则可按参考来源尝试,我是没登GitHub,登陆了再次输入克隆命令后花了些时间就成功了。(所以可以尝试多克隆几次)

④最后一句命令完成后cocoapods就更新完毕,unity内重新build后就能看到xcworkspace文件了。

Ⅱ.添加内购并真机测试

1.基础iOS文件设置

登陆苹果开发者账号,选择Certificates,Identifiers & Profiles

Unity项目添加广告,内购测试并上线APP Store_第11张图片

其实这些步骤如果完成上面的真机测试的话都已经使用过了。

Unity项目添加广告,内购测试并上线APP Store_第12张图片

Unity项目添加广告,内购测试并上线APP Store_第13张图片

即有证书(Certificates)才能证明自己的身份,App IDs提供app 的bundle ID,配置文件(pp)容许开发者把应用装到测试机器上。参考1 参考2

2.协议、税务和银行业务 设置

点击苹果开发者账号的appstore connect(如果不是苹果机的话直接浏览器里搜然后用苹果开发者账号登陆)。

点击 协议、税务和银行业务 

进入后点击

Unity项目添加广告,内购测试并上线APP Store_第14张图片

Unity项目添加广告,内购测试并上线APP Store_第15张图片

浙商银行网站查询CNAPS

Unity项目添加广告,内购测试并上线APP Store_第16张图片

Unity项目添加广告,内购测试并上线APP Store_第17张图片

Unity项目添加广告,内购测试并上线APP Store_第18张图片

Unity项目添加广告,内购测试并上线APP Store_第19张图片

Unity项目添加广告,内购测试并上线APP Store_第20张图片

Unity项目添加广告,内购测试并上线APP Store_第21张图片

Unity项目添加广告,内购测试并上线APP Store_第22张图片

Unity项目添加广告,内购测试并上线APP Store_第23张图片

3. 我的APP 设置

返回appstore connect首页,点击 我的APP,点击+号创建app。

如果提示“您没有适用于 iOS App的合格套装ID”,证明现在你没有还未管理的bundle ID了。

Unity项目添加广告,内购测试并上线APP Store_第24张图片

回到Appstore connect-我的App继续创建App

填写好基础信息

如果提示bundle ID不匹配无法创建的话,说明当前已有的bundle ID都已申请绑定好app了,再注册个新的bundle id就好了。

Unity项目添加广告,内购测试并上线APP Store_第25张图片

下图在要上线提交前再填写,只是沙箱真机测试的话不用管。

之后我们点击 功能 添加内购项目

Unity项目添加广告,内购测试并上线APP Store_第26张图片

Unity项目添加广告,内购测试并上线APP Store_第27张图片

苹果对上面内购类型的解释:

Unity项目添加广告,内购测试并上线APP Store_第28张图片

Unity项目添加广告,内购测试并上线APP Store_第29张图片

Unity项目添加广告,内购测试并上线APP Store_第30张图片

填写完毕后如下

Unity项目添加广告,内购测试并上线APP Store_第31张图片

上面 元数据丢失是因为我没有填写屏幕快照和审核信息,完整填写后就会显示准备提交。上面显示的“您的首个App内购买项目必须以新的App版本提交”不用管,意思是要在appstore内让玩家使用的话必须提交。沙箱真机测试就不用了。

4.设置沙盒测试

Unity项目添加广告,内购测试并上线APP Store_第32张图片

5.Unity内内购设置

先按照参考里在Unity的Services打开In-App Purchasing并导入官方包。

1.如果使用IAPButton(几乎没有代码,特点是丑)

参考

之后新建camera模式的canvas并添加iap button

之后点击iap catalog填写信息

Unity项目添加广告,内购测试并上线APP Store_第33张图片

ID就填写之前在appstore connect里设置的id

Title和Description填写

Unity项目添加广告,内购测试并上线APP Store_第34张图片

然后点开Apple Configuration填写之前设置的该内购的标价。

Unity项目添加广告,内购测试并上线APP Store_第35张图片

Apple SKU和 Apple Team ID

Unity项目添加广告,内购测试并上线APP Store_第36张图片

添加完后关闭iap catalog选择刚添加的product ID

然后添加购买完成的代码

Unity项目添加广告,内购测试并上线APP Store_第37张图片

2.单用脚本实现内购

首先Unity内Window > Unity IAP > IAP Receipt Validation Obfuscator.打开验证混淆窗口。如果使用Google Play则复制进Google的公钥,如果只用Apple的话不用添加任何东西直接点红框按钮,Unity会在文件里生成Tangle文件。这个工具主要是加密收据并验证收据。

Unity项目添加广告,内购测试并上线APP Store_第38张图片

新建IAP脚本如下

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
using UnityEngine.Purchasing.Security;
using System;

public class IAP : MonoBehaviour, IStoreListener
{
    IStoreController m_StoreController;          // The Unity Purchasing system.
    IExtensionProvider m_StoreExtensionProvider; // The store-specific Purchasing subsystems.
    IAppleExtensions m_AppleExtensions;
    ConfigurationBuilder builder;

    public string coins100 = "com.mycompany.app.100coins";//消耗型
    public string newMap = "com.mycompany.app.newMap";//非消耗型
    public string subscription = "com.mycompany.app.subscription";//非续期型订阅
    public string newableSub = "com.mycompany.app.newablesubscription";//可续期型订阅
    //建议使用可续期型订阅通过SubscriptionManager读取是否到期等属性

    void Start()
    {
        if (m_StoreController == null)
        {
            InitializePurchasing();
        }
    }

    public void InitializePurchasing()//添加商品并初始化内购系统
    {
        if (IsInitialized())
            return;
 
        builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

        builder.AddProduct(coins100, ProductType.Consumable);//在这里添加内购产品
        builder.AddProduct(newMap, ProductType.NonConsumable);
        builder.AddProduct(subscription, ProductType.Subscription);
        builder.AddProduct(newableSub, ProductType.Subscription);

        UnityPurchasing.Initialize(this, builder);
    }

    private bool IsInitialized()//判定是否初始化,初始化在OnInitialized()里
    {
        return m_StoreController != null && m_StoreExtensionProvider != null;
    }


    void BuyProductID(string productId)//充值内购按钮调用
    {
        if (IsInitialized())
        {
            // ... look up the Product reference with the general product identifier and the Purchasing 
            // system's products collection.
            Product product = m_StoreController.products.WithID(productId);

            // If the look up found a product for this device's store and that product is ready to be sold ... 
            if (product != null && product.availableToPurchase)
            {
                Console.WriteLine(string.Format("异步购买: '{0}'", product.definition.id));
                m_StoreController.InitiatePurchase(product);//开始购买
            }
            else
            {
                Console.WriteLine("购买失败:无此产品或此产品无法购买");
            }
        }
        else
        {
            Console.WriteLine("购买失败:IAP未完成初始化");
        }
    }

    //恢复购买按钮调用,用于Non-Consumable(比如关卡等)和renewable Subscription(可续期订阅),消耗类和非续期订阅不可恢复
    public void RestorePurchases()
    {
        if (!IsInitialized())
        {
            Debug.Log("购买失败:IAP还未完成初始化");
            return;
        }

        //和以前不同的是:不要在这里判断平台,应该在UI里判断,如果不是ios则不要显示恢复按钮
        if (m_StoreExtensionProvider != null)
        {
            IAppleExtensions apple = m_StoreExtensionProvider.GetExtension();
            // Begin the asynchronous process of restoring purchases. Expect a confirmation response in 
            // the Action below, and ProcessPurchase if there are previously purchased products to restore.
            apple.RestoreTransactions((result) => {
                if (result)
                {

                    Console.WriteLine("正在恢复: " + result + ". If no further messages, no purchases available to restore.");
                    //注意这里恢复的是购买的非消耗品。会自动调用一次非消耗品的脚本
                    //在此为:
                    //else if (string.equals(e.purchasedproduct.definition.id, newmap, stringcomparison.ordinal))
                    //{
                    //    debug.log(string.format("processpurchase: pass. product: '{0}'", e.purchasedproduct.definition.id));
                    //    // todo: the non-consumable item has been successfully purchased, grant this item to the player.
                    //}里的todo
                }
                else
                {
                    Console.WriteLine("恢复购买失败");
                }
            });
        }
    }

    public void CheckSub()//使用SubscriptionManager检查订阅是否到期
    {
        m_AppleExtensions = m_StoreExtensionProvider.GetExtension();
        Dictionary introductory_info_dict = m_AppleExtensions.GetIntroductoryPriceDictionary();

        foreach (var item in m_StoreController.products.all)
        {
            if (item.receipt != null)
            {
                if (item.definition.type == ProductType.Subscription)
                {
                    string intro_json = (introductory_info_dict == null || 
                        !introductory_info_dict.ContainsKey(item.definition.storeSpecificId)) 
                        ? null : introductory_info_dict[item.definition.storeSpecificId];

                    SubscriptionManager p = new SubscriptionManager(item, intro_json);
                    SubscriptionInfo info = p.getSubscriptionInfo();

                    Console.WriteLine("产品ID: " + info.getProductId());
                    Console.WriteLine("购买日期: " + info.getPurchaseDate());
                    Console.WriteLine("到期时间为: " + info.getExpireDate());//返回产品下次自动续订或到期的日期(对于已取消的自动续订订阅)
                    Console.WriteLine("是否已订阅 :" + info.isSubscribed().ToString());//非自动更新订阅返回“Result.Unsupported”
                    Console.WriteLine("是否已过期 :" + info.isExpired().ToString());//非自动更新订阅返回“Result.Unsupported”
                    Console.WriteLine("是否续期: " + info.isCancelled());//意味着已订阅,但不会续期,非自动更新订阅返回“Result.Unsupported”
                    Console.WriteLine("产品是否为免费试用版 :" + info.isFreeTrial());//非自动更新订阅返回“Result.Unsupported”
                    Console.WriteLine("是否自动续订: " + info.isAutoRenewing());//非自动更新订阅返回“Result.Unsupported”
                    Console.WriteLine("下个结算日期前剩余时间: " + info.getRemainingTime());//下一个结算日期之前剩余的时间,返回“TimeSpan”
                    Console.WriteLine("是否在介绍期内: " + info.isIntroductoryPricePeriod());//非自动更新订阅返回“Result.Unsupported”
                    Console.WriteLine("介绍价格: " + info.getIntroductoryPrice());//该产品的介绍价格,返回格式为“0.99USD”的值
                    Console.WriteLine("介绍期剩余时间: " + info.getIntroductoryPricePeriod()); //没有介绍价格期的订阅产品返回“TimeSpan.Zero”
                    Console.WriteLine("介绍期内可应用此产品的数量: " + info.getIntroductoryPricePeriodCycles());//返回int

                    if (info.isSubscribed() == Result.True)
                    {
                        if (info.isExpired() == Result.False)
                        {
                            if (String.Equals(info.getProductId(), newableSub, StringComparison.Ordinal))
                            {
                                Console.WriteLine(string.Format("订阅有效:", info.getProductId()));
                                //这里写执行
                            }
                        }
                        else
                            Console.WriteLine("订阅过期");
                    }
  
                }
            }
        }
    }

    public void CheckSubscriptionReceipt()//另一种检查订阅方法,通过receipt里的expiredate和当前时间对比来判断是否过期
    {
        var appleConfig = builder.Configure();//关于applestore的配置
        var receiptData = Convert.FromBase64String(appleConfig.appReceipt);//将receipt转换为64位
        AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);//AppleReceiptParser收据验证
        //这里如果不使用unity的收据混淆则为:AppleReceipt receipt = new AppleReceiptParser().Parse(receiptData);
        foreach (AppleInAppPurchaseReceipt productReceipt in receipt.inAppPurchaseReceipts)
        {//这里如果有多个订阅的话,可以先匹配productID,再读取productType
            if (productReceipt.productType == 3)//有三种类型:消耗/非消耗/订阅 
            {
                Console.WriteLine("订阅productID = " + productReceipt.productID);
                DateTime expirationDate = productReceipt.subscriptionExpirationDate;//订阅到期时间
                Console.WriteLine("订阅到期时间 = " + expirationDate.ToString());
                
                DateTime now = DateTime.Now.ToUniversalTime();//转换为世界时间
                if (DateTime.Compare(now, expirationDate) < 0)//DateTime.Compare(t1,t2);t1早于t2小于0(等于0,大于0)
                    Console.WriteLine("订阅未过期");
                    //在这里写逻辑
                else
                    //订阅无效,在这里写逻辑
                    Console.WriteLine("订阅已过期");
            }
        }
    }

    //--------IStoreListener接口
    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
        Debug.Log("初始化通过");
        m_StoreController = controller;
        m_StoreExtensionProvider = extensions;
    }

    public void OnInitializeFailed(InitializationFailureReason error)
    {
        Debug.Log("初始化失败,原因:" + error);
    }

    public void OnPurchaseFailed(Product i, PurchaseFailureReason p)
    {
        Debug.Log(string.Format("购买失败. Product: '{0}', 原因: {1}", i.definition.storeSpecificId, p));
    }

    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)//在这里写内购成功后的操作
    {
        bool validPurchase = true; // Presume valid for platforms with no R.V.

        var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
            AppleTangle.Data(), Application.identifier);

        try
        {//验证收据
            var result = validator.Validate(e.purchasedProduct.receipt);
            // For informational purposes, we list the receipt(s)
            Console.WriteLine("收据已验证. Contents:");
            foreach (IPurchaseReceipt productReceipt in result)
            {
                AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
                if (null != apple)
                {
                    Console.WriteLine(apple.productID);
                    Console.WriteLine(apple.purchaseDate);
                    Console.WriteLine(apple.originalTransactionIdentifier);
                    Console.WriteLine(apple.subscriptionExpirationDate);
                    Console.WriteLine(apple.cancellationDate);
                    Console.WriteLine(apple.quantity);
                }
            }
        }
        catch (IAPSecurityException)
        {
            Console.WriteLine("无效收据");
            validPurchase = false;
        }

        if (validPurchase)
        {
            if (string.Equals(e.purchasedProduct.definition.id, coins100, StringComparison.Ordinal))
            {
                Debug.Log(string.Format("购买成功. ID: '{0}'", e.purchasedProduct.definition.id));
                //这里写逻辑
            }
            else if (string.Equals(e.purchasedProduct.definition.id, newMap, StringComparison.Ordinal))
            {
                Debug.Log(string.Format("购买成功. ID: '{0}'", e.purchasedProduct.definition.id));
                //这里写逻辑
            }
            else if (string.Equals(e.purchasedProduct.definition.id, subscription, StringComparison.Ordinal))
            {
                Debug.Log(string.Format("购买成功. ID: '{0}'", e.purchasedProduct.definition.id));
                //这里写逻辑
            }
            else if (string.Equals(e.purchasedProduct.definition.id, newableSub, StringComparison.Ordinal))
            {
                Debug.Log(string.Format("购买成功. ID: '{0}'", e.purchasedProduct.definition.id));
                //这里写逻辑
            }
            else
            {
                Debug.Log(string.Format("购买失败. Unrecognized product: '{0}'", e.purchasedProduct.definition.id));
            }
        }
        // Return a flag indicating whether this product has completely been received, or if the application needs 
        // to be reminded of this purchase at next app launch. Use PurchaseProcessingResult.Pending when still 
        // saving purchased products to the cloud, and when that save is delayed. 
        return PurchaseProcessingResult.Complete;
    }

}

要说的是,里面有一个RestorePurchase方法和两个检查订阅是否过期的方法。经我测试保险起见在OnInitialized()里先调用RestorePurchases()再调用CheckSub()来恢复订阅逻辑。CheckSubscriptionReceipt()时见效时不见效。

注意如果要使用订阅的话最好用自动更新型订阅,方便Restore,同时SubscriptionManager里的各种属性也只对自动更新型有效。

如果使用服务器,则可以验证收据 参考

Unity项目添加广告,内购测试并上线APP Store_第39张图片

Unity项目添加广告,内购测试并上线APP Store_第40张图片

3.关于自动续订的各种情况

Unity项目添加广告,内购测试并上线APP Store_第41张图片

Unity项目添加广告,内购测试并上线APP Store_第42张图片

Unity项目添加广告,内购测试并上线APP Store_第43张图片

Unity项目添加广告,内购测试并上线APP Store_第44张图片

Unity项目添加广告,内购测试并上线APP Store_第45张图片

Unity项目添加广告,内购测试并上线APP Store_第46张图片

如果需要最新unity IAP的例子参考  和 Unity IAP 手册

4.问题爬坑

1.日志显示“初始化失败,原因:NoProductsAvailable”

有可能是因为没添加成功内购物品,日志里会显示UnityIAP:Received 0 products,且之后日志每项内购项目都会Unvailable product com.xxxxxx.xxxx 之类的。原因是开发者过期了,续费后不仅要在Developer Account里重新同意协议,还要在App connect的“协议,税务和银行业务”选项里重新统一一遍,直到下面你的app显示有效后就可以了。

Ⅲ.上线App Store

1.打包

xcode内Product→Archive打包。

Unity项目添加广告,内购测试并上线APP Store_第47张图片

然后验证

Unity项目添加广告,内购测试并上线APP Store_第48张图片

问题爬坑

1.Xcode内Build,Archive,Debug,Release,Profile,Analyze的区别

    参考来源

    

Build则是xcode内的那个播放键,就是生成真机测试版本,archive是生成打包appstore上传的文件。

Unity项目添加广告,内购测试并上线APP Store_第49张图片

Unity项目添加广告,内购测试并上线APP Store_第50张图片

Unity项目添加广告,内购测试并上线APP Store_第51张图片

Unity项目添加广告,内购测试并上线APP Store_第52张图片

  • 可能出现的问题

2.提示:“Code signing is required for product type 'Application' in SDK 'iOS 11.2”

参考来源

Unity项目添加广告,内购测试并上线APP Store_第53张图片

Unity项目添加广告,内购测试并上线APP Store_第54张图片

我出现这条提示时因为我把pp文件里的release版(需要distribution identity绑定)填成了debug版(developer identity),换成对应的就OK了。

3.提示:“Invalid bitcode version (Producer: '802.0.42.0_0' Reader: '800.0.42.1_0')”

参考来源

Unity项目添加广告,内购测试并上线APP Store_第55张图片

4.valid时提示UIApplicationExitsOnSuspend key invaild

参考来源

Unity项目添加广告,内购测试并上线APP Store_第56张图片

5.提示The Google Mobile Ads SDK was initialized incorrectly

参考来源

GADIsAdManagerApp

Unity项目添加广告,内购测试并上线APP Store_第57张图片

6.提示all ios apps submitted to the app store must be built with the ios 13 sdk or later

是因为xcode版本太低了,先升级xcode。如果macOS版本太低升级不了Xcode的话先升级macOS

Unity项目添加广告,内购测试并上线APP Store_第58张图片

Unity项目添加广告,内购测试并上线APP Store_第59张图片

2.上传到AppStore(即上传在AppConnect里的“构建版本”)

参考来源

Unity项目添加广告,内购测试并上线APP Store_第60张图片

Unity项目添加广告,内购测试并上线APP Store_第61张图片

Unity项目添加广告,内购测试并上线APP Store_第62张图片

Unity项目添加广告,内购测试并上线APP Store_第63张图片

问题爬坑:

①xcode 上传ipa时卡在authenticating,进度条不动了

建议更新下梯子,换个流畅的通道很快就好了。

②xcode内上传完成,但app connect内一直构建版本为空

ipa因为不合规定被打回了,登入注册developer账号的邮箱,苹果会发邮件并说明是什么原因。

修改后再次上传时记得版本号要升一阶。

③提交版本被打回,苹果邮箱提示ITMS-90809: Deprecated API Usage

因为使用了UIWebView API,在Unity和Admob的框架里都有使用。

虽然Unity和Admob都说已解决了问题。unity哪些版本修复了

此外,Unity引擎在所有受支持的版本中均删除了对UIWebview的引用:
2017.4.33f1
2018.4.10f1
2019.2.7f2
2019.3.0b4
2020.1.0a5
 

但我使用2019.3.8f1 2019.3.9f1 2018.4.22f1的中国特供版(是的,只能从unity.com下载)都还是有问题。

只有2020.1.0a25版本的Unity没有问题。(new:2019.3.13f1 中国特供版经我测试也没有问题)

查找当前版本有没有问题不用archive后上传后被拒后看邮件。直接在当前版本里新建空项目,转换到ios platform后build

然后在终端cd 你build好的文件夹 回车后 参考来源

grep -r UIWebView .

就会显示是否有UIWebview的引用。

admob说它修复了,但我更新了最新版本admob unity包 5.1.0 ,在使用unity 2020.1.0a25 版本下的空文件,还是提示有UIWebView的引用。所以目前我只能取消了游戏使用Admob,等之后版本出来会继续试看哪些能用。

查到的修改UIWebView 里面有修改libiPhone.a内的UIWebView的脚本,我试了还是不行。

④ xcode内上传了,app connect里的构建版本为空,也没有邮件提醒

在app connect 里查看 活动 选项卡,里面有你的版本,如果显示正在处理的话,20分钟以内会处理好,然后就可以在构建版本里选择你上传的版本了。20分钟还是正在处理的话 试试这个 。

⑤更新到最新的admob运行时提醒EntryPointNotFoundException: GADUCreateRewardedAd

没有问题不用管,这是移动端的调用,打包到真机后就不会有提醒了。

⑥app connect显示上传截屏尺寸不对

unity内项目build的时选simulator,然后在xcode内用模拟器针对appstore要求的各个尺寸截图。

⑦上传截屏显示不要使用Alhpa通道

直接mac打开截屏图片然后 文件-导出-取消勾选Alpha然后导出新图片就可以了。

你可能感兴趣的:(unity,ios,android)