using Base; using Entity; using System.Collections.Generic; using UnityEngine; // 确保 Character 类在此命名空间下 namespace UI { /// /// 负责管理和显示角色的装备用户界面。 /// 该组件会监听当前关注实体的变化,并根据所关注角色的库存数据动态更新装备槽位的显示, /// 同时采用对象池技术高效管理 ItemUI 实例的创建和复用。 /// 除了显示,现在还支持通过滚轮选择物品,并同步更新焦点角色的 CurrentSelected 字段。 /// public class EquipmentUI : MonoBehaviour, ITick { [SerializeField] [Tooltip("所有 ItemUI 实例的父级 GameObject,用于布局。")] private GameObject uiParent; [SerializeField] [Tooltip("用于实例化装备槽位的 ItemUI 预制件。")] private ItemUI itemUIPrefab; /// /// 当前界面所关联和关注的角色实体。 /// private Character focusedEntity; /// /// ItemUI 实例的对象池,用于高效管理和复用 ItemUI。 /// private List itemUIPool = new(); /// /// MonoBehaviour 的 Start 生命周期方法。 /// 在此方法中,注册当游戏主要程序中关注的实体发生变化时,调用 方法进行更新。 /// private void Start() { Program.Instance.OnFocusedEntityChanged += UpdateFocusedEntity; uiParent.SetActive(false); } /// /// MonoBehaviour 的 OnDestroy 生命周期方法。 /// 在此方法中,取消注册所有已订阅的事件监听器,并清理对象池中创建的所有 ItemUI 实例, /// 以防止内存泄漏和不必要的引用。 /// private void OnDestroy() { Program.Instance.OnFocusedEntityChanged -= UpdateFocusedEntity; // 如果当前有关注的角色,取消注册其库存改变事件。 // 确保 focusedEntity 不为 null 且 Inventory 不为 null。 if (focusedEntity != null && focusedEntity.Inventory != null) { focusedEntity.Inventory.OnInventoryChanged -= UpdateUI; } // 销毁对象池中所有 ItemUI 的 GameObject。 foreach (var itemUI in itemUIPool) { if (itemUI != null && itemUI.gameObject != null) { Destroy(itemUI.gameObject); } } itemUIPool.Clear(); } /// /// 当游戏程序中关注的实体发生变化时调用此方法。 /// 该方法会更新当前 EquipmentUI 所关联的角色,并相应地注册或取消注册库存改变事件。 /// /// 新的关注实体,可能为 null 或非 Character 类型。 private void UpdateFocusedEntity(Entity.Entity entity) { // 如果之前有关注的角色,先取消注册其库存改变事件。 // 确保 focusedEntity 不为 null 且 Inventory 不为 null。 if (focusedEntity != null && focusedEntity.Inventory != null) { focusedEntity.Inventory.OnInventoryChanged -= UpdateUI; } // 尝试将新的实体转换为角色类型。 Character newCharacter = entity as Character; if (newCharacter != null) { focusedEntity = newCharacter; } else { // 如果传入的 entity 不是 Character 类型,或者为 null,则清除当前的 focusedEntity。 focusedEntity = null; } // 如果现在有关注的角色,注册其库存改变事件。 if (focusedEntity != null) { focusedEntity.Inventory.OnInventoryChanged += UpdateUI; } // 立即更新UI以反映新的关注实体(或没有关注实体)的状态。 UpdateUI(); // 在更新UI后,确保UI的选中状态与角色当前选中字段同步 // 只有当有焦点角色且库存不为空时才更新选中状态 if (focusedEntity != null && focusedEntity.Inventory != null && focusedEntity.Inventory.Capacity > 0) { // 确保 CurrentSelected 在有效范围内,否则重置为0 if (focusedEntity.CurrentSelected < 0 || focusedEntity.CurrentSelected >= focusedEntity.Inventory.Capacity) { focusedEntity.CurrentSelected = 0; } UpdateSelectionUI(focusedEntity.CurrentSelected); } else { // 如果没有焦点实体或库存为空,则清空所有选中状态 UpdateSelectionUI(-1); // 传入一个无效索引以取消所有选中 } } /// /// 根据当前关注角色的库存数据更新装备UI的显示。 /// 该方法通过对象池机制高效地管理 ItemUI 实例的创建、复用和禁用。 /// private void UpdateUI() { // 如果没有关注的角色或其库存,则禁用所有 ItemUI。 if (focusedEntity == null || focusedEntity.Inventory == null) { foreach (var itemUI in itemUIPool) { if (itemUI != null && itemUI.gameObject != null) { itemUI.gameObject.SetActive(false); } } uiParent.SetActive(false); return; } // 检查用于创建物品UI的预制件是否已在 Inspector 中赋值。 if (itemUIPrefab == null) { Debug.LogError("ItemUIPrefab 未在 EquipmentUI 中指定。无法创建物品用户界面。", this); foreach (var itemUI in itemUIPool) itemUI.gameObject.SetActive(false); uiParent.SetActive(false); // 确保父级也被禁用 return; } int requiredUIs = focusedEntity.Inventory.Capacity; int currentUIPoolSize = itemUIPool.Count; // 当前对象池中 ItemUI 实例的总数。 // 遍历所有必要的物品槽位,复用对象池中的 ItemUI,或在不足时创建新的 ItemUI。 for (int i = 0; i < requiredUIs; i++) { ItemUI itemUI; if (i < currentUIPoolSize) { itemUI = itemUIPool[i]; } else { // 使用 Instantiate(GameObject, Transform) 以确保父级设置正确且避免转换问题。 var itemObj = Instantiate(itemUIPrefab.gameObject, uiParent.transform); itemUI = itemObj.GetComponent(); itemUIPool.Add(itemUI); currentUIPoolSize++; // 更新池的大小计数。 } // 确保 ItemUI GameObject 处于激活状态,并使用当前物品槽位的数据进行初始化。 itemUI.gameObject.SetActive(true); itemUI.Init(focusedEntity.Inventory.GetSlot(i), i); // 移除此处 itemUI.Select = false; 选中状态将由 UpdateSelectionUI 统一管理 } // 如果库存槽位数量减少,禁用对象池中多余的 ItemUI 实例。 for (int i = requiredUIs; i < currentUIPoolSize; i++) { if (itemUIPool[i] != null && itemUIPool[i].gameObject != null) { itemUIPool[i].gameObject.SetActive(false); itemUIPool[i].Select = false; // 禁用时也确保清除选中状态 } } uiParent.SetActive(true); // 首次更新UI时,或者当Inventory改变时,需要确保 CurrentSelected 的UI状态是正确的 // 但如果 UpdateFocusedEntity 已经处理了,这里可以省略,或者确保只在必要时调用 // 考虑到 UpdateUI 也会被 Inventory.OnInventoryChanged 调用,这里再次确保同步是合理的。 if (focusedEntity != null && focusedEntity.Inventory != null && focusedEntity.Inventory.Capacity > 0) { UpdateSelectionUI(focusedEntity.CurrentSelected); } else { UpdateSelectionUI(-1); } } /// /// 当当前选中物品改变时,更新所有 ItemUI 的选中状态。 /// /// 当前选中的物品索引。传入 -1 将取消所有 ItemUI 的选中状态。 private void UpdateSelectionUI(int selectedItemIndex) { // 如果对象池为空,则无需更新 if (itemUIPool == null || itemUIPool.Count == 0) { return; } for (int i = 0; i < itemUIPool.Count; i++) { ItemUI itemUI = itemUIPool[i]; if (itemUI != null && itemUI.gameObject != null) { // 只有在 ItemUI 激活状态下才设置其选中状态,避免对禁用UI的操作 // 或者如果传入-1,即使激活也全部设置为false if (itemUI.gameObject.activeSelf || selectedItemIndex == -1) // 确保当取消所有选中时,循环到所有激活的,甚至当前禁用的ItemUI { itemUI.Select = (i == selectedItemIndex && itemUI.gameObject.activeSelf); } } } } /// /// 每帧调用的更新方法,用于处理滚轮输入以选择物品。 /// public void Tick() { // 如果没有焦点实体、没有库存或库存为空,不进行选择操作 if (focusedEntity == null || focusedEntity.Inventory == null || focusedEntity.Inventory.Capacity == 0) { return; } float scrollInput = Input.GetAxis("Mouse ScrollWheel"); if (scrollInput != 0) // 检测到滚轮输入 { int currentSelection = focusedEntity.CurrentSelected; int inventoryCapacity = focusedEntity.Inventory.Capacity; if (scrollInput > 0) // 滚轮向上,选择前一个 { currentSelection--; if (currentSelection < 0) { currentSelection = inventoryCapacity - 1; // 循环到最后一个 } } else // 滚轮向下,选择后一个 { currentSelection++; if (currentSelection >= inventoryCapacity) { currentSelection = 0; // 循环到第一个 } } // 如果选择发生变化,则更新焦点实体和UI if (focusedEntity.CurrentSelected != currentSelection) { focusedEntity.CurrentSelected = currentSelection; UpdateSelectionUI(currentSelection); // 更新UI选中状态 } } } } }