Files
Echo/Assets/_Project/Scripts/UI/InventoryUI.cs
T

413 lines
16 KiB
C#
Raw Normal View History

2026-07-08 19:53:15 +08:00
using Inventory;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.InputSystem; // 引入 Input System
using Player;
public class InventoryUI : MonoBehaviour
{
[Header("Player References")]
public HandController handController; // 拖入 Player 身上的脚本
public PlayerInput playerInput; // 拖入 Player 身上的 PlayerInput 组件
[Header("UI References")]
public Transform hotbarPanel; // HotbarPanel
public GameObject slotPrefab; // õ Hotbar_Slot Prefab
[Header("Settings")]
public Color selectedColor = new Color(0f, 1f, 1f, 0.5f); // ѡʱı߿ɫɫ͸
public Color normalColor = new Color(0f, 1f, 1f, 0f); // δѡʱı߿ɫ͸
// ڲӵ UI ãÿ GetComponent
private class SlotUI
{
public GameObject instance;
public Image icon;
public TextMeshProUGUI amountText;
public Image frame; // ñ߿ɫʾѡ״̬
}
private List<SlotUI> slots = new List<SlotUI>();
private InputAction hotbar1Action;
private InputAction hotbar2Action;
private InputAction hotbar3Action;
private InputAction hotbar4Action;
private InputAction dropAction;
private System.Action<InputAction.CallbackContext> hotbar1Handler;
private System.Action<InputAction.CallbackContext> hotbar2Handler;
private System.Action<InputAction.CallbackContext> hotbar3Handler;
private System.Action<InputAction.CallbackContext> hotbar4Handler;
private System.Action<InputAction.CallbackContext> dropHandler;
private void Awake()
{
if (handController == null)
{
handController = FindObjectOfType<HandController>(true);
}
if (playerInput == null)
{
var pc = FindObjectOfType<PlayerController>(true);
if (pc != null)
{
playerInput = pc.GetComponent<PlayerInput>();
}
if (playerInput == null)
{
playerInput = FindObjectOfType<PlayerInput>(true);
}
}
}
private void Start()
{
TryInitializeUI();
}
private void OnEnable()
{
if (hotbar1Handler == null) hotbar1Handler = _ => SelectSlot(0);
if (hotbar2Handler == null) hotbar2Handler = _ => SelectSlot(1);
if (hotbar3Handler == null) hotbar3Handler = _ => SelectSlot(2);
if (hotbar4Handler == null) hotbar4Handler = _ => SelectSlot(3);
if (dropHandler == null) dropHandler = OnDropItem;
if (playerInput == null)
{
Debug.LogError("[InventoryUI] PlayerInput 未赋值,无法绑定 Hotbar/Drop 输入。请在 Inspector 里拖入玩家身上的 PlayerInput。");
return;
}
if (playerInput.currentActionMap != null && playerInput.currentActionMap.name != "Player")
{
Debug.LogWarning($"[InventoryUI] 当前 ActionMap 为 '{playerInput.currentActionMap.name}'Hotbar/Drop 可能不会触发。");
}
if (playerInput != null)
{
// 订阅 Hotbar 切换事件
// 注意:Action 名字必须和你 Input Actions 里的一模一样
hotbar1Action = playerInput.actions["Hotbar1"];
if (hotbar1Action != null) hotbar1Action.performed += hotbar1Handler;
else Debug.LogError("[InventoryUI] 找不到名为 'Hotbar1' 的 Action,请检查 Input Actions。");
hotbar2Action = playerInput.actions["Hotbar2"];
if (hotbar2Action != null) hotbar2Action.performed += hotbar2Handler;
else Debug.LogError("[InventoryUI] 找不到名为 'Hotbar2' 的 Action,请检查 Input Actions。");
hotbar3Action = playerInput.actions["Hotbar3"];
if (hotbar3Action != null) hotbar3Action.performed += hotbar3Handler;
else Debug.LogError("[InventoryUI] 找不到名为 'Hotbar3' 的 Action,请检查 Input Actions。");
hotbar4Action = playerInput.actions["Hotbar4"];
if (hotbar4Action != null) hotbar4Action.performed += hotbar4Handler;
else Debug.LogError("[InventoryUI] 找不到名为 'Hotbar4' 的 Action,请检查 Input Actions。");
// 订阅丢弃事件
dropAction = playerInput.actions["Drop"];
if (dropAction != null) dropAction.performed += dropHandler;
else Debug.LogError("[InventoryUI] 找不到名为 'Drop' 的 Action,请检查 Input Actions。");
}
}
private void OnDisable()
{
// 取消订阅,防止内存泄漏
if (hotbar1Action != null && hotbar1Handler != null) hotbar1Action.performed -= hotbar1Handler;
if (hotbar2Action != null && hotbar2Handler != null) hotbar2Action.performed -= hotbar2Handler;
if (hotbar3Action != null && hotbar3Handler != null) hotbar3Action.performed -= hotbar3Handler;
if (hotbar4Action != null && hotbar4Handler != null) hotbar4Action.performed -= hotbar4Handler;
if (dropAction != null && dropHandler != null) dropAction.performed -= dropHandler;
hotbar1Action = null;
hotbar2Action = null;
hotbar3Action = null;
hotbar4Action = null;
dropAction = null;
}
private void Update()
{
TryInitializeUI();
if (isInitialized)
{
RefreshUI();
}
}
private bool isInitialized;
private void TryInitializeUI()
{
if (isInitialized) return;
if (InventorySystem.Instance == null) return;
InitializeUI();
isInitialized = true;
}
private void InitializeUI()
{
if (hotbarPanel == null || slotPrefab == null)
{
Debug.LogError("[InventoryUI] 缺少 HotbarPanel 或 SlotPrefab 引用!请在 Inspector 中赋值。");
return;
}
// 1. 清理现有的子物体(比如你在编辑器里复制的那 5 个测试格子)
foreach (Transform child in hotbarPanel)
{
Destroy(child.gameObject);
}
slots.Clear();
// 2. 根据 InventorySystem 的最大槽位生成格子
// 如果 InventorySystem 还没准备好,我们默认生成 5 个
int slotCount = InventorySystem.Instance != null ? InventorySystem.Instance.maxSlots : 5;
Debug.Log($"[InventoryUI] 正在生成 {slotCount} 个格子...");
for (int i = 0; i < slotCount; i++)
{
GameObject newSlot = Instantiate(slotPrefab, hotbarPanel);
SlotUI ui = new SlotUI();
ui.instance = newSlot;
// 使用递归查找,这样即使层级稍微深一点也能找到
ui.icon = FindChild<Image>(newSlot.transform, "Icon");
ui.amountText = FindChild<TextMeshProUGUI>(newSlot.transform, "AmountText");
ui.frame = FindChild<Image>(newSlot.transform, "Frame");
if (ui.icon == null) Debug.LogError($"[InventoryUI] 格子 {i} 找不到名为 'Icon' 的 Image 组件!");
if (ui.amountText == null) Debug.LogError($"[InventoryUI] 格子 {i} 找不到名为 'AmountText' 的 TMP 组件!");
// 默认隐藏图标和文字,并重置颜色为不透明
if (ui.icon != null)
{
ui.icon.color = Color.white;
ui.icon.sprite = null;
ui.icon.gameObject.SetActive(false);
}
if (ui.amountText != null) ui.amountText.gameObject.SetActive(false);
slots.Add(ui);
}
// 3. 初始化默认选择第一个格子
SelectSlot(0);
}
// 辅助方法:递归查找子物体组件
private T FindChild<T>(Transform parent, string name) where T : Component
{
foreach (Transform child in parent)
{
if (child.name == name) return child.GetComponent<T>();
T found = FindChild<T>(child, name);
if (found != null) return found;
}
return null;
}
// 记录上一次当前选中格子的物品数据,用于检测是否捡到了新东西
private ItemData lastSelectedItemData = null;
private void RefreshUI()
{
if (InventorySystem.Instance == null) return;
List<InventoryItem> items = InventorySystem.Instance.inventory;
// 检查当前选中格子的物品是否发生了变化
if (currentSelection >= 0 && currentSelection < items.Count)
{
InventoryItem currentItem = items[currentSelection];
ItemData currentData = currentItem?.data;
// 如果物品数据变了(比如从 null 变成了 Apple,或者从 Apple 换成了 Bottle
// 并且当前没有强制收手,就自动刷新手中的模型
if (currentData != lastSelectedItemData)
{
lastSelectedItemData = currentData;
if (!isHandHidden)
{
UpdateHandItem();
}
}
}
for (int i = 0; i < slots.Count; i++)
{
InventoryItem item = i < items.Count ? items[i] : null;
ItemData data = item != null ? item.data : null;
Sprite iconSprite = data != null ? data.icon : null;
bool hasRenderableItem = data != null && iconSprite != null;
if (slots[i].icon != null)
{
if (hasRenderableItem)
{
slots[i].icon.sprite = iconSprite;
slots[i].icon.color = Color.white;
slots[i].icon.gameObject.SetActive(true);
}
else
{
slots[i].icon.sprite = null;
slots[i].icon.gameObject.SetActive(false);
}
}
if (slots[i].amountText != null)
{
if (data != null && item != null && item.stackSize > 1)
{
slots[i].amountText.gameObject.SetActive(true);
slots[i].amountText.text = item.stackSize.ToString();
}
else
{
slots[i].amountText.text = string.Empty;
slots[i].amountText.gameObject.SetActive(false);
}
}
}
}
// 丢弃物品的逻辑桩
private void OnDropItem(InputAction.CallbackContext context)
{
if (currentSelection < 0 || InventorySystem.Instance == null) return;
// 检查当前格子有没有东西
if (currentSelection < InventorySystem.Instance.inventory.Count)
{
var item = InventorySystem.Instance.inventory[currentSelection];
if (item != null && item.data != null)
{
Debug.Log($"[Input] 按下了 G 键,准备丢弃: {item.data.name}");
// 1. 在玩家前方生成掉落物
if (item.data.pickupPrefab != null)
{
// 优先使用 HandController 的位置(即 Player 位置)作为基准
// 如果能获取到具体的 handPosition(手的位置)则更好,否则用 Player 身体位置
Transform origin = handController != null ? handController.transform : transform;
// 如果 handController 有定义 handPosition,尽量用它,因为它通常在摄像机附近
if (handController != null && handController.handPosition != null)
{
origin = handController.handPosition;
}
// 在起点前方 0.5 米处生成(避免穿模)
Vector3 spawnPos = origin.position + origin.forward * 0.5f;
GameObject droppedObj = Instantiate(item.data.pickupPrefab, spawnPos, Quaternion.identity);
// 1.5 如果有 ItemPhysicsState,强制解除可能存在的 startKinematic 锁定
var physState = droppedObj.GetComponent<Project.Interaction.ItemPhysicsState>();
if (physState != null)
{
physState.ReleaseKinematic();
}
// 2. 如果有刚体,给它一个向前的力(扔出去的感觉)
Rigidbody rb = droppedObj.GetComponent<Rigidbody>();
if (rb != null)
{
// 确保物理完全启用(作为最后保障)
rb.isKinematic = false;
rb.useGravity = true;
// 随机一点旋转,更自然
rb.AddTorque(Random.insideUnitSphere * 5f);
// 使用 origin.forward 确保是向当前朝向扔出
rb.AddForce(origin.forward * 4f + Vector3.up * 1.5f, ForceMode.Impulse);
}
}
else
{
Debug.LogWarning($"[Inventory] 物品 {item.data.name} 缺少 PickupPrefab,无法在场景中生成掉落物!只会在背包中移除。");
}
// 3. 从背包中移除 1 个
InventorySystem.Instance.RemoveFromSlot(currentSelection, 1);
// 4. 手部模型更新会由 RefreshUI 自动处理
}
else
{
Debug.Log("[Input] 当前格子是空的,无法丢弃。");
}
}
}
private int currentSelection = -1;
private bool isHandHidden = false; // 记录当前是否强制收手
private void SelectSlot(int index)
{
if (index < 0 || index >= slots.Count) return;
// 如果再次按下当前选中的格子 -> 切换收手/拿出状态
if (index == currentSelection)
{
isHandHidden = !isHandHidden;
UpdateHandItem(); // 更新手部显示
return;
}
// 切换到了新格子
currentSelection = index;
isHandHidden = false; // 重置收手状态,默认拿出新物品
// 同步选中状态给 InventorySystem
if (InventorySystem.Instance != null)
{
InventorySystem.Instance.selectedSlotIndex = index;
}
// 更新高亮显示
for (int i = 0; i < slots.Count; i++)
{
if (slots[i].frame != null)
{
slots[i].frame.color = (i == index) ? selectedColor : normalColor;
}
}
UpdateHandItem();
Debug.Log($"选择了第 {index + 1} 个格子");
}
// 独立出来的更新手部物品方法
private void UpdateHandItem()
{
if (handController == null || InventorySystem.Instance == null) return;
// 如果强制收手,直接传 null
if (isHandHidden)
{
handController.EquipItem(null);
return;
}
// 否则尝试装备当前选中格子的物品
if (currentSelection >= 0 && currentSelection < InventorySystem.Instance.inventory.Count)
{
var item = InventorySystem.Instance.inventory[currentSelection];
handController.EquipItem(item != null ? item.data : null);
}
else
{
handController.EquipItem(null);
}
}
}