迁移剩下的 UI 到 Serialization 方式

This commit is contained in:
2026-09-14 17:19:31 +08:00
parent 091a29db63
commit b628a8537c
107 changed files with 4147 additions and 3680 deletions
@@ -0,0 +1,55 @@
using Cysharp.Threading.Tasks;
using SepCore.CustomUtility;
using SepCore.Definition;
using SepCore.Entity;
using UnityEngine;
namespace SepCore.UI
{
public class GoodsItemContext : UIContext
{
public string Name;
public Rarity Rarity;
public Sprite Icon;
public ItemType ItemType;
public string Description;
public int Price;
public GoodsItemContext()
{
Name = string.Empty;
Description = string.Empty;
Rarity = Rarity.White;
Price = 0;
Icon = null;
ItemType = ItemType.Weapon;
}
public GoodsItemContext(GoodsItemRawData rawData) : this()
{
Price = rawData.Price;
Rarity = rawData.Rarity;
ItemType = rawData.ItemType;
if (ItemType == ItemType.Weapon)
{
if (rawData.DataRow is WeaponConfig weaponConfig)
{
Name = weaponConfig.WeaponType.ToString();
Description = ItemDescUtility.CreateWeaponDescription(weaponConfig);
}
else if (rawData.DataRow is WeaponData weaponData)
{
Name = weaponData.Title;
Description = ItemDescUtility.CreateWeaponDescription(weaponData);
}
}
else
{
var prop = (PropConfig)rawData.DataRow;
Name = prop.Name;
Description = ItemDescUtility.CreatePropDescription(prop.Modifiers);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9d0b321b02be5f41a27cc0a8bf84e34
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,35 @@
using System.Collections.Generic;
namespace SepCore.UI
{
public class ShopContext : UIContext
{
public int CurrentLevel;
public int RefreshPrice;
public int PlayerCoin;
public List<GoodsItemContext> GoodsItems;
public DisplayListContext PropListContext;
public DisplayListContext WeaponListContext;
// 控制字段
public bool NeedRefreshGoodsItems;
public bool NeedRefreshPlayerCoin;
public bool NeedRefreshWeaponList;
public bool NeedRefreshPropList;
public bool NeedRefreshPrice;
public ShopContext()
{
CurrentLevel = 0;
RefreshPrice = 0;
PlayerCoin = 0;
}
public ShopContext(ShopRawData rawData)
{
CurrentLevel = rawData.CurrentLevel;
RefreshPrice = rawData.RefreshPrice;
PlayerCoin = rawData.PlayerCoin;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b0ef5183d2f87a54793b86d848e6524a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,136 @@
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using SepCore.AsyncTask;
using SepCore.Definition;
using SepCore.Entity;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public enum DisplayListAreaType : byte
{
None = 0,
Prop = 1,
Weapon = 2
}
public partial class ShopController
{
private async UniTask<ShopContext> BuildContext(ShopRawData rawData)
{
if (rawData == null)
{
Log.Error("ShopFormController.BuildContext() rawData is null.");
return null;
}
_rawData = rawData;
List<GoodsItemContext> goodsItems = new List<GoodsItemContext>();
foreach (var item in rawData.GoodsItems)
{
var context = await CreateGoodsItemContextAsync(item);
goodsItems.Add(context);
}
return new ShopContext(rawData)
{
GoodsItems = goodsItems,
PropListContext =
BuildDisplayListContext(DisplayListAreaType.Prop, rawData.PropItems, rawData.PropMaxCount),
WeaponListContext = BuildDisplayListContext(DisplayListAreaType.Weapon, rawData.WeaponItems,
rawData.WeaponMaxCount),
NeedRefreshGoodsItems = true,
NeedRefreshPlayerCoin = true,
NeedRefreshPropList = true,
NeedRefreshWeaponList = true,
NeedRefreshPrice = true
};
}
private static DisplayListContext BuildDisplayListContext(DisplayListAreaType listType,
IReadOnlyList<object> items, int maxCount)
{
DisplayItemContext[] itemContexts = new DisplayItemContext[items.Count];
switch (listType)
{
case DisplayListAreaType.Weapon:
IReadOnlyList<WeaponBase> weapons = (IReadOnlyList<WeaponBase>)items;
for (int i = 0; i < weapons.Count; i++)
{
itemContexts[i] = BuildWeaponItem(weapons[i]);
}
break;
case DisplayListAreaType.Prop:
IReadOnlyList<PropItem> propItems = (IReadOnlyList<PropItem>)items;
for (int i = 0; i < propItems.Count; i++)
{
itemContexts[i] = BuildPropItem(propItems[i]);
}
break;
}
return new DisplayListContext
{
CurrentCount = itemContexts.Length,
MaxCount = maxCount,
ItemContexts = itemContexts
};
}
private static DisplayItemContext BuildPropItem(PropItem propItem)
{
return new DisplayItemContext
{
IconAssetName = propItem.IconAssetName,
Rarity = propItem.Rarity,
IsWeapon = false
};
}
private static DisplayItemContext BuildWeaponItem(WeaponBase weaponBase)
{
return new DisplayItemContext
{
IconAssetName = weaponBase.WeaponData.IconAssetName,
Rarity = weaponBase.WeaponData.Rarity,
IsWeapon = true
};
}
private static void AppendDisplayItemContext(DisplayListContext listContext, DisplayItemContext newItem)
{
int oldCount = listContext.ItemContexts.Length;
DisplayItemContext[] newContexts = new DisplayItemContext[oldCount + 1];
if (oldCount > 0)
{
System.Array.Copy(listContext.ItemContexts, newContexts, oldCount);
}
newContexts[oldCount] = newItem;
listContext.ItemContexts = newContexts;
listContext.CurrentCount = oldCount + 1;
}
private async UniTask<GoodsItemContext> CreateGoodsItemContextAsync(GoodsItemRawData rawData,
float timeout = 30f)
{
var context = new GoodsItemContext(rawData);
context.Icon = context.ItemType switch
{
ItemType.Weapon => await SpriteLoader.LoadSpriteByKeyAsync(rawData.DataRow switch
{
WeaponConfig weaponConfig => weaponConfig.Sprite,
WeaponData weaponData => weaponData.IconAssetName,
_ => string.Empty
}),
ItemType.Prop => await SpriteLoader.LoadSpriteByKeyAsync(((PropConfig)rawData.DataRow).Sprite),
_ => null
};
return context;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9d035632a30d29a4db6714cdbb9a391f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,374 @@
using Cysharp.Threading.Tasks;
using SepCore.Event;
using SepCore.Definition;
using SepCore.Entity;
using GameFramework.Event;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public partial class ShopController : UIControllerBase<ShopContext, ShopForm>
{
private ShopUseCase _useCase;
private ShopRawData _rawData;
private bool _tooltipLocked = false;
protected override UIFormType UIFormType => UIFormType.ShopForm;
protected override void RefreshUI(ShopForm form, ShopContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(ShopRefreshEventArgs.EventId, ShopRefresh);
GameEntry.Event.Subscribe(ShopPurchaseEventArgs.EventId, ShopPurchase);
GameEntry.Event.Subscribe(ShopContinueEventArgs.EventId, ShopContinue);
GameEntry.Event.Subscribe(DisplayItemHoverEventArgs.EventId, DisplayItemHover);
GameEntry.Event.Subscribe(DisplayItemLockEventArgs.EventId, DisplayItemLock);
GameEntry.Event.Subscribe(DisplayItemHideEventArgs.EventId, DisplayItemHide);
GameEntry.Event.Subscribe(PlayerCoinChangeEventArgs.EventId, PlayerCoinChange);
GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormComplete);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(ShopRefreshEventArgs.EventId, ShopRefresh);
GameEntry.Event.Unsubscribe(ShopPurchaseEventArgs.EventId, ShopPurchase);
GameEntry.Event.Unsubscribe(ShopContinueEventArgs.EventId, ShopContinue);
GameEntry.Event.Unsubscribe(DisplayItemHoverEventArgs.EventId, DisplayItemHover);
GameEntry.Event.Unsubscribe(DisplayItemLockEventArgs.EventId, DisplayItemLock);
GameEntry.Event.Unsubscribe(DisplayItemHideEventArgs.EventId, DisplayItemHide);
GameEntry.Event.Unsubscribe(PlayerCoinChangeEventArgs.EventId, PlayerCoinChange);
GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OpenUIFormComplete);
}
#region UI Methods
public override async UniTask CloseUIAsync(object userData = null, float timeout = 30f)
{
GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget();
_rawData = null;
_tooltipLocked = false;
await base.CloseUIAsync(userData, timeout);
}
public async UniTask<int?> OpenUIAsync(ShopRawData rawData, float timeout = 30f)
{
ShopContext context = await BuildContext(rawData);
return await OpenFormAsync(context, timeout);
}
public override async UniTask OpenUIAsync(object userData = null, float timeout = 30f)
{
if (userData is ShopRawData rawData)
{
await OpenUIAsync(rawData, timeout);
return;
}
if (userData != null)
{
Log.Warning("ShopController.OpenUIAsync() userData type is invalid.");
return;
}
if (_useCase == null)
{
Log.Error("ShopController.OpenUIAsync() useCase is null.");
return;
}
ShopRawData initialRawData = _useCase.CreateInitialModel();
await OpenUIAsync(initialRawData, timeout);
}
public override void BindUseCase(IUIUseCase useCase)
{
if (useCase is not ShopUseCase shopFormUseCase)
{
Log.Error("ShopForm.BindUseCase() useCase is invalid.");
return;
}
_useCase = shopFormUseCase;
}
#endregion
#region Service
private async UniTask RefreshGoodsItems(ShopRefreshResult result)
{
for (int i = 0; i < result.GoodsItems.Count; i++)
{
if (i < Context.GoodsItems.Count)
{
Context.GoodsItems[i] = await CreateGoodsItemContextAsync(result.GoodsItems[i]);
}
else Context.GoodsItems.Add(await CreateGoodsItemContextAsync(result.GoodsItems[i]));
}
if (Context.GoodsItems.Count != result.GoodsItems.Count)
{
int count = Context.GoodsItems.Count - result.GoodsItems.Count;
Context.GoodsItems.RemoveRange(result.GoodsItems.Count, count);
}
Context.RefreshPrice = result.RefreshPrice;
Context.NeedRefreshGoodsItems = true;
Context.NeedRefreshPrice = true;
RefreshUI(Form, Context);
}
private void ApplyGoodsPurchased(ShopPurchaseResult result)
{
if (result.GoodsIndex >= 0 && result.GoodsIndex < Context.GoodsItems.Count)
{
Context.GoodsItems[result.GoodsIndex] = null;
}
DisplayItemContext context = new DisplayItemContext(result.DisplayItem);
if (result.DisplayItem.IsWeapon)
{
AppendDisplayItemContext(Context.WeaponListContext, context);
Context.NeedRefreshWeaponList = true;
}
else
{
AppendDisplayItemContext(Context.PropListContext, context);
Context.NeedRefreshPropList = true;
}
Context.NeedRefreshGoodsItems = true;
RefreshUI(Form, Context);
}
private bool TryGetWeaponInfoRawData(int index, Vector3 targetPos, out ItemTooltipRawData rawData)
{
rawData = null;
if (_rawData?.WeaponItems == null)
{
Log.Error("ShopFormController.TryGetWeaponInfoRawData() WeaponItems is null.");
return false;
}
if (Context == null)
{
Log.Error("ShopFormController.TryGetWeaponInfoRawData() Context is null.");
return false;
}
if (index < 0 || index >= _rawData.WeaponItems.Count)
{
Log.Error($"ShopFormController.TryGetWeaponInfoRawData() invalid weapon index: {index}.");
return false;
}
WeaponBase weapon = _rawData.WeaponItems[index];
if (weapon?.WeaponData == null)
{
Log.Error($"ShopFormController.TryGetWeaponInfoRawData() weapon data is null at index {index}.");
return false;
}
rawData = new ItemTooltipRawData
{
TargetPos = targetPos,
Index = index,
WeaponData = weapon.WeaponData,
Rarity = weapon.WeaponData.Rarity,
ItemType = ItemType.Weapon,
Price = _useCase.CalculateRecyclePrice(weapon.WeaponData.Price),
OnRecycle = RecycleWeapon,
};
return true;
}
private bool TryGetPropInfoRawData(int index, Vector3 targetPos, out ItemTooltipRawData rawData)
{
rawData = null;
if (_rawData?.PropItems == null)
{
Log.Error("ShopFormController.TryGetPropInfoRawData() PropItems is null.");
return false;
}
if (index < 0 || index >= _rawData.PropItems.Count)
{
Log.Error($"ShopFormController.TryGetPropInfoRawData() invalid prop index: {index}.");
return false;
}
PropItem propItem = _rawData.PropItems[index];
if (propItem == null)
{
Log.Error($"ShopFormController.TryGetPropInfoRawData() prop item is null at index {index}.");
return false;
}
rawData = new ItemTooltipRawData
{
TargetPos = targetPos,
Index = index,
PropData = propItem,
Rarity = propItem.Rarity,
ItemType = ItemType.Prop,
Price = 0
};
return true;
}
private void RecycleWeapon(int index, int price)
{
if (_useCase == null || Context == null)
{
Log.Error("ShopFormController.RecycleWeapon() controller state is invalid.");
return;
}
bool success = _useCase.TryRecycleWeapon(index, price);
if (!success)
{
return;
}
int currentCount = Mathf.Max(0, Context.WeaponListContext.CurrentCount - 1);
Context.WeaponListContext.CurrentCount = currentCount;
Context.NeedRefreshWeaponList = true;
RefreshUI(Form, Context);
GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget();
}
#endregion
#region Event Handlers
private async void ShopRefresh(object sender, GameEventArgs e)
{
if (e is not ShopRefreshEventArgs args)
{
return;
}
ShopRefreshResult result = _useCase.TryRefresh(args.Cost);
if (result == null)
{
return;
}
await RefreshGoodsItems(result);
}
private void ShopPurchase(object sender, GameEventArgs e)
{
if (e is not ShopPurchaseEventArgs args)
{
return;
}
OnPurchaseRequestAsync(args).Forget();
}
private async UniTaskVoid OnPurchaseRequestAsync(ShopPurchaseEventArgs args)
{
ShopPurchaseResult result = await _useCase.TryPurchaseAsync(args.GoodsIndex);
if (result == null)
{
return;
}
ApplyGoodsPurchased(result);
}
private void ShopContinue(object sender, GameEventArgs e)
{
if (e is not ShopContinueEventArgs)
{
return;
}
_useCase.Continue();
}
private void DisplayItemHover(object sender, GameEventArgs e)
{
if (e is not DisplayItemHoverEventArgs args)
{
return;
}
if (_rawData == null)
{
Log.Error("ShopFormController.OnDisplayItemHover() _rawData is null.");
return;
}
ItemTooltipRawData rawData;
bool success = args.IsWeapon
? TryGetWeaponInfoRawData(args.Index, args.TargetPos, out rawData)
: TryGetPropInfoRawData(args.Index, args.TargetPos, out rawData);
if (!success)
{
return;
}
_tooltipLocked = false;
GameEntry.UIRouter.OpenUIAsync(UIFormType.ItemTooltipForm, rawData).Forget();
}
private void DisplayItemLock(object sender, GameEventArgs e)
{
if (e is not DisplayItemLockEventArgs)
{
return;
}
_tooltipLocked = true;
}
private void DisplayItemHide(object sender, GameEventArgs e)
{
if (e is not DisplayItemHideEventArgs args)
{
return;
}
if (_tooltipLocked)
{
return;
}
GameEntry.UIRouter.CloseUIAsync(UIFormType.ItemTooltipForm).Forget();
}
private void PlayerCoinChange(object sender, GameEventArgs e)
{
if (e is not PlayerCoinChangeEventArgs args) return;
Context.PlayerCoin = args.CoinCount;
Context.NeedRefreshPlayerCoin = true;
RefreshUI(Form, Context);
}
private void OpenUIFormComplete(object sender, GameEventArgs e)
{
if (e is not OpenUIFormSuccessEventArgs ne) return;
if (ne.UIForm.Logic is ItemTooltipForm)
{
_tooltipLocked = false;
}
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be57ace1e7b07be429632ea49bcab6c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,134 @@
using System.Collections.Generic;
using SepCore.Event;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public partial class ShopForm : UGuiForm
{
private readonly List<ShopGoodsItem> _goodsItems = new List<ShopGoodsItem>();
private ShopContext _context;
private bool _listenersBound;
public void RefreshUI(ShopContext context)
{
EnsureButtonListeners();
_context = context;
if (context.NeedRefreshGoodsItems)
{
View.titleFormatText.Set(context.CurrentLevel);
View.continueButtonFormatText.Set(context.CurrentLevel + 1);
RefreshGoodsItems(context.GoodsItems);
context.NeedRefreshGoodsItems = false;
}
if (context.NeedRefreshPrice)
{
View.refreshButtonFormatText.Set(context.RefreshPrice);
context.NeedRefreshPrice = false;
}
if (context.NeedRefreshPlayerCoin)
{
View.playerCoinFormatText.Set(context.PlayerCoin);
context.NeedRefreshPlayerCoin = false;
}
if (context.NeedRefreshPropList)
{
View.propList.OnInit(context.PropListContext);
context.NeedRefreshPropList = false;
}
if (context.NeedRefreshWeaponList)
{
View.weaponList.OnInit(context.WeaponListContext);
context.NeedRefreshWeaponList = false;
}
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is not ShopContext context)
{
Log.Error("ShopForm requires ShopContext as userData.");
return;
}
RefreshUI(context);
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
for (int i = 0; i < _goodsItems.Count; i++)
{
_goodsItems[i].OnReset();
}
View.propList.OnReset();
View.weaponList.OnReset();
base.OnClose(isShutdown, userData);
}
private void EnsureButtonListeners()
{
if (_listenersBound)
{
return;
}
_listenersBound = true;
View.continueButton.onClick.AddListener(OnContinueButtonClick);
View.refreshButton.onClick.AddListener(OnRefreshButtonClick);
}
private void RefreshGoodsItems(List<GoodsItemContext> goodsItems)
{
EnsureGoodsItemCount(goodsItems.Count);
for (int i = 0; i < _goodsItems.Count; i++)
{
ShopGoodsItem item = _goodsItems[i];
if (i < goodsItems.Count && goodsItems[i] != null)
{
item.Init(goodsItems[i], i);
item.gameObject.SetActive(true);
}
else
{
item.OnReset();
item.gameObject.SetActive(false);
}
}
}
private void EnsureGoodsItemCount(int count)
{
while (_goodsItems.Count < count)
{
ShopGoodsItem item = Instantiate(View.goodsItemTemplate, View.GoodsItemRoot);
_goodsItems.Add(item);
}
}
public void OnContinueButtonClick()
{
GameEntry.Event.Fire(this, ShopContinueEventArgs.Create());
}
public void OnRefreshButtonClick()
{
GameEntry.Event.Fire(this, ShopRefreshEventArgs.Create(_context.RefreshPrice));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a6f22432e5701804d9a78d0c07f4c334
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class ShopForm : UGuiForm
{
[SerializeField] private ShopView _view = null;
public ShopView View => _view;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b261ad08be35724a83c21227db57e6b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,71 @@
using System;
using SepCore.Event;
using TMPro;
using UnityEngine;
namespace SepCore.UI
{
public class ShopGoodsItem : MonoBehaviour
{
[SerializeField] private IconArea _iconArea;
[SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _typeText;
[SerializeField] private TMP_Text _descriptionText;
[SerializeField] private TMP_Text _costText;
private int _index = -1;
private bool _listenersBound;
public void Init(GoodsItemContext data, int index)
{
EnsureListeners();
_index = index;
_iconArea.OnInit(data.Icon, data.Rarity);
_titleText.text = data.Name;
_typeText.text = data.ItemType switch
{
ItemType.None => "error",
ItemType.Weapon => "武器",
ItemType.Prop => "道具",
_ => throw new ArgumentOutOfRangeException()
};
_descriptionText.text = data.Description;
_costText.text = $"{data.Price} <sprite name=\"coin\" index=0>";
}
public void OnReset()
{
_index = -1;
}
private void EnsureListeners()
{
if (_listenersBound)
{
return;
}
_listenersBound = true;
CommonButton button = GetComponentInChildren<CommonButton>(true);
button.onClick.AddListener(OnPurchaseClick);
}
private void OnPurchaseClick()
{
if (_index < 0)
{
return;
}
GameEntry.Event.Fire(this, ShopPurchaseEventArgs.Create(_index));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 271f2a0727c521c4280eb2d47fd8de33
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,20 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class ShopView : MonoBehaviour
{
[SerializeField] public global::SepCore.UI.FormatTextUI titleFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI playerCoinFormatText;
[SerializeField] public global::SepCore.UI.CommonButton refreshButton;
[SerializeField] public global::SepCore.UI.FormatTextUI refreshButtonFormatText;
[SerializeField] public global::UnityEngine.RectTransform GoodsItemRoot;
[SerializeField] public global::SepCore.UI.ShopGoodsItem goodsItemTemplate;
[SerializeField] public global::SepCore.UI.CommonButton continueButton;
[SerializeField] public global::SepCore.UI.FormatTextUI continueButtonFormatText;
[SerializeField] public global::SepCore.UI.DisplayListForm weaponList;
[SerializeField] public global::SepCore.UI.DisplayListForm propList;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10a546b2f753668479061813a7f5781f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: