Files
Echo/Assets/_Project/Scripts/UI/InventoryUI.cs
T
2026-07-08 20:42:51 +08:00

413 lines
16 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 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);
}
}
}