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 slots = new List(); private InputAction hotbar1Action; private InputAction hotbar2Action; private InputAction hotbar3Action; private InputAction hotbar4Action; private InputAction dropAction; private System.Action hotbar1Handler; private System.Action hotbar2Handler; private System.Action hotbar3Handler; private System.Action hotbar4Handler; private System.Action dropHandler; private void Awake() { if (handController == null) { handController = FindObjectOfType(true); } if (playerInput == null) { var pc = FindObjectOfType(true); if (pc != null) { playerInput = pc.GetComponent(); } if (playerInput == null) { playerInput = FindObjectOfType(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(newSlot.transform, "Icon"); ui.amountText = FindChild(newSlot.transform, "AmountText"); ui.frame = FindChild(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(Transform parent, string name) where T : Component { foreach (Transform child in parent) { if (child.name == name) return child.GetComponent(); T found = FindChild(child, name); if (found != null) return found; } return null; } // 记录上一次当前选中格子的物品数据,用于检测是否捡到了新东西 private ItemData lastSelectedItemData = null; private void RefreshUI() { if (InventorySystem.Instance == null) return; List 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(); if (physState != null) { physState.ReleaseKinematic(); } // 2. 如果有刚体,给它一个向前的力(扔出去的感觉) Rigidbody rb = droppedObj.GetComponent(); 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); } } }