Unity 接入IAP(上)Android篇

Unity 接入IAP(上)Android篇,第1张

很多项目都会遇到内购和订阅相关模块,这里我总结一下内购接入的时候遇到的各种坑,以及内购测试的时候,有什么比较好的方法测试。本篇先介绍Android篇。

打开Unity编辑器,找到Servic,启用IAP 服务。(启动后也会默认打开数据分析服务)

IAP设置页面,需要设置app适用年龄,根据自己产品自行选择年龄是否13+,然后需要去注意有个Options选项,需要填入一个Google Public Key,这个Key在Google后台,关联产品后就有了,会自动填写上。

接下来就是IAP文档的内容了,建议去官网看文档,这里我把基本框架写出来。

参考: >

1、首先可以直接在assetchecker工具文件下找到rulereportyaml文件并用记事本或者其他软件打开查看。

2、其次登陆UPR网站查看。

3、最后在检测时如果填写了projectid,检测结束后,可直接在UPR网站该项目内查看检测结果。

感觉你的问题应该有更直接的办法,一是不太懂你的意思,比如你具体是要实现怎样的功能?为什么要根据XY计算,难道同样的装备随机次数不同,power计算也不同? 第一、第二个装备又是什么意思?理论上应该限定总的可装备上限,比如一共就可以装备三个东西,那么你自然需要定义一个三个元素的数组了。如果每个装备名称对应的xy是固定的话,计算power似乎没意义了,还不如把power参数和装备写在一起,而你只要随机xy来取不同装备就行了吧。

以下顺带讲一下写入数据的办法。

(不好意思我用的是JS,语法上转换一下就行了。)

不用playerprefs的方法是:

新建一个类,这个类里面定义你要储存在磁盘上的变量,最后把这个类写成dat文件(binary文件),这样这个类里的数据就写在磁盘上了,以后你就可以随时调取、更新所存储的数据了。

(1)编程时你要用到几个基本的包:

import System;

import SystemRuntimeSerializationFormattersBinary; //用来写binary文件

import SystemIO; //基本的输入输出

详细的你还可以去查net 的MSDN 参考。

(2)你要自定义一个类用来规定数据,比如:

Class GameData {

var itemID:int;

var power:float;

}

(3)你还需要一个实例化的脚本(比如命名成,GameDataManager ),把这个脚本放在一个场景中GameObject上就可以了,这个脚本用来实际 *** 作读取和写入。把这个类做成一个Singleton,就是说仅在整个游戏刚启动时初始化一个静态的实例,而且在此后的场景退出时都不要清除,这样可以避免反复覆盖读取和存储数据的风险。比如:

static var instance:GameDataManager;

Awake() {

if(instance == null){ //当前场景中没有其他实例化的脚本,

DontDestroyOnLoad(gameObject); //那么说现在本脚本是唯一的实例,所以不要销毁

instance = this; //把唯一的静态指针指向自己。

}else if(instance != this){

Destroy(gameObject); //当前场景中已经有了其它实例!说本脚本是重复的实例,销毁!

}

}

(4)接下来要判断是否已经存在先前的存档binary文件,如果没有,就需要初始化一个GameData类。

var myGameData:GameData;

function Start () {

myGameData= Load(); //此处Load()是脚本后面定义的一个读取binary文件Dat的方法

if(myGameData== null){ // 如果没有读取到文件,就初始化一个新的数据类

myGameData= new GameData();

myGameDatapower= 999; // 数据初始化,这里你可以自定义更复杂的方法或算法

Save(); //写入数据,此处Save()也是后面定义的一个存储binary文件Dat的方法

}

}

(5)具体完成Load() 和 Save()方法:

function Save (){

var bFile:BinaryFormatter;

var file:FileStream;

bFile = new BinaryFormatter();

file = FileCreate(ApplicationpersistentDataPath + "/GameDatadat"); //在系统默认应用程序路径创建Dat文件

bFileSerialize(file, currentGameData); // 写入数据

fileClose(); //完成文件

}

function Load ():GameData{

var bFile:BinaryFormatter;

var file:FileStream;

var loadData:GameData;

if(FileExists(ApplicationpersistentDataPath + "/GameDatadat")){//判断dat文件是否存在

bFile = new BinaryFormatter();

file = FileOpen(ApplicationpersistentDataPath +"/GameDatadat", FileModeOpen);//打开系统默认路径中的Dat文件

loadData = bFileDeserialize(file) as GameData; //获取读取到的数据

fileClose();//关闭文件

}

return loadData; //返回获取到的数据类

}

最后,如果你英文过的去,unity的官方网站上有全套视频,其中一个章节就是讲解如何存储数据的!不过前提是你得会 夫安 七一昂,否则视频可能看不了。今年封的更严了,国情你懂的,

由于数据要序列化,所以要保存类中的字段是可在Unity可序列化的,例如:Type 和 MethodInfo 以及 object 就不行

object不能序列化就导致反射函数的参数需要转换,带来很多麻烦

一、定义保存数据的类

[Serializable]

public class SerializableClass

{

public byte bytedata;

public short shortdata;

public int intdata;

public string stringdata = "";

public float floatdata;

public double doubledata;

public bool booldata;

public Color colordata;

public Vector2 Vector2data;

public Vector3 Vector3data;

public Vector4 Vector4data;

public Vector2Int Vector2Intdata;

public Vector3Int Vector3Intdata;

public AnimationCurve animationCurvedata = new AnimationCurve();

public Gradient gradientdata = new Gradient();

public Ease animationType = EaseLinear;

public UnityEngineObject data = null;

public CustomType type = CustomTypeInt16;

public SerializableClass()

{

}

public SerializableClass(object value)

{

type = (CustomType)EnumParse(typeof(CustomType), valueGetType()Name);

FieldInfo[] fieldinfo = typeof(SerializableClass)GetFields();

for (int i = 0; i < fieldinfoLength; i++)

{

if(fieldinfo[i]FieldType == valueGetType() || valueGetType()IsInstanceOfType(fieldinfo[i]FieldType))

{

fieldinfo[i]SetValue(this, value);

}

}

}

public object GetData()

{

FieldInfo[] fieldinfo = typeof(SerializableClass)GetFields();

for (int i = 0; i < fieldinfoLength; i++)

{

if (fieldinfo[i]FieldTypeName == typeToString())

{

return fieldinfo[i]GetValue(this);

}

}

return data;

}

public void SetValue(object value )

{

if (value == null) return;

if (typeof(UnityEngineObject)IsAssignableFrom(valueGetType()))

type = (CustomType)EnumParse(typeof(CustomType), typeof(UnityEngineObject)Name);

else

type = (CustomType)EnumParse(typeof(CustomType), valueGetType()Name);

FieldInfo[] fieldinfo = typeof(SerializableClass)GetFields();

for (int i = 0; i < fieldinfoLength; i++)

{

if (fieldinfo[i]FieldType == valueGetType() || fieldinfo[i]FieldTypeIsAssignableFrom(valueGetType()))

{

fieldinfo[i]SetValue(this, value);

}

}

}

}

public enum CustomType

{

Byte,

Int16,

Int32,

String,

Single,

Double,

Boolean,

Color,

Vector2,

Vector3,

Vector4,

Vector2Int,

Vector3Int,

AnimationCurve,

Gradient,

Object,

Ease

}

登录后复制

首先把数据分成3种,一种是基础的数据类型例如int、float等,第二种是在Unity种定义的没有继承Object的类型,一般是Unity种的结构体,第三种是Unity中定义的继承与Object的类型,例如继承MonoBehaviour的类。

因为C#中的Type不能序列化,所以自定义CustomType,这个通过对比类型名和CustomType中的字段,区分属于哪种类型。

二、计算

public enum MathParameterEnum

{

[CanUsedType(null, "GreaterThan")]

[TipsName("大于")]

Greater = 0,

[CanUsedType(null, "LessThan")]

[TipsName("小于")]

Less,

[CanUsedType(null, "")]

[TipsName("等于")]

Equal,

[CanUsedType(null, "")]

[TipsName("不等于")]

NotEqual,

[CanUsedType(null, "Add")]

[TipsName("加法")]

Addition,

[CanUsedType(null, "Subtract")]

[TipsName("减法")]

Subtraction,

[CanUsedType(null, "Multiply")]

[TipsName("乘法")]

Multiply,

[CanUsedType(null, "Divide")]

[TipsName("除法")]

Division,

[TipsName("绝对值")]

[CanUsedType(typeof(Mathf) , "Abs")]

Abs = 8,

[TipsName("正切")]

[CanUsedType(typeof(Mathf), "Tan")]

Tan,

[TipsName("余弦")]

[CanUsedType(typeof(Mathf), "Cos")]

Cos,

[TipsName("正弦")]

[CanUsedType(typeof(Mathf), "Sin")]

Sin,

[TipsName("长度归一")]

[CanUsedType(null , "Normalize")]

Normalized = 12,

[TipsName("角度" )]

[CanUsedType(null, "Angle")]

Angle,

[TipsName("距离")]

[CanUsedType(null, "Distance")]

Distance,

}

登录后复制

首先通过自定义特性来简化计算的遍历。这里有三种计算类型,一种是基础类型的加减乘除运算,一种是Mathf类或者其他计算类中的函数,还有一种是数据类型自定义的计算。

其中大部分运算都可以通过反射实现,但是这里有两个问题。

1、隐式转换

例如Vector3 可以和float 相乘,也可以和Int相乘,但是Vector3的自定义运算中并没有和int相乘的函数。因为int可以隐式转换乘float,然后再调用float的运算重载。

因此在计算判断的时候,需要考虑类型能否隐式转换为可计算的类型。

//判断类型是否有隐式转换

public static bool HasImplicitConversion(Type baseType, Type targetType)

{

if (IsNumber(baseType, targetType))

{

if(typeof(double) == targetType && (typeof(long) == targetType || typeof(float) == baseType || typeof(int) == baseType|| typeof(short) == baseType|| typeof(byte) == baseType))

{

return true;

}

if (typeof(float) == targetType && (typeof(long) == targetType || typeof(int) == baseType || typeof(short) == baseType || typeof(byte) == baseType))

{

return true;

}

if (typeof(long) == targetType && (typeof(int) == baseType || typeof(short) == baseType || typeof(byte) == baseType))

{

return true;

}

if (typeof(int) == targetType && (typeof(short) == baseType || typeof(byte) == baseType))

{

return true;

}

if (typeof(short) == targetType && (typeof(byte) == baseType))

{

return true;

}

}

return baseTypeGetMethods(BindingFlagsPublic | BindingFlagsStatic)

Where(mi => miName == "op_Implicit" && miReturnType == targetType)

Any(mi => {

ParameterInfo pi = miGetParameters()FirstOrDefault();

return pi != null && piParameterType == baseType;

});

}

登录后复制

因为C#的基础数据类型,并没有靠op_Implicit去写隐式转换,所以只能手写。

2基本数据的计算

Vector3这种类型的加减乘除可以通过发射寻找计算函数,但是float,int,double等的加减运算就不行了。

public static BinaryExpression CanMath(ParameterExpression type1, ParameterExpression type2, string methodName)

{

try

{

BinaryExpression obj = (BinaryExpression)typeof(Expression)InvokeMember(methodName,

SystemReflectionBindingFlagsInvokeMethod | SystemReflectionBindingFlagsStatic

| SystemReflectionBindingFlagsPublic, null, null,

new object[] { type1, type2 });

return obj;

}

catch (Exception e)

{

return null;

}

}

登录后复制

public static object MathNumber(List<object> allobj, MathParameterEnum mathtype)

{

Type baseType = null;

for (int i = 0; i < allobjCount; i++)

{

Type nowtype = allobj[i]GetType();

if (baseType == null)

baseType = nowtype;

else

{

if (nowtype != baseType)

{

if (!HasImplicitConversion(nowtype, baseType))

{

if (HasImplicitConversion(baseType, nowtype))

{

baseType = nowtype;

}

else

return null;

}

}

}

}

CanUsedType obsAttr = GetEnumMathTip(mathtypeGetType()GetField(mathtypeToString()));

ParameterExpression _ParaA = ExpressionParameter(baseType, "a");

ParameterExpression _ParaB = ExpressionParameter(baseType, "b");

BinaryExpression _BinaAdd = CanMath(_ParaA, _ParaB, obsAttrmethodName);

if (_BinaAdd == null) return null;

Expression<Func<double, double, double>> doubleLamb;

Expression<Func<float, float, float>> floatLamb;

Expression<Func<long, long, long>> longLamb;

Expression<Func<int, int, int>> intLamb;

Expression<Func<short, short, short>> shortLamb;

if (baseType == typeof(double))

{

doubleLamb = ExpressionLambda<Func<double, double, double>>(_BinaAdd, new ParameterExpression[] { _ParaA, _ParaB });

double data = ConvertToDouble(allobj[0]);

for (int i = 1; i < allobjCount; i++)

{

data += doubleLambCompile()(data, ConvertToDouble(allobj[i]));

}

return data;

}

else if (baseType == typeof(float))

{

floatLamb = ExpressionLambda<Func<float, float, float>>(_BinaAdd , new ParameterExpression[] { _ParaA, _ParaB });

float data = ConvertToSingle(allobj[0]);

for (int i = 1; i < allobjCount; i ++)

{

data = floatLambCompile()(data , ConvertToSingle(allobj[i]));

}

return data;

}

else if (baseType == typeof(long))

{

longLamb = ExpressionLambda<Func<long, long, long>>(_BinaAdd, new ParameterExpression[] { _ParaA, _ParaB });

long data = ConvertToInt64(allobj[0]);

for (int i = 1; i < allobjCount; i++)

{

data = longLambCompile()(data, ConvertToInt64(allobj[i]));

}

return data;

}

else if (baseType == typeof(int))

{

intLamb = ExpressionLambda<Func<int, int, int>>(_BinaAdd, new ParameterExpression[] { _ParaA, _ParaB });

int data = ConvertToInt32(allobj[0]);

for (int i = 1; i < allobjCount; i++)

{

data = intLambCompile()(data, ConvertToInt32(allobj[i]));

}

return data;

}

else if (baseType == typeof(short))

{

shortLamb = ExpressionLambda<Func<short, short, short>>(_BinaAdd, new ParameterExpression[] { _ParaA, _ParaB });

short data = ConvertToInt16(allobj[0]);

for (int i = 1; i < allobjCount; i++)

{

data = shortLambCompile()(data, ConvertToInt16(allobj[i]));

}

return data;

}

return null;

}

登录后复制

这种方式虽然还是很麻烦,但是比用if else 简单了很多很多。

(T)ConvertChangeType(dataValue, typeof(T)); 应用为类型转换为范型

Author :JerryYang

Create By 20201019

环境:

Unity:201942f1

MacOS:10156

Xcode: 1201

我发现很多做Unity的童鞋都没打过IOS包,这里分享一个教程,希望能帮到大家。

首先需要准备一台Mac电脑(搭载MacOS系统的电脑),一部苹果手机,和一个苹果开发者账号;

软件需要用到Unity Edit 和Xcode;

最后就是你的unity项目啦。

这里的注意事项:

<1> Icon不用切圆角;

<2> 一定不能包含透明通道,否则提交不了。

<1>Bundle Identifier和App Store后台申请的Bundle ID要对应上;

<2>Version也要和后台相对应,我推荐使用semver格式管理版本;

<3>Build每次提交AppStore的时候必须改变(version 相同的情况下+1,不同的情况可以从0开始);

<4>TeamID可以暂时不写,也可以写开发者账号对应的TeamID。

当我们项目开发完要上线的时候可以看接下来这部分。

如需上架Google Play请看 Unity项目上架Google Play 。

暂时写这么多,有不懂的或者需要补充的请私信我。

我们先生成一个最简单的prefab,看看unity是如何存储数据的

生成的yaml数据如下图:

注意图中的2个ID

Node节点的Component组件包含了一个ID为 799 的组件

而这个 799 的组件是一个transform,并且transformm_GameObjectfileID指向了node的ID

不难看出,通过id建立了之间的索引关系

如果我copy自己点,这个ID会重新生成吗?

很明显,即时我们通过复制一个节点,新的节点id也发生了变化

我们给节点添加一个脚本,任何在copy后,发现id也是被重新生成了

可以看出,unity采用的方案是围绕着id进行关系关联

以上就是关于Unity 接入IAP(上)Android篇全部的内容,包括:Unity 接入IAP(上)Android篇、【unity官方】Unity项目常见问题、unity怎么查是否盗版资源等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/9632944.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-30
下一篇 2023-04-30

发表评论

登录后才能评论

评论列表(0条)

保存