This commit is contained in:
2026-04-30 19:33:16 +08:00
commit fb2252f688
556 changed files with 69721 additions and 0 deletions
@@ -0,0 +1,14 @@
namespace GeometryTD.UI
{
public sealed class GoodsItemContext : UIContext
{
public int GoodsIndex;
public string Title;
public string TypeText;
public string Description;
public string[] TagTexts;
public string PurchaseButtonText;
public bool CanPurchase;
public IconAreaContext IconAreaContext;
}
}
@@ -0,0 +1,8 @@
namespace GeometryTD.UI
{
public sealed class ShopFormContext : UIContext
{
public string GoldText;
public GoodsItemContext[] GoodsItems;
}
}
@@ -0,0 +1,59 @@
using GeometryTD.CustomUtility;
namespace GeometryTD.UI
{
public sealed partial class ShopFormController
{
private static ShopFormContext BuildContext(ShopFormRawData rawData)
{
GoodsItemContext[] goodsItemContexts = System.Array.Empty<GoodsItemContext>();
if (rawData?.GoodsItems != null && rawData.GoodsItems.Count > 0)
{
goodsItemContexts = new GoodsItemContext[rawData.GoodsItems.Count];
for (int i = 0; i < rawData.GoodsItems.Count; i++)
{
GoodsItemRawData item = rawData.GoodsItems[i];
goodsItemContexts[i] = new GoodsItemContext
{
GoodsIndex = item?.GoodsIndex ?? i,
Title = item?.Title ?? string.Empty,
TypeText = item?.TypeText ?? string.Empty,
Description = BuildDescription(item),
TagTexts = BuildTagTexts(item),
PurchaseButtonText = item != null && item.IsPurchased ? "已购买" : $"购买 {item?.Price ?? 0}",
CanPurchase = item != null && !item.IsPurchased,
IconAreaContext = item?.IconAreaContext
};
}
}
return new ShopFormContext
{
GoldText = $"金币: {rawData?.PlayerGold ?? 0}",
GoodsItems = goodsItemContexts
};
}
private static string BuildDescription(GoodsItemRawData rawData)
{
string baseDescription = rawData?.Description ?? string.Empty;
string tagDescription = TagDisplayUtility.BuildTagDescriptionText(rawData?.Tags);
if (string.IsNullOrWhiteSpace(tagDescription))
{
return baseDescription;
}
if (string.IsNullOrWhiteSpace(baseDescription))
{
return tagDescription;
}
return $"{baseDescription}\n{tagDescription}";
}
private static string[] BuildTagTexts(GoodsItemRawData rawData)
{
return TagDisplayUtility.BuildTagTexts(rawData?.Tags);
}
}
}
@@ -0,0 +1,137 @@
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using GameFramework.Event;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public sealed partial class ShopFormController : UIFormControllerCommonBase<ShopFormContext, ShopForm>
{
private ShopFormUseCase _useCase;
protected override UIFormType UIFormTypeId => UIFormType.ShopForm;
protected override void RefreshUI(ShopForm form, ShopFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(ShopPurchaseRequestedEventArgs.EventId, OnPurchaseRequested);
GameEntry.Event.Subscribe(ShopExitRequestedEventArgs.EventId, OnExitRequested);
GameEntry.Event.Subscribe(ShopInventoryRequestedEventArgs.EventId, OnInventoryRequested);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(ShopPurchaseRequestedEventArgs.EventId, OnPurchaseRequested);
GameEntry.Event.Unsubscribe(ShopExitRequestedEventArgs.EventId, OnExitRequested);
GameEntry.Event.Unsubscribe(ShopInventoryRequestedEventArgs.EventId, OnInventoryRequested);
}
public override int? OpenUI(object userData = null)
{
if (userData is ShopFormContext context)
{
return OpenUIInternal(context);
}
if (userData is ShopFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("ShopFormController.OpenUI() userData type is invalid.");
return null;
}
if (_useCase == null)
{
Log.Error("ShopFormController.OpenUI() useCase is null.");
return null;
}
ShopFormRawData rawData = _useCase.CreateInitialModel();
return OpenUI(rawData);
}
public int? OpenUI(ShopFormRawData rawData)
{
ShopFormContext context = BuildContext(rawData);
return OpenUIInternal(context);
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is ShopFormUseCase shopFormUseCase))
{
Log.Error("ShopFormController.BindUseCase() useCase is invalid.");
return;
}
_useCase = shopFormUseCase;
}
private void OnPurchaseRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is ShopPurchaseRequestedEventArgs args) || _useCase == null)
{
return;
}
if (!_useCase.TryPurchase(args.GoodsIndex, out ShopFormRawData rawData))
{
return;
}
SetContext(BuildContext(rawData));
RefreshCurrentUI();
}
private void OnExitRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is ShopExitRequestedEventArgs))
{
return;
}
GameEntry.ShopNode.EndShop();
}
private void OnInventoryRequested(object sender, GameEventArgs e)
{
if (!IsEventFromCurrentForm(sender) || !(e is ShopInventoryRequestedEventArgs))
{
return;
}
GameEntry.UIRouter.OpenUI(UIFormType.RepoForm);
}
private bool IsEventFromCurrentForm(object sender)
{
if (Form == null)
{
return false;
}
if (ReferenceEquals(sender, Form))
{
return true;
}
if (sender is Component component)
{
ShopForm ownerForm = component.GetComponentInParent<ShopForm>();
return ownerForm == Form;
}
return false;
}
}
}
@@ -0,0 +1,17 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public sealed class GoodsItemRawData
{
public int GoodsIndex;
public string Title;
public string TypeText;
public string Description;
public int Price;
public TagType[] Tags;
public IconAreaContext IconAreaContext;
public TowerCompItemData SourceItem;
public bool IsPurchased;
}
}
@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace GeometryTD.UI
{
public sealed class ShopFormRawData
{
public int PlayerGold;
public List<GoodsItemRawData> GoodsItems;
}
}
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public sealed class ShopFormUseCase : IUIUseCase
{
private const int GoodsCount = 4;
private readonly List<GoodsItemRawData> _currentGoods = new List<GoodsItemRawData>(GoodsCount);
public bool PrepareForOpen(int runSeed = 0, int sequenceIndex = -1)
{
if (GameEntry.InventoryGeneration == null)
{
Log.Warning("ShopFormUseCase.PrepareForOpen() inventory generation component is null.");
return false;
}
_currentGoods.Clear();
_currentGoods.AddRange(GameEntry.InventoryGeneration.BuildShopGoods(GoodsCount, runSeed, sequenceIndex));
return _currentGoods.Count == GoodsCount;
}
public ShopFormRawData CreateInitialModel()
{
return new ShopFormRawData
{
PlayerGold = GameEntry.PlayerInventory != null ? GameEntry.PlayerInventory.Gold : 0,
GoodsItems = new List<GoodsItemRawData>(_currentGoods)
};
}
public bool TryPurchase(int goodsIndex, out ShopFormRawData updatedRawData)
{
updatedRawData = null;
if (goodsIndex < 0 || goodsIndex >= _currentGoods.Count)
{
Log.Warning("ShopFormUseCase.TryPurchase() goods index is invalid: {0}", goodsIndex);
return false;
}
if (GameEntry.PlayerInventory == null)
{
Log.Warning("ShopFormUseCase.TryPurchase() player inventory is null.");
return false;
}
GoodsItemRawData goodsItem = _currentGoods[goodsIndex];
if (goodsItem == null || goodsItem.SourceItem == null)
{
return false;
}
if (goodsItem.IsPurchased)
{
Log.Warning("ShopFormUseCase.TryPurchase() goods item {0} already purchased.", goodsIndex);
return false;
}
if (!GameEntry.PlayerInventory.TryPurchaseComponent(goodsItem.SourceItem, goodsItem.Price))
{
Log.Warning("ShopFormUseCase.TryPurchase() failed. Purchase command was rejected for goods item {0}.", goodsIndex);
return false;
}
goodsItem.IsPurchased = true;
updatedRawData = CreateInitialModel();
return true;
}
}
}
+195
View File
@@ -0,0 +1,195 @@
using GeometryTD.CustomEvent;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace GeometryTD.UI
{
public class GoodsItem : MonoBehaviour
{
[SerializeField] private IconArea _iconArea;
[SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _typeText;
[SerializeField] private TMP_Text _descriptionText;
[SerializeField] private RectTransform _descriptionScrollView;
[SerializeField] private RectTransform _descriptionContent;
[SerializeField] private float _maxDescriptionViewportHeight = 0f;
[SerializeField] private Transform _tagsParent;
[SerializeField] private TMP_Text _purchaseButtonText;
[SerializeField] private GameObject _tagItemPrefab;
[SerializeField] private int _index;
private GoodsItemContext _context;
public void OnInit(GoodsItemContext context)
{
_context = context;
_index = context?.GoodsIndex ?? -1;
_iconArea?.OnInit(context?.IconAreaContext ?? new IconAreaContext());
if (_titleText != null)
{
_titleText.text = context?.Title ?? string.Empty;
}
if (_typeText != null)
{
_typeText.text = context?.TypeText ?? string.Empty;
}
if (_descriptionText != null)
{
_descriptionText.text = context?.Description ?? string.Empty;
}
ResizeDescriptionArea();
if (_purchaseButtonText != null)
{
_purchaseButtonText.text = context?.PurchaseButtonText ?? string.Empty;
}
RefreshTags(context?.TagTexts);
}
public void OnReset()
{
_context = null;
_index = -1;
_iconArea?.OnReset();
if (_titleText != null)
{
_titleText.text = string.Empty;
}
if (_typeText != null)
{
_typeText.text = string.Empty;
}
if (_descriptionText != null)
{
_descriptionText.text = string.Empty;
}
ResetDescriptionScroll();
if (_purchaseButtonText != null)
{
_purchaseButtonText.text = string.Empty;
}
ClearTags();
}
public void OnPurchaseButtonClick()
{
if (_context == null || !_context.CanPurchase || _index < 0)
{
return;
}
GameEntry.Event.Fire(this, ShopPurchaseRequestedEventArgs.Create(_index));
}
private void RefreshTags(string[] tagTexts)
{
ClearTags();
if (_tagsParent == null || _tagItemPrefab == null || tagTexts == null)
{
return;
}
for (int i = 0; i < tagTexts.Length; i++)
{
if (string.IsNullOrEmpty(tagTexts[i]))
{
continue;
}
GameObject tagItemObject = Instantiate(_tagItemPrefab, _tagsParent);
TagItem tagItem = tagItemObject.GetComponent<TagItem>();
if (tagItem != null)
{
tagItem.OnInit(new TagItemContext
{
TagName = tagTexts[i]
});
}
}
}
private void ResizeDescriptionArea()
{
if (_descriptionText == null)
{
return;
}
_descriptionText.ForceMeshUpdate();
float descriptionHeight = Mathf.Max(0f, _descriptionText.preferredHeight);
SetRectHeight(_descriptionText.rectTransform, descriptionHeight);
if (_descriptionContent != null)
{
SetRectHeight(_descriptionContent, descriptionHeight);
}
if (_descriptionScrollView != null)
{
float viewportHeight = _maxDescriptionViewportHeight > 0f
? Mathf.Min(descriptionHeight, _maxDescriptionViewportHeight)
: descriptionHeight;
SetRectHeight(_descriptionScrollView, viewportHeight);
}
ResetDescriptionScroll();
}
private void ResetDescriptionScroll()
{
if (_descriptionScrollView != null &&
_descriptionScrollView.TryGetComponent(out ScrollRect scrollRect))
{
scrollRect.verticalNormalizedPosition = 1f;
}
}
private static void SetRectHeight(RectTransform rectTransform, float height)
{
if (rectTransform == null)
{
return;
}
Vector2 size = rectTransform.sizeDelta;
size.y = height;
rectTransform.sizeDelta = size;
}
private void ClearTags()
{
if (_tagsParent == null)
{
return;
}
for (int i = _tagsParent.childCount - 1; i >= 0; i--)
{
Destroy(_tagsParent.GetChild(i).gameObject);
}
}
}
}
+94
View File
@@ -0,0 +1,94 @@
using GeometryTD.CustomEvent;
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class ShopForm : UGuiForm
{
[SerializeField] private TMP_Text _goldText;
[SerializeField] private GoodsItem[] _goldItems;
private ShopFormContext _context;
public void RefreshUI(ShopFormContext context)
{
_context = context;
if (_goldText != null)
{
_goldText.text = context?.GoldText ?? string.Empty;
}
RefreshGoodsItems(context?.GoodsItems);
}
public void OnInventoryButtonClick()
{
GameEntry.Event.Fire(this, ShopInventoryRequestedEventArgs.Create());
}
public void OnExitButtonClick()
{
GameEntry.Event.Fire(this, ShopExitRequestedEventArgs.Create());
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is ShopFormContext context)
{
RefreshUI(context);
return;
}
Log.Warning("ShopForm requires ShopFormContext as userData.");
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
if (_goldText != null)
{
_goldText.text = string.Empty;
}
if (_goldItems != null)
{
foreach (var item in _goldItems)
{
item?.OnReset();
}
}
base.OnClose(isShutdown, userData);
}
private void RefreshGoodsItems(GoodsItemContext[] goodsItems)
{
if (_goldItems == null)
{
return;
}
for (int i = 0; i < _goldItems.Length; i++)
{
GoodsItem goodsItem = _goldItems[i];
if (goodsItem == null)
{
continue;
}
if (goodsItems != null && i < goodsItems.Length && goodsItems[i] != null)
{
goodsItem.OnInit(goodsItems[i]);
}
else
{
goodsItem.OnReset();
}
}
}
}
}