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