Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/UI/EquipmentUI.cs

276 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Base;
using Entity;
using System.Collections.Generic;
using UnityEngine;
// 确保 Character 类在此命名空间下
namespace UI
{
/// <summary>
/// 负责管理和显示角色的装备用户界面。
/// 该组件会监听当前关注实体的变化,并根据所关注角色的库存数据动态更新装备槽位的显示,
/// 同时采用对象池技术高效管理 ItemUI 实例的创建和复用。
/// 除了显示,现在还支持通过滚轮选择物品,并同步更新焦点角色的 CurrentSelected 字段。
/// </summary>
public class EquipmentUI : MonoBehaviour, ITick
{
[SerializeField]
[Tooltip("所有 ItemUI 实例的父级 GameObject用于布局。")]
private GameObject uiParent;
[SerializeField]
[Tooltip("用于实例化装备槽位的 ItemUI 预制件。")]
private ItemUI itemUIPrefab;
/// <summary>
/// 当前界面所关联和关注的角色实体。
/// </summary>
private Character focusedEntity;
/// <summary>
/// ItemUI 实例的对象池,用于高效管理和复用 ItemUI。
/// </summary>
private List<ItemUI> itemUIPool = new();
/// <summary>
/// MonoBehaviour 的 Start 生命周期方法。
/// 在此方法中,注册当游戏主要程序中关注的实体发生变化时,调用 <see cref="UpdateFocusedEntity"/> 方法进行更新。
/// </summary>
private void Start()
{
Program.Instance.OnFocusedEntityChanged += UpdateFocusedEntity;
uiParent.SetActive(false);
}
/// <summary>
/// MonoBehaviour 的 OnDestroy 生命周期方法。
/// 在此方法中,取消注册所有已订阅的事件监听器,并清理对象池中创建的所有 ItemUI 实例,
/// 以防止内存泄漏和不必要的引用。
/// </summary>
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();
}
/// <summary>
/// 当游戏程序中关注的实体发生变化时调用此方法。
/// 该方法会更新当前 EquipmentUI 所关联的角色,并相应地注册或取消注册库存改变事件。
/// </summary>
/// <param name="entity">新的关注实体,可能为 null 或非 Character 类型。</param>
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); // 传入一个无效索引以取消所有选中
}
}
/// <summary>
/// 根据当前关注角色的库存数据更新装备UI的显示。
/// 该方法通过对象池机制高效地管理 ItemUI 实例的创建、复用和禁用。
/// </summary>
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<ItemUI>();
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);
}
}
/// <summary>
/// 当当前选中物品改变时,更新所有 ItemUI 的选中状态。
/// </summary>
/// <param name="selectedItemIndex">当前选中的物品索引。传入 -1 将取消所有 ItemUI 的选中状态。</param>
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);
}
}
}
}
/// <summary>
/// 每帧调用的更新方法,用于处理滚轮输入以选择物品。
/// </summary>
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选中状态
}
}
}
}
}