AssetStore一直是Unity开发者又爱又恨的东西。首先这个生态绝对是先进的,并且越来越能够吸引高质量的开发者开发或开源他们的插件。它的缺点可能是大陆开发者尤为头疼的。
1、付费,客观的看99%的资源价格都在100刀以内,先不论个人开发者能否完成同样功能的开发,即使可以,也绝对值回工时费了。
2、网络,没有验证过在国外是否都能流畅下载,至少使用电信、网通的“局域网”都十分痛苦。
3、断点续传,AssetStore的资源下载过程中一旦网络异常或关闭Unity,都只能“从0开始”。
我们都知道AssetStore下载到Windows本地的路径是C:\Users\用户名\AppData\Roaming\Unity\Asset Store-5.x\开发者公司名\插件名\xxx.unitypackage。
下载过程中会有如下2个文件生成,在目录下持续F5,TMP文件体积会增长。
而JSON文件由url和key组成。
{"download" : {"url" : "http://d2ujflorbtfzji.cloudfront.net/download/950fd1fa-e786-4587-8180-13f83f057c52", "key" : "b370540fa8321d35e84cedafc7565512b69f06ae73cf07c589db3899a1b0cd4555c11dc3eae8561271d18d9012106263"}}
url很明显可以拷贝到任何下载器中下载,但下载完的文件即使手动添加后缀.unitypackage,也无法导入unity中使用。
后来查阅参考资料,发现可以通过key来解码,代码如下。
using System;
using System.Reflection;
namespace Babybus.Framework.ExtensionMethods
{
public static class AccessExtensions
{
public static T InvokeConstructor<T>(this Type type, Type[] paramTypes = null, object[] paramValues = null)
{
return (T)type.InvokeConstructor(paramTypes, paramValues);
}
public static object InvokeConstructor(this Type type, Type[] paramTypes = null, object[] paramValues = null)
{
if (paramTypes == null || paramValues == null)
{
paramTypes = new Type[] { };
paramValues = new object[] { };
}
var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, paramTypes, null);
return constructor.Invoke(paramValues);
}
public static T Invoke<T>(this object o, string methodName, params object[] args)
{
var value = o.Invoke(methodName, args);
if (value != null)
{
return (T)value;
}
return default(T);
}
public static T Invoke<T>(this object o, string methodName, Type[] types, params object[] args)
{
var value = o.Invoke(methodName, types, args);
if (value != null)
{
return (T)value;
}
return default(T);
}
public static object Invoke(this object o, string methodName, params object[] args)
{
Type[] types = new Type[args.Length];
for (int i = 0; i < args.Length; i++)
types[i] = args[i] == null ? null : args[i].GetType();
return o.Invoke(methodName, types, args);
}
public static object Invoke(this object o, string methodName, Type[] types, params object[] args)
{
var type = o is Type ? (Type)o : o.GetType();
var method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, types, null);
if (method == null)
method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
return method.Invoke(o, args);
}
public static T GetFieldValue<T>(this object o, string name)
{
var value = o.GetFieldValue(name);
if (value != null)
{
return (T)value;
}
return default(T);
}
public static object GetFieldValue(this object o, string name)
{
var field = o.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return field.GetValue(o);
}
return null;
}
public static void SetFieldValue(this object o, string name, object value)
{
var field = o.GetType().GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
field.SetValue(o, value);
}
}
public static T GetPropertyValue<T>(this object o, string name)
{
var value = o.GetPropertyValue(name);
if (value != null)
{
return (T)value;
}
return default(T);
}
public static object GetPropertyValue(this object o, string name)
{
var property = o.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
{
return property.GetValue(o, null);
}
return null;
}
public static void SetPropertyValue(this object o, string name, object value)
{
var property = o.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
{
property.SetValue(o, value, null);
}
}
}
}
Menu工具
using UnityEditor;
using Babybus.Framework.ExtensionMethods;
using UnityEngine;
class DecryptUtility
{
[MenuItem("Utility/DecryptFile")]
static void DecryptFile()
{
var inputFile = Application.dataPath + "/950fd1fa-e786-4587-8180-13f83f057c52";
var key = "b370540fa8321d35e84cedafc7565512b69f06ae73cf07c589db3899a1b0cd4555c11dc3eae8561271d18d9012106263";
var unityEditor = typeof(Editor).Assembly;
var assetStoreUtils = unityEditor.GetType("UnityEditor.AssetStoreUtils");
assetStoreUtils.Invoke("DecryptFile", inputFile, inputFile + ".unitypackage", key);
}
}
这里注意修改inputFile的路径,需要解码的文件名和key值,我的代码对应路径是项目的project文件夹。点击Menu上新增的Utility→DecryptFile,执行上面的代码。进度条读完,一个同名的unitypackage已经生成在目录下了。
把这个unitypackage文件拷到C:\Users\用户名\AppData\Roaming\Unity\Asset Store-5.x\开发者公司名\插件名\
路径下,Unity内打开AssetStore,可以看见即使文件没有重命名,系统已经识别该文件存在。
另外笔者实验将不相关的unitypackage重命名靠过来,或低版本的该插件导进来,系统都能识别,结果分别是导入按钮变灰色,及显示更新。key值或许就控制了这些奥妙。