很多项目都会遇到内购和订阅相关模块,这里我总结一下内购接入的时候遇到的各种坑,以及内购测试的时候,有什么比较好的方法测试。本篇先介绍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怎么查是否盗版资源等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)