Optimize packaging bundle core logic

优化了打包的核心逻辑,对依赖资源进行自动划分。
This commit is contained in:
hevinci
2022-04-30 19:10:52 +08:00
parent b1bb79bd95
commit b443a1c308
17 changed files with 308 additions and 278 deletions
@@ -14,6 +14,11 @@ namespace YooAsset.Editor
/// </summary>
public string CollectPath = string.Empty;
/// <summary>
/// 收集器类型
/// </summary>
public ECollectorType CollectorType = ECollectorType.MainCollector;
/// <summary>
/// 寻址规则类名
/// </summary>
@@ -29,12 +34,6 @@ namespace YooAsset.Editor
/// </summary>
public string FilterRuleName = nameof(CollectAll);
/// <summary>
/// 不写入资源列表
/// </summary>
public bool NotWriteToAssetList = false;
/// <summary>
/// 资源分类标签
/// </summary>
@@ -65,14 +64,17 @@ namespace YooAsset.Editor
if (AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(CollectPath) == null)
throw new Exception($"Invalid collect path : {CollectPath}");
if (CollectorType == ECollectorType.None)
throw new Exception($"{nameof(ECollectorType)}.{ECollectorType.None} is invalid in collector : {CollectPath}");
if (AssetBundleGrouperSettingData.HasPackRuleName(PackRuleName) == false)
throw new Exception($"Invalid {nameof(IPackRule)} class type : {PackRuleName}");
throw new Exception($"Invalid {nameof(IPackRule)} class type : {PackRuleName} in collector : {CollectPath}");
if (AssetBundleGrouperSettingData.HasFilterRuleName(FilterRuleName) == false)
throw new Exception($"Invalid {nameof(IFilterRule)} class type : {FilterRuleName}");
throw new Exception($"Invalid {nameof(IFilterRule)} class type : {FilterRuleName} in collector : {CollectPath}");
if (AssetBundleGrouperSettingData.HasAddressRuleName(AddressRuleName) == false)
throw new Exception($"Invalid {nameof(IAddressRule)} class type : {AddressRuleName}");
throw new Exception($"Invalid {nameof(IAddressRule)} class type : {AddressRuleName} in collector : {CollectPath}");
}
/// <summary>
@@ -80,70 +82,61 @@ namespace YooAsset.Editor
/// </summary>
public List<CollectAssetInfo> GetAllCollectAssets(AssetBundleGrouper grouper)
{
Dictionary<string, string> adressTemper = new Dictionary<string, string>(1000);
Dictionary<string, CollectAssetInfo> result = new Dictionary<string, CollectAssetInfo>(1000);
bool isRawAsset = PackRuleName == nameof(PackRawFile);
// 如果是文件夹
// 检测原生资源包的收集器类型
if (isRawAsset && CollectorType != ECollectorType.MainCollector)
throw new Exception($"The raw file must be set to {nameof(ECollectorType)}.{ECollectorType.MainCollector} : {CollectPath}");
// 收集打包资源
if (AssetDatabase.IsValidFolder(CollectPath))
{
string collectDirectory = CollectPath;
string[] findAssets = EditorTools.FindAssets(EAssetSearchType.All, collectDirectory);
foreach (string assetPath in findAssets)
{
if (IsValidateAsset(assetPath) == false)
continue;
if (IsCollectAsset(assetPath) == false)
continue;
if (result.ContainsKey(assetPath) == false)
if (IsValidateAsset(assetPath) && IsCollectAsset(assetPath))
{
string address = GetAddress(grouper, assetPath);
string bundleName = GetBundleName(grouper, assetPath, isRawAsset);
List<string> assetTags = GetAssetTags(grouper);
var collectAssetInfo = new CollectAssetInfo(bundleName, address, assetPath, assetTags, isRawAsset, NotWriteToAssetList);
collectAssetInfo.DependAssets = GetAllDependencies(assetPath);
result.Add(assetPath, collectAssetInfo);
}
else
{
throw new Exception($"The collecting asset file is existed : {assetPath} in collector : {CollectPath}");
if (result.ContainsKey(assetPath) == false)
{
var collectAssetInfo = CreateCollectAssetInfo(grouper, assetPath, isRawAsset);
result.Add(assetPath, collectAssetInfo);
}
else
{
throw new Exception($"The collecting asset file is existed : {assetPath} in collector : {CollectPath}");
}
}
}
}
else
{
string assetPath = CollectPath;
if (result.ContainsKey(assetPath) == false)
if (IsValidateAsset(assetPath) && IsCollectAsset(assetPath))
{
if (isRawAsset && NotWriteToAssetList)
UnityEngine.Debug.LogWarning($"Are you sure raw file are not write to asset list : {assetPath}");
string address = GetAddress(grouper, assetPath);
string bundleName = GetBundleName(grouper, assetPath, isRawAsset);
List<string> assetTags = GetAssetTags(grouper);
var collectAssetInfo = new CollectAssetInfo(bundleName, address, assetPath, assetTags, isRawAsset, NotWriteToAssetList);
collectAssetInfo.DependAssets = GetAllDependencies(assetPath);
var collectAssetInfo = CreateCollectAssetInfo(grouper, assetPath, isRawAsset);
result.Add(assetPath, collectAssetInfo);
}
else
{
throw new Exception($"The collecting asset file is existed : {assetPath} in collector : {CollectPath}");
throw new Exception($"The collecting single asset file is invalid : {assetPath} in collector : {CollectPath}");
}
}
// 检测可寻址地址是否重复
if (AssetBundleGrouperSettingData.Setting.EnableAddressable)
{
foreach (var collectInfo in result)
HashSet<string> adressTemper = new HashSet<string>();
foreach (var collectInfoPair in result)
{
string address = collectInfo.Value.Address;
if (adressTemper.ContainsKey(address) == false)
if (collectInfoPair.Value.CollectorType == ECollectorType.MainCollector)
{
adressTemper.Add(address, address);
}
else
{
throw new Exception($"The address is existed : {address} in collector : {CollectPath}");
string address = collectInfoPair.Value.Address;
if (adressTemper.Contains(address) == false)
adressTemper.Add(address);
else
throw new Exception($"The address is existed : {address} in collector : {CollectPath}");
}
}
}
@@ -152,7 +145,15 @@ namespace YooAsset.Editor
return result.Values.ToList();
}
private CollectAssetInfo CreateCollectAssetInfo(AssetBundleGrouper grouper, string assetPath, bool isRawAsset)
{
string address = GetAddress(grouper, assetPath);
string bundleName = GetBundleName(grouper, assetPath);
List<string> assetTags = GetAssetTags(grouper);
CollectAssetInfo collectAssetInfo = new CollectAssetInfo(CollectorType, bundleName, address, assetPath, assetTags, isRawAsset);
collectAssetInfo.DependAssets = GetAllDependencies(assetPath);
return collectAssetInfo;
}
private bool IsValidateAsset(string assetPath)
{
if (assetPath.StartsWith("Assets/") == false && assetPath.StartsWith("Packages/") == false)
@@ -188,24 +189,31 @@ namespace YooAsset.Editor
}
private string GetAddress(AssetBundleGrouper grouper, string assetPath)
{
if (NotWriteToAssetList)
return assetPath;
if (CollectorType != ECollectorType.MainCollector)
return string.Empty;
IAddressRule addressRuleInstance = AssetBundleGrouperSettingData.GetAddressRuleInstance(AddressRuleName);
string adressValue = addressRuleInstance.GetAssetAddress(new AddressRuleData(assetPath, CollectPath, grouper.GrouperName));
return adressValue;
}
private string GetBundleName(AssetBundleGrouper grouper, string assetPath, bool isRawAsset)
private string GetBundleName(AssetBundleGrouper grouper, string assetPath)
{
string shaderBundleName = AssetBundleGrouperHelper.CollectShaderBundleName(assetPath);
if (string.IsNullOrEmpty(shaderBundleName) == false)
return shaderBundleName;
// 如果自动收集所有的着色器
if (AssetBundleGrouperSettingData.Setting.AutoCollectShaders)
{
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
if (assetType == typeof(UnityEngine.Shader))
{
string bundleName = AssetBundleGrouperSettingData.Setting.ShadersBundleName;
return EditorTools.GetRegularPath(bundleName).ToLower();
}
}
// 根据规则设置获取资源包名称
{
IPackRule packRuleInstance = AssetBundleGrouperSettingData.GetPackRuleInstance(PackRuleName);
string bundleName = packRuleInstance.GetBundleName(new PackRuleData(assetPath, CollectPath, grouper.GrouperName));
return AssetBundleGrouperHelper.CorrectBundleName(bundleName, isRawAsset);
return EditorTools.GetRegularPath(bundleName).ToLower();
}
}
private List<string> GetAssetTags(AssetBundleGrouper grouper)
@@ -47,41 +47,39 @@ namespace YooAsset.Editor
/// </summary>
public List<CollectAssetInfo> GetAllCollectAssets()
{
Dictionary<string, string> adressTemper = new Dictionary<string, string>(10000);
Dictionary<string, CollectAssetInfo> result = new Dictionary<string, CollectAssetInfo>(10000);
// 收集打包资源
foreach (var collector in Collectors)
{
var temper = collector.GetAllCollectAssets(this);
foreach (var assetInfo in temper)
{
if (result.ContainsKey(assetInfo.AssetPath) == false)
{
result.Add(assetInfo.AssetPath, assetInfo);
}
else
{
throw new Exception($"The collecting asset file is existed : {assetInfo.AssetPath} in grouper : {GrouperName}");
}
}
}
// 检测可寻址地址是否重复
if (AssetBundleGrouperSettingData.Setting.EnableAddressable)
{
foreach (var collectInfo in result)
HashSet<string> adressTemper = new HashSet<string>();
foreach (var collectInfoPair in result)
{
string address = collectInfo.Value.Address;
if (adressTemper.ContainsKey(address) == false)
if (collectInfoPair.Value.CollectorType == ECollectorType.MainCollector)
{
adressTemper.Add(address, address);
}
else
{
throw new Exception($"The address is existed : {address} in grouper : {GrouperName}");
string address = collectInfoPair.Value.Address;
if (adressTemper.Contains(address) == false)
adressTemper.Add(address);
else
throw new Exception($"The address is existed : {address} in grouper : {GrouperName}");
}
}
}
// 返回列表
return result.Values.ToList();
}
}
@@ -17,14 +17,13 @@ namespace YooAsset.Editor
public const string XmlGrouperName = "GrouperName";
public const string XmlGrouperDesc = "GrouperDesc";
public const string XmlCollector = "Collector";
public const string XmlDirectory = "CollectPath";
public const string XmlCollectPath = "CollectPath";
public const string XmlCollectorType = "CollectType";
public const string XmlAddressRule = "AddressRule";
public const string XmlPackRule = "PackRule";
public const string XmlFilterRule = "FilterRule";
public const string XmlNotWriteToAssetList = "NotWriteToAssetList";
public const string XmlAssetTags = "AssetTags";
/// <summary>
/// 导入XML配置表
/// </summary>
@@ -81,25 +80,25 @@ namespace YooAsset.Editor
foreach (var collectorNode in collectorNodeList)
{
XmlElement collectorElement = collectorNode as XmlElement;
if (collectorElement.HasAttribute(XmlDirectory) == false)
throw new Exception($"Not found attribute {XmlDirectory} in {XmlCollector}");
if (collectorElement.HasAttribute(XmlCollectPath) == false)
throw new Exception($"Not found attribute {XmlCollectPath} in {XmlCollector}");
if (collectorElement.HasAttribute(XmlCollectorType) == false)
throw new Exception($"Not found attribute {XmlCollectorType} in {XmlCollector}");
if (collectorElement.HasAttribute(XmlAddressRule) == false)
throw new Exception($"Not found attribute {XmlAddressRule} in {XmlCollector}");
if (collectorElement.HasAttribute(XmlPackRule) == false)
throw new Exception($"Not found attribute {XmlPackRule} in {XmlCollector}");
if (collectorElement.HasAttribute(XmlFilterRule) == false)
throw new Exception($"Not found attribute {XmlFilterRule} in {XmlCollector}");
if (collectorElement.HasAttribute(XmlNotWriteToAssetList) == false)
throw new Exception($"Not found attribute {XmlNotWriteToAssetList} in {XmlCollector}");
if (collectorElement.HasAttribute(XmlAssetTags) == false)
throw new Exception($"Not found attribute {XmlAssetTags} in {XmlCollector}");
AssetBundleCollector collector = new AssetBundleCollector();
collector.CollectPath = collectorElement.GetAttribute(XmlDirectory);
collector.CollectPath = collectorElement.GetAttribute(XmlCollectPath);
collector.CollectorType = StringUtility.NameToEnum<ECollectorType>(collectorElement.GetAttribute(XmlCollectorType));
collector.AddressRuleName = collectorElement.GetAttribute(XmlAddressRule);
collector.PackRuleName = collectorElement.GetAttribute(XmlPackRule);
collector.FilterRuleName = collectorElement.GetAttribute(XmlFilterRule);
collector.NotWriteToAssetList = collectorElement.GetAttribute(XmlNotWriteToAssetList) == "True" ? true : false;
collector.AssetTags = collectorElement.GetAttribute(XmlAssetTags); ;
grouper.Collectors.Add(collector);
}
@@ -149,11 +148,11 @@ namespace YooAsset.Editor
foreach (var collector in grouper.Collectors)
{
var collectorElement = xmlDoc.CreateElement(XmlCollector);
collectorElement.SetAttribute(XmlDirectory, collector.CollectPath);
collectorElement.SetAttribute(XmlCollectPath, collector.CollectPath);
collectorElement.SetAttribute(XmlCollectorType, collector.CollectorType.ToString());
collectorElement.SetAttribute(XmlAddressRule, collector.AddressRuleName);
collectorElement.SetAttribute(XmlPackRule, collector.PackRuleName);
collectorElement.SetAttribute(XmlFilterRule, collector.FilterRuleName);
collectorElement.SetAttribute(XmlNotWriteToAssetList, collector.NotWriteToAssetList.ToString());
collectorElement.SetAttribute(XmlAssetTags, collector.AssetTags);
grouperElement.AppendChild(collectorElement);
}
@@ -6,45 +6,11 @@ using UnityEditor;
namespace YooAsset.Editor
{
public static class AssetBundleGrouperHelper
/// <summary>
/// 编辑器下运行时支持
/// </summary>
public static class AssetBundleGrouperRuntimeSupport
{
/// <summary>
/// 收集着色器的资源包名称
/// </summary>
public static string CollectShaderBundleName(string assetPath)
{
// 如果自动收集所有的着色器
if (AssetBundleGrouperSettingData.Setting.AutoCollectShaders)
{
System.Type assetType = AssetDatabase.GetMainAssetTypeAtPath(assetPath);
if (assetType == typeof(UnityEngine.Shader))
{
string bundleName = AssetBundleGrouperSettingData.Setting.ShadersBundleName;
return CorrectBundleName(bundleName, false);
}
}
return null;
}
/// <summary>
/// 修正资源包名称
/// </summary>
public static string CorrectBundleName(string bundleName, bool isRawBundle)
{
if (isRawBundle)
{
string fullName = $"{bundleName}.{YooAssetSettingsData.Setting.RawFileVariant}";
return EditorTools.GetRegularPath(fullName).ToLower();
}
else
{
string fullName = $"{bundleName}.{YooAssetSettingsData.Setting.AssetBundleFileVariant}";
return EditorTools.GetRegularPath(fullName).ToLower(); ;
}
}
#region 编辑器下运行时支持
private static readonly Dictionary<string, CollectAssetInfo> _locationDic = new Dictionary<string, CollectAssetInfo>(1000);
public static void InitEditorPlayMode(bool enableAddressable)
@@ -56,7 +22,7 @@ namespace YooAsset.Editor
var collectAssetList = AssetBundleGrouperSettingData.Setting.GetAllCollectAssets();
foreach (var collectAsset in collectAssetList)
{
if (collectAsset.NotWriteToAssetList)
if(collectAsset.CollectorType != ECollectorType.MainCollector)
continue;
string address = collectAsset.Address;
@@ -71,7 +37,7 @@ namespace YooAsset.Editor
var collectAssetList = AssetBundleGrouperSettingData.Setting.GetAllCollectAssets();
foreach (var collectAsset in collectAssetList)
{
if (collectAsset.NotWriteToAssetList)
if (collectAsset.CollectorType != ECollectorType.MainCollector)
continue;
// 添加原始路径
@@ -128,6 +94,5 @@ namespace YooAsset.Editor
UnityEngine.Debug.LogWarning($"Found illegal character in location : \"{location}\"");
}
}
#endregion
}
}
@@ -45,41 +45,39 @@ namespace YooAsset.Editor
/// </summary>
public List<CollectAssetInfo> GetAllCollectAssets()
{
Dictionary<string, string> adressTemper = new Dictionary<string, string>(10000);
Dictionary<string, CollectAssetInfo> result = new Dictionary<string, CollectAssetInfo>(10000);
// 收集打包资源
foreach (var grouper in Groupers)
{
var temper = grouper.GetAllCollectAssets();
foreach (var assetInfo in temper)
{
if (result.ContainsKey(assetInfo.AssetPath) == false)
{
result.Add(assetInfo.AssetPath, assetInfo);
}
else
{
throw new Exception($"The collecting asset file is existed : {assetInfo.AssetPath}");
}
throw new Exception($"The collecting asset file is existed : {assetInfo.AssetPath} in grouper setting.");
}
}
// 检测可寻址地址是否重复
if (EnableAddressable)
{
foreach (var collectInfo in result)
HashSet<string> adressTemper = new HashSet<string>();
foreach (var collectInfoPair in result)
{
string address = collectInfo.Value.Address;
if (adressTemper.ContainsKey(address) == false)
if (collectInfoPair.Value.CollectorType == ECollectorType.MainCollector)
{
adressTemper.Add(address, address);
}
else
{
throw new Exception($"The address is existed : {address}");
string address = collectInfoPair.Value.Address;
if (adressTemper.Contains(address) == false)
adressTemper.Add(address);
else
throw new Exception($"The address is existed : {address} in grouper setting.");
}
}
}
// 返回列表
return result.Values.ToList();
}
}
@@ -18,6 +18,7 @@ namespace YooAsset.Editor
window.minSize = new Vector2(800, 600);
}
private List<string> _collectorTypeList;
private List<string> _addressRuleList;
private List<string> _packRuleList;
private List<string> _filterRuleList;
@@ -38,6 +39,7 @@ namespace YooAsset.Editor
VisualElement root = this.rootVisualElement;
_collectorTypeList = new List<string>() { $"{nameof(ECollectorType.MainCollector)}", $"{nameof(ECollectorType.StaticCollector)}"};
_addressRuleList = AssetBundleGrouperSettingData.GetAddressRuleNames();
_packRuleList = AssetBundleGrouperSettingData.GetPackRuleNames();
_filterRuleList = AssetBundleGrouperSettingData.GetFilterRuleNames();
@@ -318,6 +320,13 @@ namespace YooAsset.Editor
label.style.width = 90;
elementBottom.Add(label);
}
{
var popupField = new PopupField<string>(_collectorTypeList, 0);
popupField.name = "PopupField0";
popupField.style.unityTextAlign = TextAnchor.MiddleLeft;
popupField.style.width = 150;
elementBottom.Add(popupField);
}
if (_enableAddressableToogle.value)
{
var popupField = new PopupField<string>(_addressRuleList, 0);
@@ -340,17 +349,6 @@ namespace YooAsset.Editor
popupField.style.width = 150;
elementBottom.Add(popupField);
}
{
var toggle = new Toggle();
toggle.name = "Toggle1";
toggle.label = "NotWriteToAssetList";
toggle.style.unityTextAlign = TextAnchor.MiddleLeft;
toggle.style.width = 150;
toggle.style.marginLeft = 20;
elementBottom.Add(toggle);
var label = toggle.Q<Label>();
label.style.minWidth = 130;
}
{
var textField = new TextField();
textField.name = "TextField1";
@@ -408,7 +406,7 @@ namespace YooAsset.Editor
RemoveCollectorBtn_clicked(collector);
};
// Collect Path
// Collector Path
var objectField1 = element.Q<ObjectField>("ObjectField1");
objectField1.SetValueWithoutNotify(collectObject);
objectField1.RegisterValueChangedCallback(evt =>
@@ -419,6 +417,16 @@ namespace YooAsset.Editor
RefreshFoldout(foldout, selectGrouper, collector);
});
// Collector Type
var popupField0 = element.Q<PopupField<string>>("PopupField0");
popupField0.index = GetCollectorTypeIndex(collector.CollectorType.ToString());
popupField0.RegisterValueChangedCallback(evt =>
{
collector.CollectorType = StringUtility.NameToEnum<ECollectorType>(evt.newValue);
AssetBundleGrouperSettingData.ModifyCollector(selectGrouper, collector);
RefreshFoldout(foldout, selectGrouper, collector);
});
// Address Rule
var popupField1 = element.Q<PopupField<string>>("PopupField1");
if (popupField1 != null)
@@ -452,16 +460,6 @@ namespace YooAsset.Editor
RefreshFoldout(foldout, selectGrouper, collector);
});
// NotWriteToAssetList
var toggle1 = element.Q<Toggle>("Toggle1");
toggle1.SetValueWithoutNotify(collector.NotWriteToAssetList);
toggle1.RegisterValueChangedCallback(evt =>
{
collector.NotWriteToAssetList = evt.newValue;
AssetBundleGrouperSettingData.ModifyCollector(selectGrouper, collector);
RefreshFoldout(foldout, selectGrouper, collector);
});
// Tags
var textFiled1 = element.Q<TextField>("TextField1");
textFiled1.SetValueWithoutNotify(collector.AssetTags);
@@ -476,7 +474,7 @@ namespace YooAsset.Editor
// 清空旧元素
foldout.Clear();
if (collector.IsValid() && collector.NotWriteToAssetList == false)
if (collector.IsValid() && collector.CollectorType == ECollectorType.MainCollector)
{
List<CollectAssetInfo> collectAssetInfos = null;
@@ -484,12 +482,12 @@ namespace YooAsset.Editor
{
collectAssetInfos = collector.GetAllCollectAssets(grouper);
}
catch(System.Exception e)
catch (System.Exception e)
{
Debug.LogError(e.ToString());
}
if(collectAssetInfos != null)
if (collectAssetInfos != null)
{
foreach (var collectAssetInfo in collectAssetInfos)
{
@@ -536,6 +534,15 @@ namespace YooAsset.Editor
FillCollectorViewData();
}
private int GetCollectorTypeIndex(string typeName)
{
for (int i = 0; i < _collectorTypeList.Count; i++)
{
if (_collectorTypeList[i] == typeName)
return i;
}
return 0;
}
private int GetAddressRuleIndex(string ruleName)
{
for (int i = 0; i < _addressRuleList.Count; i++)
@@ -5,6 +5,11 @@ namespace YooAsset.Editor
{
public class CollectAssetInfo
{
/// <summary>
/// 收集器类型
/// </summary>
public ECollectorType CollectorType { private set; get; }
/// <summary>
/// 资源包名称
/// </summary>
@@ -14,7 +19,7 @@ namespace YooAsset.Editor
/// 可寻址地址
/// </summary>
public string Address { private set; get; }
/// <summary>
/// 资源路径
/// </summary>
@@ -30,25 +35,20 @@ namespace YooAsset.Editor
/// </summary>
public bool IsRawAsset { private set; get; }
/// <summary>
/// 不写入资源列表
/// </summary>
public bool NotWriteToAssetList { private set; get; }
/// <summary>
/// 依赖的资源列表
/// </summary>
public List<string> DependAssets = new List<string>();
public CollectAssetInfo(string bundleName, string address, string assetPath, List<string> assetTags, bool isRawAsset, bool notWriteToAssetList)
public CollectAssetInfo(ECollectorType collectorType, string bundleName, string address, string assetPath, List<string> assetTags, bool isRawAsset)
{
CollectorType = collectorType;
BundleName = bundleName;
Address = address;
AssetPath = assetPath;
AssetTags = assetTags;
IsRawAsset = isRawAsset;
NotWriteToAssetList = notWriteToAssetList;
}
}
}
@@ -0,0 +1,23 @@
using System;
namespace YooAsset.Editor
{
[Serializable]
public enum ECollectorType
{
/// <summary>
/// 收集参与打包构建的资源对象,并全部写入到资源清单的资源列表里(可以通过代码加载)。
/// </summary>
MainCollector,
/// <summary>
/// 收集参与打包构建的资源对象,但不写入到资源清单的资源列表里(无法通过代码加载)。
/// </summary>
StaticCollector,
/// <summary>
/// 该收集器类型不能被设置
/// </summary>
None,
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4bbef9cb5c1a40b41a28a65df3449abe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: