This commit is contained in:
huangjun
2026-08-30 22:25:23 +08:00
parent 92bf3e9097
commit 93a10a89c5
180 changed files with 15257 additions and 11604 deletions
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 9618a0e69480c40b383585e41cda54a1
guid: 4df8f90e8698941068b8d0dd7c3af46e
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,41 @@
using SepCore.Definition;
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
/// <summary>
/// 使用 Luban 格式化文本表(TbFormatText)驱动 TextMeshProUGUI 的组件。
/// 在 Inspector 上填写 Key,调用 Set 传入格式化参数即可更新文本。
/// </summary>
[RequireComponent(typeof(TextMeshProUGUI))]
public class FormatTextUI : MonoBehaviour
{
[SerializeField] private string key;
private TextMeshProUGUI _text;
private void Awake()
{
_text = GetComponent<TextMeshProUGUI>();
}
public void Set(params object[] args)
{
FormatText format = GameEntry.Luban.Tables.TbFormatText.GetOrDefault(key);
if (format == null)
{
Log.Warning("Can not find format text '{0}'.", key);
return;
}
_text.SetText(string.Format(format.Format, args));
}
public void Clear()
{
_text.SetText(string.Empty);
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ba271361c636f48edb5d7d8ba15a595c
guid: 6566a3a7cfd184008ab2282feea295f8
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -6,16 +6,8 @@ namespace SepCore.UI
[DisallowMultipleComponent]
public partial class CombatReadinessView : MonoBehaviour
{
[SerializeField] public global::TMPro.TextMeshProUGUI squadMemberText;
[SerializeField] public global::UnityEngine.RectTransform characterSlotRoot;
[SerializeField] public global::SepCore.UI.CharacterSlotItem characterSlotTemplate;
[SerializeField] public global::UnityEngine.UI.ToggleGroup difficultyTabsToggleGroup;
[SerializeField] public global::TMPro.TMP_InputField seedInput;
[SerializeField] public global::UnityEngine.UI.Button randomizeButton;
[SerializeField] public global::TMPro.TextMeshProUGUI partySizeText;
[SerializeField] public global::TMPro.TextMeshProUGUI mapIdText;
[SerializeField] public global::TMPro.TextMeshProUGUI timeLimitText;
[SerializeField] public global::UnityEngine.UI.Button beginRunButton;
[SerializeField] public global::TMPro.TextMeshProUGUI storageText;
[SerializeField] public global::SepCore.UI.SquadForm squadForm;
[SerializeField] public global::SepCore.UI.DeploymentForm deploymentForm;
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 233579b69c85c4f9db143fcf0368c36a
guid: e9d3ff59008964126bb0c8052f0626e0
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,13 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class DeploymentForm : UGuiForm
{
[SerializeField] private DeploymentView _view = null;
public DeploymentView View => _view;
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: f585fae70a14d446ba433f3244efded1
guid: 37901c3791a804ff6b35692ab2f67f04
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,19 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class DeploymentView : MonoBehaviour
{
[SerializeField] public global::UnityEngine.UI.Toggle tier1Toggle;
[SerializeField] public global::UnityEngine.UI.Toggle tier2Toggle;
[SerializeField] public global::UnityEngine.UI.Toggle tier3Toggle;
[SerializeField] public global::TMPro.TMP_InputField seedInput;
[SerializeField] public global::UnityEngine.UI.Button randomizeButton;
[SerializeField] public global::TMPro.TextMeshProUGUI partySizeText;
[SerializeField] public global::TMPro.TextMeshProUGUI mapIdText;
[SerializeField] public global::TMPro.TextMeshProUGUI timeLimitText;
[SerializeField] public global::UnityEngine.UI.Button beginRunButton;
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: bffe339530c484a4b83162e20f163ed8
guid: 0e9a8d6edf3794bc69f3b822e538ff96
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -0,0 +1,195 @@
using System.Collections.Generic;
using SepCore.Base;
using SepCore.Definition;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
/// <summary>
/// 物品列表界面逻辑(手写 partial,与自动生成的 InventoryForm.cs 合并)。
/// 支持按物品类型筛选(ALL / 战利品 / 装备 / 消耗品)。
/// 注意:本表单作为嵌套表单挂在 LobbyForm/WarehouseForm 下,UGF 生命周期(OnInit/OnOpen)
/// 不会被框架调用,事件绑定改为幂等式(EnsureListenersBound)。
/// </summary>
public partial class InventoryForm : UGuiForm
{
private bool _listenersBound = false;
private IReadOnlyList<ItemStack> _stacks = null;
private ItemType? _filterType = null;
private List<ItemStack> _visibleStacks = new List<ItemStack>();
private readonly List<WarehouseSlotItem> _slots = new List<WarehouseSlotItem>();
private void EnsureListenersBound()
{
if (_listenersBound)
{
return;
}
_listenersBound = true;
View.allToggle.onValueChanged.AddListener(OnAllToggleValueChanged);
View.lootToggle.onValueChanged.AddListener(OnLootToggleValueChanged);
View.equipmentToggle.onValueChanged.AddListener(OnEquipmentToggleValueChanged);
View.consumablesToggle.onValueChanged.AddListener(OnConsumablesToggleValueChanged);
}
private void OnAllToggleValueChanged(bool isOn)
{
if (isOn)
{
SetFilter(null);
}
}
private void OnLootToggleValueChanged(bool isOn)
{
if (isOn)
{
SetFilter(ItemType.Loot);
}
}
private void OnEquipmentToggleValueChanged(bool isOn)
{
if (isOn)
{
SetFilter(ItemType.Equipment);
}
}
private void OnConsumablesToggleValueChanged(bool isOn)
{
if (isOn)
{
SetFilter(ItemType.Consumable);
}
}
private void SetFilter(ItemType? filterType)
{
_filterType = filterType;
RebuildGrid();
}
/// <summary>
/// 设置列表数据并重建网格,保留当前筛选。
/// </summary>
public void RefreshList(IReadOnlyList<ItemStack> stacks)
{
EnsureListenersBound();
_stacks = stacks;
RebuildGrid();
}
private void RebuildGrid()
{
InventoryView inventoryView = View;
if (inventoryView == null || inventoryView.warehouseSlotRoot == null ||
inventoryView.warehouseSlotTemplate == null)
{
Log.Warning("InventoryForm is not fully configured.");
return;
}
int slotCount = GameEntry.Luban.Global.Data.WarehouseSlotCount;
if (slotCount <= 0)
{
Log.Warning("Warehouse slot count from global config is invalid: {0}.", slotCount);
return;
}
_visibleStacks = CollectVisibleStacks();
_slots.Clear();
WarehouseSlotItem template = inventoryView.warehouseSlotTemplate;
template.gameObject.SetActive(false);
for (int i = inventoryView.warehouseSlotRoot.childCount - 1; i >= 0; i--)
{
Transform child = inventoryView.warehouseSlotRoot.GetChild(i);
if (child == template.transform)
{
continue;
}
Destroy(child.gameObject);
}
for (int i = 0; i < slotCount; i++)
{
WarehouseSlotItem slot = Instantiate(template, inventoryView.warehouseSlotRoot);
slot.gameObject.SetActive(true);
slot.SetSlotId(i);
if (i < _visibleStacks.Count)
{
slot.SetItem(_visibleStacks[i]);
}
else
{
slot.SetEmpty();
}
_slots.Add(slot);
}
if (inventoryView.itemCountFormatText != null)
{
int usedSlotCount = _stacks != null ? _stacks.Count : 0;
inventoryView.itemCountFormatText.Set(usedSlotCount, slotCount);
}
}
/// <summary>
/// 按网格索引高亮对应格子,其余格子清除选中。
/// </summary>
public void SetSelectedSlot(int slotId)
{
for (int i = 0; i < _slots.Count; i++)
{
_slots[i].SetSelected(i == slotId);
}
}
/// <summary>
/// 获取指定网格索引对应的物品 ID;空格或越界返回 0。
/// </summary>
public int GetItemIdAtSlot(int slotId)
{
if (slotId < 0 || slotId >= _visibleStacks.Count)
{
return 0;
}
return _visibleStacks[slotId].itemId;
}
private List<ItemStack> CollectVisibleStacks()
{
List<ItemStack> visible = new List<ItemStack>();
if (_stacks == null)
{
return visible;
}
for (int i = 0; i < _stacks.Count; i++)
{
ItemStack stack = _stacks[i];
if (_filterType.HasValue)
{
ItemConfig config = GameEntry.Luban.Get<ItemConfig>(stack.itemId);
if (config == null || config.ItemType != _filterType.Value)
{
continue;
}
}
visible.Add(stack);
}
return visible;
}
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 4774c8ef6d24f489c8c5858aa014ff13
guid: 0452e5e692a1e42118fa2097e6575eaa
MonoImporter:
externalObjects: {}
serializedVersion: 2
@@ -6,10 +6,15 @@ namespace SepCore.UI
[DisallowMultipleComponent]
public partial class InventoryView : MonoBehaviour
{
[SerializeField] public global::UnityEngine.UI.Toggle allToggle;
[SerializeField] public global::UnityEngine.UI.Toggle lootToggle;
[SerializeField] public global::UnityEngine.UI.Toggle equipmentToggle;
[SerializeField] public global::UnityEngine.UI.Toggle consumablesToggle;
[SerializeField] public global::TMPro.TMP_InputField searchInput;
[SerializeField] public global::UnityEngine.UI.Button sortButton;
[SerializeField] public global::UnityEngine.UI.ScrollRect itemScroll;
[SerializeField] public global::UnityEngine.RectTransform warehouseSlotRoot;
[SerializeField] public global::SepCore.UI.WarehouseSlotItem warehouseSlotTemplate;
[SerializeField] public global::SepCore.UI.FormatTextUI itemCountFormatText;
}
}
@@ -1,13 +0,0 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class InventoryPanelForm : UGuiForm
{
[SerializeField] private InventoryPanelView _view = null;
public InventoryPanelView View => _view;
}
}
@@ -1,16 +0,0 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class InventoryPanelView : MonoBehaviour
{
[SerializeField] public global::UnityEngine.UI.InputField searchInput;
[SerializeField] public global::UnityEngine.UI.Button sortButton;
[SerializeField] public global::UnityEngine.UI.ScrollRect itemScroll;
[SerializeField] public global::UnityEngine.RectTransform warehouseSlotRoot;
[SerializeField] public global::SepCore.UI.WarehouseSlotItem warehouseSlotTemplate;
[SerializeField] public global::UnityEngine.UI.Text itemCountText;
}
}
@@ -1,8 +1,8 @@
using System.Collections;
using System.Collections.Generic;
using SepCore.Definition;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
@@ -11,10 +11,57 @@ namespace SepCore.UI
[SerializeField] private Image bg;
[SerializeField] private Image icon;
[SerializeField] private TextMeshProUGUI characterName;
[SerializeField] private TextMeshProUGUI hpText;
[SerializeField] private TextMeshProUGUI mpText;
[SerializeField] private TextMeshProUGUI speedText;
[SerializeField] private TextMeshProUGUI atkText;
[SerializeField] private TextMeshProUGUI matText;
[SerializeField] private FormatTextUI hpText;
[SerializeField] private FormatTextUI mpText;
[SerializeField] private FormatTextUI speedText;
[SerializeField] private FormatTextUI atkText;
[SerializeField] private FormatTextUI matText;
/// <summary>
/// 用存档角色填充格子;角色配置不存在时按空格子显示。
/// </summary>
public void SetCharacter(CharacterSave save)
{
CharacterConfig config = GameEntry.Luban.Get<CharacterConfig>(save.characterId);
if (config == null)
{
Log.Warning("Can not find character config '{0}' for squad slot.", save.characterId);
SetEmpty();
return;
}
characterName.text = config.Name;
hpText.Set(config.MaxHp);
mpText.Set(config.MaxMp);
speedText.Set(config.Speed);
atkText.Set(config.Atk);
matText.Set(config.Mat);
HideIcon();
}
/// <summary>
/// 显示为空格子。
/// </summary>
public void SetEmpty()
{
characterName.text = string.Empty;
hpText.Clear();
mpText.Clear();
speedText.Clear();
atkText.Clear();
matText.Clear();
HideIcon();
}
private void HideIcon()
{
if (icon == null)
{
return;
}
icon.sprite = null;
icon.gameObject.SetActive(false);
}
}
}
@@ -1,15 +1,137 @@
using SepCore.Base;
using SepCore.Definition;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
public class WarehouseSlotItem : MonoBehaviour
{
private static readonly Color RarityWhite = new Color32(245, 247, 248, 255);
private static readonly Color RarityGreen = new Color32(93, 155, 97, 255);
private static readonly Color RarityBlue = new Color32(52, 127, 168, 255);
private static readonly Color RarityGold = new Color32(214, 169, 61, 255);
private static readonly Color RarityRed = new Color32(185, 87, 79, 255);
private static readonly Color RarityEmpty = new Color32(111, 101, 90, 255);
private static readonly Color SelectedBgColor = new Color(0.86f, 0.95f, 1f, 1f);
[SerializeField] private Image bg;
[SerializeField] private Button button;
[SerializeField] private Image icon;
[SerializeField] private Image rarity;
[SerializeField] private TextMeshProUGUI quantityText;
private int _slotId = 0;
private bool _filled = false;
private bool _clickBound = false;
/// <summary>
/// 设置格子在固定网格中的索引,点击事件以此作为唯一标识。
/// </summary>
public void SetSlotId(int slotId)
{
_slotId = slotId;
}
/// <summary>
/// 用物品堆叠填充格子;物品配置不存在时按空格子显示。
/// </summary>
public void SetItem(ItemStack stack)
{
ItemConfig config = GameEntry.Luban.Get<ItemConfig>(stack.itemId);
if (config == null)
{
Log.Warning("Can not find item config '{0}' for warehouse slot.", stack.itemId);
SetEmpty();
return;
}
_filled = true;
rarity.color = GetRarityColor(config.Rarity);
quantityText.text = stack.count.ToString();
HideIcon();
BindClick();
if (button != null)
{
button.interactable = true;
}
}
/// <summary>
/// 显示为空格子。
/// </summary>
public void SetEmpty()
{
_filled = false;
rarity.color = RarityEmpty;
quantityText.text = string.Empty;
HideIcon();
if (button != null)
{
button.interactable = false;
}
}
/// <summary>
/// 设置选中高亮。
/// </summary>
public void SetSelected(bool selected)
{
if (bg != null)
{
bg.color = selected ? SelectedBgColor : Color.white;
}
}
private void BindClick()
{
if (_clickBound || button == null)
{
return;
}
_clickBound = true;
button.onClick.AddListener(OnClick);
}
private void OnClick()
{
if (!_filled)
{
return;
}
GameEntry.Event.Fire(this, WarehouseSlotItemClickEventArgs.Create(_slotId));
}
private void HideIcon()
{
if (icon == null)
{
return;
}
icon.sprite = null;
icon.gameObject.SetActive(false);
}
private static Color GetRarityColor(Rarity rarity)
{
switch (rarity)
{
case Rarity.Green:
return RarityGreen;
case Rarity.Blue:
return RarityBlue;
case Rarity.Gold:
return RarityGold;
case Rarity.Red:
return RarityRed;
default:
return RarityWhite;
}
}
}
}
@@ -0,0 +1,136 @@
using SepCore.Definition;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
/// <summary>
/// 道具详情界面逻辑(手写 partial,与自动生成的 ItemDetailsForm.cs 合并)。
/// 只提供详情显示能力,由调用方(WarehouseForm)传入物品 ID 后刷新。
/// </summary>
public partial class ItemDetailsForm : UGuiForm
{
/// <summary>
/// 按物品 ID 刷新详情界面;配置不存在时清空显示。
/// </summary>
public void Refresh(int itemId)
{
ItemDetailsView view = View;
if (view == null)
{
Log.Warning("ItemDetailsForm view is not configured.");
return;
}
ItemConfig config = GameEntry.Luban.Get<ItemConfig>(itemId);
if (config == null)
{
Log.Warning("Can not find item config '{0}' for item details.", itemId);
ClearDetails(view);
return;
}
if (view.itemDetailNameText != null)
{
view.itemDetailNameText.text = config.Name;
}
if (view.itemDetailTypeText != null)
{
view.itemDetailTypeText.text = GetItemTypeName(config.ItemType) + " / " + GetRarityName(config.Rarity);
}
if (view.hpFormatText != null)
{
view.hpFormatText.Set(config.MaxHpBonus);
}
if (view.atkFormatText != null)
{
view.atkFormatText.Set(config.AtkBonus);
}
if (view.mpFormatText != null)
{
view.mpFormatText.Set(config.MaxMpBonus);
}
if (view.matFormatText != null)
{
view.matFormatText.Set(config.MatBonus);
}
if (view.speedFormatText != null)
{
view.speedFormatText.Set(0);
}
if (view.stackFormatText != null)
{
view.stackFormatText.Set(config.StackLimit);
}
if (view.itemDetailDescriptionText != null)
{
view.itemDetailDescriptionText.text = string.Empty;
}
if (view.itemDetailIcon != null)
{
view.itemDetailIcon.gameObject.SetActive(false);
}
}
private static void ClearDetails(ItemDetailsView view)
{
if (view.itemDetailNameText != null)
{
view.itemDetailNameText.text = string.Empty;
}
if (view.itemDetailTypeText != null)
{
view.itemDetailTypeText.text = string.Empty;
}
if (view.itemDetailDescriptionText != null)
{
view.itemDetailDescriptionText.text = string.Empty;
}
}
private static string GetItemTypeName(ItemType itemType)
{
switch (itemType)
{
case ItemType.Loot:
return "战利品";
case ItemType.Equipment:
return "装备";
case ItemType.Consumable:
return "消耗品";
default:
return "未知";
}
}
private static string GetRarityName(Rarity rarity)
{
switch (rarity)
{
case Rarity.White:
return "白";
case Rarity.Green:
return "绿";
case Rarity.Blue:
return "蓝";
case Rarity.Gold:
return "金";
case Rarity.Red:
return "红";
default:
return "无";
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33d70aa8e73294a65b7b132a8a0619af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -9,12 +9,12 @@ namespace SepCore.UI
[SerializeField] public global::UnityEngine.UI.Image itemDetailIcon;
[SerializeField] public global::TMPro.TextMeshProUGUI itemDetailNameText;
[SerializeField] public global::TMPro.TextMeshProUGUI itemDetailTypeText;
[SerializeField] public global::TMPro.TextMeshProUGUI hpText;
[SerializeField] public global::TMPro.TextMeshProUGUI atkText;
[SerializeField] public global::TMPro.TextMeshProUGUI mpText;
[SerializeField] public global::TMPro.TextMeshProUGUI matText;
[SerializeField] public global::TMPro.TextMeshProUGUI speedText;
[SerializeField] public global::TMPro.TextMeshProUGUI stackText;
[SerializeField] public global::SepCore.UI.FormatTextUI hpFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI atkFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI mpFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI matFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI speedFormatText;
[SerializeField] public global::SepCore.UI.FormatTextUI stackFormatText;
[SerializeField] public global::TMPro.TextMeshProUGUI itemDetailDescriptionText;
[SerializeField] public global::UnityEngine.UI.Button openLoadoutButton;
}
@@ -1,13 +0,0 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class ItemDetailsPanelForm : UGuiForm
{
[SerializeField] private ItemDetailsPanelView _view = null;
public ItemDetailsPanelView View => _view;
}
}
@@ -1,20 +0,0 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class ItemDetailsPanelView : MonoBehaviour
{
[SerializeField] public global::UnityEngine.UI.Image itemDetailIcon;
[SerializeField] public global::UnityEngine.UI.Text nameText;
[SerializeField] public global::UnityEngine.UI.Text typeText;
[SerializeField] public global::UnityEngine.UI.Text hpText;
[SerializeField] public global::UnityEngine.UI.Text atkText;
[SerializeField] public global::UnityEngine.UI.Text mpText;
[SerializeField] public global::UnityEngine.UI.Text matText;
[SerializeField] public global::UnityEngine.UI.Text speedText;
[SerializeField] public global::UnityEngine.UI.Text stackText;
[SerializeField] public global::UnityEngine.UI.Button openLoadoutButton;
}
}
@@ -0,0 +1,153 @@
using DG.Tweening;
using SepCore.Definition;
using UnityEngine;
using UnityEngine.UI;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
/// <summary>
/// 大厅界面逻辑(手写 partial,与自动生成的 LobbyForm.cs 合并)。
/// 导航使用 Toggle + ToggleGroup 管理选中状态;本类只负责页面切换与标记动画。
/// </summary>
public partial class LobbyForm : UGuiForm
{
private const float SelectionMarkerMoveDuration = 0.25f;
private enum LobbyPage
{
CombatReadiness,
Warehouse,
}
protected override void OnInit(object userData)
{
base.OnInit(userData);
View.homeToggle.onValueChanged.AddListener(OnHomeToggleValueChanged);
View.warehouseToggle.onValueChanged.AddListener(OnWarehouseToggleValueChanged);
View.loadoutToggle.onValueChanged.AddListener(OnLoadoutToggleValueChanged);
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
SwitchPage(LobbyPage.CombatReadiness, View.homeToggle);
}
protected override void OnClose(bool isShutdown, object userData)
{
if (View.selectionMarkerObject != null)
{
View.selectionMarkerObject.DOKill();
}
View.homeToggle.onValueChanged.RemoveListener(OnHomeToggleValueChanged);
View.warehouseToggle.onValueChanged.RemoveListener(OnWarehouseToggleValueChanged);
View.loadoutToggle.onValueChanged.RemoveListener(OnLoadoutToggleValueChanged);
base.OnClose(isShutdown, userData);
}
private void OnHomeToggleValueChanged(bool isOn)
{
if (isOn)
{
SwitchPage(LobbyPage.CombatReadiness, View.homeToggle);
}
}
private void OnWarehouseToggleValueChanged(bool isOn)
{
if (isOn)
{
SwitchPage(LobbyPage.Warehouse, View.warehouseToggle);
}
}
private void OnLoadoutToggleValueChanged(bool isOn)
{
// 战备页暂无对应界面,选中状态由 ToggleGroup 维护,仅预留切换入口
}
private void SwitchPage(LobbyPage page, Toggle activeToggle)
{
bool showCombatReadiness = page == LobbyPage.CombatReadiness;
bool showWarehouse = page == LobbyPage.Warehouse;
if (View.combatRandinessForm != null)
{
View.combatRandinessForm.gameObject.SetActive(showCombatReadiness);
}
if (View.warehouseForm != null)
{
View.warehouseForm.gameObject.SetActive(showWarehouse);
}
MoveSelectionMarker(activeToggle.transform as RectTransform);
if (showCombatReadiness)
{
RefreshCombatReadiness();
}
if (showWarehouse)
{
RefreshWarehouse();
}
}
private void MoveSelectionMarker(RectTransform targetToggle)
{
RectTransform marker = View.selectionMarkerObject;
if (marker == null || targetToggle == null || marker.parent == targetToggle)
{
return;
}
// 把标记相对目标 Toggle 锚点(anchorMin)的偏移,换算到 Toggle pivot 的局部坐标空间:
// 锚点相对 pivot 的偏移 = (anchor - pivot) * rect 尺寸。
Vector3 targetLocalPosition = new Vector3(
marker.anchoredPosition.x + (marker.anchorMin.x - targetToggle.pivot.x) * targetToggle.rect.width,
marker.anchoredPosition.y + (marker.anchorMin.y - targetToggle.pivot.y) * targetToggle.rect.height,
0f);
Vector3 targetWorldPosition = targetToggle.TransformPoint(targetLocalPosition);
marker.DOKill();
marker.DOMove(targetWorldPosition, SelectionMarkerMoveDuration)
.SetEase(Ease.OutCubic)
.OnComplete(() => marker.SetParent(targetToggle, true));
}
private void RefreshCombatReadiness()
{
CombatReadinessForm form = View.combatRandinessForm;
if (form == null)
{
Log.Warning("LobbyForm combat readiness form is not configured.");
return;
}
SaveData save = GameEntry.Save.Data;
if (save == null || form.View == null || form.View.squadForm == null)
{
Log.Warning("LobbyForm can not refresh combat readiness.");
return;
}
form.View.squadForm.RefreshCharacterList(save.characters);
}
private void RefreshWarehouse()
{
if (View.warehouseForm == null)
{
Log.Warning("LobbyForm warehouse form is not configured.");
return;
}
SaveData save = GameEntry.Save.Data;
View.warehouseForm.Refresh(save != null ? save.mainWarehouse : null);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9d236e85e2eab43f6904de56f9e5246d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -6,11 +6,11 @@ namespace SepCore.UI
[DisallowMultipleComponent]
public partial class LobbyView : MonoBehaviour
{
[SerializeField] public global::UnityEngine.UI.Button homeButton;
[SerializeField] public global::UnityEngine.UI.Toggle homeToggle;
[SerializeField] public global::UnityEngine.RectTransform selectionMarkerObject;
[SerializeField] public global::UnityEngine.UI.Button warehouseButton;
[SerializeField] public global::UnityEngine.UI.Button loadoutButton;
[SerializeField] public global::SepCore.UI.WarehouseView warehouseView;
[SerializeField] public global::SepCore.UI.CombatReadinessView combatRandinessView;
[SerializeField] public global::UnityEngine.UI.Toggle warehouseToggle;
[SerializeField] public global::UnityEngine.UI.Toggle loadoutToggle;
[SerializeField] public global::SepCore.UI.CombatReadinessForm combatRandinessForm;
[SerializeField] public global::SepCore.UI.WarehouseForm warehouseForm;
}
}
@@ -1,13 +0,0 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class MainWarehouseForm : UGuiForm
{
[SerializeField] private MainWarehouseView _view = null;
public MainWarehouseView View => _view;
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: ca70f33fa4f2c4a989b89f280e24201a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,20 +0,0 @@
// <auto-generated />
using UnityEngine;
namespace SepCore.UI
{
[DisallowMultipleComponent]
public partial class MainWarehouseView : MonoBehaviour
{
[SerializeField] public global::UnityEngine.UI.Button homeButton;
[SerializeField] public global::UnityEngine.UI.Button warehouseButton;
[SerializeField] public global::UnityEngine.UI.Button loadoutButton;
[SerializeField] public global::UnityEngine.UI.InputField searchInput;
[SerializeField] public global::UnityEngine.UI.Button sortButton;
[SerializeField] public global::UnityEngine.UI.ScrollRect itemScroll;
[SerializeField] public global::UnityEngine.RectTransform warehouseSlotRoot;
[SerializeField] public global::SepCore.UI.WarehouseSlotItem warehouseSlotTemplate;
[SerializeField] public global::UnityEngine.UI.Text itemCountText;
[SerializeField] public global::SepCore.UI.ItemDetailsView itemDetailsView;
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: f169da115e10a47e4b0b3bfefaff0d71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -6,6 +6,7 @@
"GUID:6055be8ebefd69e48b49212b09b47b2f",
"GUID:11ec41dbc3557e6499b2845ba531a9b0",
"GUID:363c5eb08ff8e6a439b85e37b8c20d96",
"GUID:3bb549212964b4192b427888ac9382e7",
"GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:d54b9488b03814a44ab937f0aeb738b1"
],
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Serialization;
namespace SepCore.UI
{
@@ -10,7 +11,7 @@ namespace SepCore.UI
public sealed class UISerializationRoot : MonoBehaviour
{
[Serializable]
public sealed class NestedViewReference
public sealed class NestedFormReference
{
[SerializeField] private UISerializationRoot root;
[SerializeField] private bool generateReference;
@@ -21,7 +22,7 @@ namespace SepCore.UI
public string VariableName => variableName;
#if UNITY_EDITOR
internal NestedViewReference(UISerializationRoot nestedRoot, string defaultVariableName)
internal NestedFormReference(UISerializationRoot nestedRoot, string defaultVariableName)
{
root = nestedRoot;
generateReference = true;
@@ -33,6 +34,19 @@ namespace SepCore.UI
generateReference = shouldGenerate;
variableName = generatedVariableName;
}
internal bool MigrateLegacyVariableName()
{
if (string.IsNullOrEmpty(variableName) ||
!variableName.EndsWith("View", StringComparison.Ordinal) ||
variableName.Length <= 4)
{
return false;
}
variableName = variableName.Substring(0, variableName.Length - 4) + "Form";
return true;
}
#endif
}
@@ -40,10 +54,11 @@ namespace SepCore.UI
private List<UISerializationItem> serializationItems = new List<UISerializationItem>();
[SerializeField, HideInInspector]
private List<NestedViewReference> nestedViewReferences = new List<NestedViewReference>();
[FormerlySerializedAs("nestedViewReferences")]
private List<NestedFormReference> nestedFormReferences = new List<NestedFormReference>();
public IReadOnlyList<UISerializationItem> SerializationItems => serializationItems;
public IReadOnlyList<NestedViewReference> NestedViewReferences => nestedViewReferences;
public IReadOnlyList<NestedFormReference> NestedFormReferences => nestedFormReferences;
#if UNITY_EDITOR
private bool refreshQueued;
@@ -76,8 +91,18 @@ namespace SepCore.UI
}
bool itemsChanged = !HasSameItems(ownedItems);
bool nestedViewsChanged = !HasSameNestedViews(directNestedRoots);
if (!itemsChanged && !nestedViewsChanged)
bool nestedFormsChanged = !HasSameNestedForms(directNestedRoots);
bool nestedNamesChanged = false;
for (int i = 0; i < directNestedRoots.Count; i++)
{
NestedFormReference existing = FindNestedForm(directNestedRoots[i]);
if (existing != null)
{
nestedNamesChanged |= existing.MigrateLegacyVariableName();
}
}
if (!itemsChanged && !nestedFormsChanged && !nestedNamesChanged)
{
return false;
}
@@ -88,24 +113,24 @@ namespace SepCore.UI
serializationItems.AddRange(ownedItems);
}
if (nestedViewsChanged)
if (nestedFormsChanged)
{
var next = new List<NestedViewReference>(directNestedRoots.Count);
var next = new List<NestedFormReference>(directNestedRoots.Count);
for (int i = 0; i < directNestedRoots.Count; i++)
{
UISerializationRoot nestedRoot = directNestedRoots[i];
NestedViewReference existing = FindNestedView(nestedRoot);
next.Add(existing ?? new NestedViewReference(nestedRoot, CreateDefaultNestedViewVariableName(nestedRoot)));
NestedFormReference existing = FindNestedForm(nestedRoot);
next.Add(existing ?? new NestedFormReference(nestedRoot, CreateDefaultNestedFormVariableName(nestedRoot)));
}
nestedViewReferences = next;
nestedFormReferences = next;
}
UnityEditor.EditorUtility.SetDirty(this);
return true;
}
public bool SetNestedViewReference(UISerializationRoot nestedRoot, bool shouldGenerate, string variableName)
public bool SetNestedFormReference(UISerializationRoot nestedRoot, bool shouldGenerate, string variableName)
{
if (nestedRoot == null || FindNearestParentRoot(nestedRoot.transform) != this)
{
@@ -113,7 +138,7 @@ namespace SepCore.UI
}
RefreshItems();
NestedViewReference reference = FindNestedView(nestedRoot);
NestedFormReference reference = FindNestedForm(nestedRoot);
if (reference == null)
{
throw new InvalidOperationException("The nested root is not registered.");
@@ -130,7 +155,7 @@ namespace SepCore.UI
return true;
}
public static string CreateDefaultNestedViewVariableName(UISerializationRoot nestedRoot)
public static string CreateDefaultNestedFormVariableName(UISerializationRoot nestedRoot)
{
string sourceName = nestedRoot == null ? "nested" : nestedRoot.gameObject.name.Replace("(Clone)", string.Empty).Trim();
if (sourceName.EndsWith("Form", StringComparison.Ordinal) && sourceName.Length > 4)
@@ -148,7 +173,7 @@ namespace SepCore.UI
identifier = "Nested";
}
return char.ToLowerInvariant(identifier[0]) + identifier.Substring(1) + "View";
return char.ToLowerInvariant(identifier[0]) + identifier.Substring(1) + "Form";
}
internal void QueueRefresh()
@@ -220,16 +245,16 @@ namespace SepCore.UI
return true;
}
private bool HasSameNestedViews(List<UISerializationRoot> found)
private bool HasSameNestedForms(List<UISerializationRoot> found)
{
if (nestedViewReferences.Count != found.Count)
if (nestedFormReferences.Count != found.Count)
{
return false;
}
for (int i = 0; i < found.Count; i++)
{
if (nestedViewReferences[i] == null || nestedViewReferences[i].Root != found[i])
if (nestedFormReferences[i] == null || nestedFormReferences[i].Root != found[i])
{
return false;
}
@@ -238,11 +263,11 @@ namespace SepCore.UI
return true;
}
private NestedViewReference FindNestedView(UISerializationRoot nestedRoot)
private NestedFormReference FindNestedForm(UISerializationRoot nestedRoot)
{
for (int i = 0; i < nestedViewReferences.Count; i++)
for (int i = 0; i < nestedFormReferences.Count; i++)
{
NestedViewReference reference = nestedViewReferences[i];
NestedFormReference reference = nestedFormReferences[i];
if (reference != null && reference.Root == nestedRoot)
{
return reference;
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ceade84509cfa498a91c9a2fe55cc189
guid: 82e9f2ea2a9254e848f8afcf5ff556e9
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -0,0 +1,50 @@
using System.Collections.Generic;
using SepCore.Definition;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
/// <summary>
/// 编队界面逻辑(手写 partial,与自动生成的 SquadForm.cs 合并)。
/// 以传入的角色列表重建角色槽列表,模板槽来自 SquadView.characterSlotTemplate。
/// </summary>
public partial class SquadForm : UGuiForm
{
/// <summary>
/// 按传入的角色列表重建角色槽列表 UI,每名角色一个槽位。
/// </summary>
public void RefreshCharacterList(IReadOnlyList<CharacterSave> characters)
{
SquadView squadView = View;
if (squadView == null || squadView.characterSlotRoot == null ||
squadView.characterSlotTemplate == null)
{
Log.Warning("SquadForm is not fully configured.");
return;
}
CharacterSlotItem template = squadView.characterSlotTemplate;
template.gameObject.SetActive(false);
for (int i = squadView.characterSlotRoot.childCount - 1; i >= 0; i--)
{
Transform child = squadView.characterSlotRoot.GetChild(i);
if (child == template.transform)
{
continue;
}
Destroy(child.gameObject);
}
int characterCount = characters != null ? characters.Count : 0;
for (int i = 0; i < characterCount; i++)
{
CharacterSlotItem slot = Instantiate(template, squadView.characterSlotRoot);
slot.gameObject.SetActive(true);
slot.SetCharacter(characters[i]);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a629f39601d3456ca1a8228059fa845
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 SquadForm : UGuiForm
{
[SerializeField] private SquadView _view = null;
public SquadView View => _view;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c6542ec01d550457587508eff74a6957
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 SquadView : MonoBehaviour
{
[SerializeField] public global::TMPro.TextMeshProUGUI squadMemberText;
[SerializeField] public global::UnityEngine.RectTransform characterSlotRoot;
[SerializeField] public global::SepCore.UI.CharacterSlotItem characterSlotTemplate;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6eacd1573559d4784862ed9c21d5928c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,73 @@
using System.Collections.Generic;
using GameFramework.Event;
using SepCore.Base;
using SepCore.Definition;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.UI
{
/// <summary>
/// 主仓库界面逻辑(手写 partial,与自动生成的 WarehouseForm.cs 合并)。
/// 只提供仓库界面能力,数据由调用方(LobbyForm)传入;
/// 监听格子点击事件,驱动选中高亮与详情界面刷新。
/// 注意:本表单作为嵌套表单使用,UGF 生命周期不会被框架调用,
/// 事件订阅改为幂等式(EnsureEventBound)。
/// </summary>
public partial class WarehouseForm : UGuiForm
{
private bool _eventBound = false;
/// <summary>
/// 用主仓库内容刷新整个仓库界面(物品列表)。
/// </summary>
public void Refresh(IReadOnlyList<ItemStack> stacks)
{
EnsureEventBound();
InventoryForm inventoryForm = View.inventoryPanelForm;
if (inventoryForm == null)
{
Log.Warning("WarehouseForm inventory panel form is not configured.");
return;
}
inventoryForm.RefreshList(stacks);
}
private void EnsureEventBound()
{
if (_eventBound)
{
return;
}
_eventBound = true;
GameEntry.Event.Subscribe(WarehouseSlotItemClickEventArgs.EventId, OnWarehouseSlotItemClick);
}
private void OnWarehouseSlotItemClick(object sender, GameEventArgs e)
{
if (this == null)
{
return;
}
WarehouseSlotItemClickEventArgs ne = (WarehouseSlotItemClickEventArgs)e;
int slotId = ne.SlotId;
if (View.inventoryPanelForm == null)
{
return;
}
View.inventoryPanelForm.SetSelectedSlot(slotId);
int itemId = View.inventoryPanelForm.GetItemIdAtSlot(slotId);
if (itemId != 0 && View.itemDetailsPanelForm != null)
{
View.itemDetailsPanelForm.Refresh(itemId);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be961300f50004e35ad24b4ff3368124
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -6,7 +6,7 @@ namespace SepCore.UI
[DisallowMultipleComponent]
public partial class WarehouseView : MonoBehaviour
{
[SerializeField] public global::SepCore.UI.InventoryView inventoryPanelView;
[SerializeField] public global::SepCore.UI.ItemDetailsView itemDetailsPanelView;
[SerializeField] public global::SepCore.UI.InventoryForm inventoryPanelForm;
[SerializeField] public global::SepCore.UI.ItemDetailsForm itemDetailsPanelForm;
}
}