Unity 之 接入IOS内购过程解析
看完此文章你可以了解IOS内购接入全过程,可以学习到Unity从零接入内购功能。另外此博文和文末源码没有涉及到掉单补单部分逻辑。


PS:若公司已有运营人员在后台操作过了,可以跳过此步骤。注意测试机上还是需要登陆沙箱账号奥。
首先打开苹果开发者官网:https://developer.apple.com/
点击登陆并点“Account”,在Apple Developer Center中,导航到相应的标识符部分:

添加新的 App ID 以创建与 Apple 的基本应用程序实体。
注意:使用显式应用程序 ID。通配符应用 ID (com.example.*) 不能用于使用应用内购买的应用。
注意:在开发者中心创建 App ID 后,即可在 iTunes Connect 中使用它。



选择功能并使用加号 (“+”) 按钮添加新的应用内购买:

选择产品类型:


iTunes Connect创建沙盒测试器以在您的测试设备的iTunes帐户上使用。为此,请导航至iTunes Connect > Users and Roles,然后选择加号 (“+”) 按钮。PS:详情可查看Apple的Sandbox Tester文档,。


Bundle Identitifier和Team与 iTunes Connect 中使用的一致

打开Windows -> Package Manager 下载 In App Purchasing

打开服务窗口,在服务窗口中查找和启用应用内购买

选择项目ID(当前登录的账号)

启用In-APP Purchasing (有的时候切换慢,需要等一会)

回答问题
问:这款应用主要面向13岁以下的儿童(是就勾选,不是不勾选)
有个报错
我没有解决也没有影响,需要解决的话按照下面的提示操作一下

实现IStoreListener接口,接口提供四个回调函数,分别是初始化成功、失败,购买成功、失败;
编写初始化逻辑,完善初始化成功、失败回调接口函数;
编写调用购买逻辑,完善购买成功、失败回调接口函数;
实际开发中需要限制,购买按钮只被点击一次。
代码结构就是这样了,详细解释代码注释已经写得很清楚了,这里不再赘述。
使用时将代码挂载到场景即可进行初始化,然后创建Button监听代码中的OnClickPurchase方法即可打包测试。
PS:注意需要将goodsList数组中的key换成你后台申请的
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
/// <summary>
/// IAP管理类
/// </summary>
public class IAPManager : MonoBehaviour, IStoreListener
{
/// <summary>
/// 需要换成对应游戏后台的key
/// </summary>
private string[] goodsList = new string[]
{
"com.czhenya.gold.1",
"com.czhenya.gold.2",
"com.czhenya.gold.3"
};
// 控制器
private IStoreController controller;
// 苹果扩展
private IAppleExtensions appleExtensions;
// 谷歌商店扩展
private IGooglePlayStoreExtensions googlePlayStoreExtensions;
// 是否可以发起购买
private bool isCanOnClickBubBtn = false;
void Start()
{
Init();
}
/// <summary>
/// 初始化
/// </summary>
private void Init()
{
// 没有网络,IAP会一直初始化
if (Application.internetReachability == NetworkReachability.NotReachable)
{
Debug.Log("----- 用户没有连接网络 IAP不可用 ------");
}
var module = StandardPurchasingModule.Instance();
ConfigurationBuilder builder = ConfigurationBuilder.Instance(module);
// builder.AddProduct("商品id1", ProductType.Consumable);
// ProductType :和后台说明对应
// consumable:可消费的,如游戏中的金币,用完还可以再购买。
// non-consumable:不可销毁的,一次购买,永久生效。比如去广告,解锁游戏关卡,这种商品只能购买一次。
// subscription:订阅的,这种一般用于新闻、杂志、或者app里面的月卡。可以按月或者按年收费。
for (int i = 0; i < goodsList.Length; i++)
{
builder.AddProduct(goodsList[i], ProductType.Consumable);
}
// 开始初始化
UnityPurchasing.Initialize(this, builder);
}
/// <summary>
/// 初始化成功 -- 接口函数
/// </summary>
/// <param name="controller"></param>
/// <param name="extensions"></param>
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
Debug.Log("【Unity IAP】初始化成功 IAP initialize success");
isCanOnClickBubBtn = true;
this.controller = controller;
// 回调赋值
this.appleExtensions = extensions.GetExtension<IAppleExtensions>();
this.googlePlayStoreExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();
//登记 购买延迟 监听器
appleExtensions.RegisterPurchaseDeferredListener(OnDeferred);
}
//购买延迟提示
private void OnDeferred(Product item)
{
Debug.Log("【Unity IAP】 网速慢.................");
}
/// <summary>
/// 初始化失败回调 -- 接口函数
/// </summary>
/// <param name="error"></param>
public void OnInitializeFailed(InitializationFailureReason error)
{
Debug.LogError("【Unity IAP】初始化失败 OnInitializeFailed, reason:" + error.ToString());
}
/// <summary>
/// 购买失败回调 -- 接口函数
/// </summary>
/// <param name="i"></param>
/// <param name="p"></param>
public void OnPurchaseFailed(Product i, PurchaseFailureReason p)
{
Debug.LogError("【Unity IAP】购买失败 OnPurchaseFailed,reason:" + p.ToString());
if (this.onPurchaseFailed != null)
{
this.onPurchaseFailed();
this.onPurchaseFailed = null;
}
}
/// <summary>
/// 购买成功回调 -- 接口函数
/// </summary>
/// <param name="e"></param>
/// <returns></returns>
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
Debug.LogError("【Unity IAP】购买过程 purchase finished, apple return receipt:" + e.purchasedProduct.receipt);
if (this.onPurchaseSuccess != null)
{
this.onPurchaseSuccess(e.purchasedProduct.receipt);
this.onPurchaseSuccess = null;
}
return PurchaseProcessingResult.Complete;
}
/// <summary>
/// 支付失败回调
/// </summary>
private Action onPurchaseFailed;
/// <summary>
/// 支付成功回调
/// </summary>
private Action<string> onPurchaseSuccess;
/// <summary>
/// 购买产品
/// </summary>
/// <param name="productId">产品ID</param>
/// <param name="onFailed">失败回调</param>
/// <param name="onSuccess">成功回调</param>
public void PurchaseProduct(string productId, Action onFailed, Action<string> onSuccess)
{
this.onPurchaseFailed = onFailed;
this.onPurchaseSuccess = onSuccess;
if (controller != null)
{
var product = controller.products.WithID(productId);
if (product != null && product.availableToPurchase)
{
Debug.Log("【Unity IAP】开始购买");
controller.InitiatePurchase(productId);
}
else
{
Debug.LogError("【Unity IAP】失败回调 no product with productId:" + productId);
if (this.onPurchaseFailed != null)
{
this.onPurchaseFailed();
}
}
}
else
{
Debug.LogError("【Unity IAP】失败回调 controller is null,can not do purchase");
if (this.onPurchaseFailed != null)
{
this.onPurchaseFailed();
}
}
}
/// <summary>
/// 发起购买函数 -- 商城按钮监听
/// </summary>
/// <param name="i"></param>
public void OnClickPurchase(int i)
{
// 正式项目时需限制 -- 不允许多次点击
Debug.Log("【Unity IAP】发起购买函数 " + Application.internetReachability);
if (Application.internetReachability == NetworkReachability.NotReachable)
{
Debug.Log("【Unity IAP】用户没网... ");
return;
}
PurchaseProduct(goodsList[0], OnBuyFailed, OnBuySuccess);
}
/// <summary>
/// 购买失败回调
/// </summary>
void OnBuyFailed()
{
Debug.Log("【Unity IAP】购买失败回调 OnBuyFailed...");
}
/// <summary>
/// 购买成功回调
/// </summary>
/// <param name="str"></param>
void OnBuySuccess(string str)
{
Debug.Log("【Unity IAP】购买成功回调 OnBuySuccess..." + str);
//会得到下面这样一个字符串
//{"Store":"AppleAppStore",
//"TransactionID":"1000000845663422",
//"Payload":"MIIT8QYJKoZIhvcNAQcCoIIT4jCCE94CAQExBBMMIIBa ... 还有N多 ..."}
}
}
代码配置和手动配置选择一个习惯用的方式即可。
由于内购需要系统库StoreKit.framework和iAd.framework。为了不每次打包Xcode时都手动添加,所以创建打包配置代码。(复制下面文件,放到Editor文件夹下)
using System.IO;
using UnityEditor;
using UnityEngine;
#if UNITY_IOS
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
#endif
/// <summary>
/// 打包自动配置文件
/// </summary>
public class CZYConfigEditor
{
#if UNITY_IOS
[PostProcessBuildAttribute(100)]
public static void onPostProcessBuild(BuildTarget target, string targetPath)
{
if (target != BuildTarget.iOS)
{
return;
}
string projPath = PBXProject.GetPBXProjectPath(targetPath);
PBXProject proj = new PBXProject();
proj.ReadFromString(File.ReadAllText(projPath));
string unityTarget = proj.GetUnityFrameworkTargetGuid();
#region 系统依赖库
proj.AddFrameworkToProject(unityTarget, "StoreKit.framework", false);
proj.AddFrameworkToProject(unityTarget, "iAd.framework", false);
#endregion
string content = proj.WriteToString();
File.WriteAllText(projPath, content);
}
#endif
}
不写上面代码的话,打包出Xcode工程后,需要手动添加StoreKit.framework和iAd.framework:

然后正常打包进行测试~ 即可完成开篇效果。
IAP初始化成功日志:

购买成功回调日志:
【Unity IAP】购买成功回调 OnBuySuccess…{“Store”:“AppleAppStore”,“TransactionID”:“1000000866663121”,“Payload”:“MIIT8QYJKoZIhvcNAQcCo
…中间省略N多行…
jSYLAk”}
System.Action`1:Invoke(T)
IAPMgr:ProcessPurchase(PurchaseEventArgs)
UnityEngine.Purchasing.PurchasingManager:ProcessPurchaseIfNew(Product)
UnityEngine.Purchasing.JSONStore:OnPurchaseSucceeded(String, String, String)
System.Action:Invoke()
UnityEngine.Purchasing.Extension.UnityUtil:Update()
其实源码以及步骤都在上面分享过了,若还有什么不明白的,可以点击下面链接下载,积分不够的童鞋关注下方卡片公号,回复:IOS内购 即可获得Demo源码~
我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我正在使用ruby1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\
简而言之错误:NOTE:Gem::SourceIndex#add_specisdeprecated,useSpecification.add_spec.Itwillberemovedonorafter2011-11-01.Gem::SourceIndex#add_speccalledfrom/opt/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:91./opt/local/lib/ruby/gems/1.8/gems/rails-2.3.8/lib/rails/gem_dependency.rb:275:in`==':und
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里编辑 3.解析依赖到项目中
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
我正在使用ruby2.1.0我有一个json文件。例如:test.json{"item":[{"apple":1},{"banana":2}]}用YAML.load加载这个文件安全吗?YAML.load(File.read('test.json'))我正在尝试加载一个json或yaml格式的文件。 最佳答案 YAML可以加载JSONYAML.load('{"something":"test","other":4}')=>{"something"=>"test","other"=>4}JSON将无法加载YAML。JSON.load("