Unity3D仿星露谷物语开发19之库存栏丢弃及交互道具

1、目标

从库存栏中把道具拖到游戏场景中,库存栏中道具数相应做减法或者删除道具。同时在库存栏中可以交换两个道具的位置。

2、UIInventorySlot设置Raycast属性

在UIInventorySlot中,我们只希望最外层的UIInventorySlot响应Raycast,他下面的2个子对象不需要相应Raycast,所以需要取消InventoryHighlight和Text的Raycast Target设置。

     

3、创建可拖动对象

(1)优化Tags.cs

为了可以方便获取Items对象,新增一个Tag名为ItemsParentTransform,并且将Items标记为ItemsParentTransform。

同时在Assets -> Scripts -> Misc的Tags.cs代码中,新增:

public const string ItemsParentTransform = "ItemsParentTransform";

此时Tags.cs的完整代码是:

using UnityEngine;

public static class Tags
{
    public const string BoundsConfiner = "BoundsConfiner";
    public const string ItemsParentTransform = "ItemsParentTransform";
}

(2)优化Play.cs

修改Assets -> Scripts -> Player -> Player.cs文件。

当我们用鼠标拖动道具到场景中时,我们不希望角色跟着鼠标移动。

改动1:将已有逻辑移到判断PlayerInputIsDisabled下,当输入不被禁用时才移动。

private void Update()
{
    #region  Player Input

    if (!PlayerInputIsDisabled)
    {
        ResetAnimationTrigger();

        PlayerMovementInput();

        PlayerWalkInput();

        // Send event to any listeners for player movement input
        EventHandler.CallMovementEvent(xInput, yInput, isWalking, isRunning, isIdle, isCarrying, toolEffect,
                isUsingToolRight, isUsingToolLeft, isUsingToolUp, isUsingToolDown,
                isLiftingToolRight, isLiftingToolLeft, isLiftingToolUp, isLiftingToolDown,
                isPickingRight, isPickingLeft, isPickingUp, isPickingDown,
                isSwingToolRight, isSwingToolLeft, isSwingToolUp, isSwingToolDown,
                false, false, false, false);
    }


    #endregion Player Input
}

改动2:创建禁用/启动输入的函数

 public void DisablePlayerInpupt()
 {
     PlayerInputIsDisabled = true;
 }


 public void EnablePlayerInput()
 {
     PlayerInputIsDisabled = false;
 }

改动3:对外提供的函数,禁用输入,并且设置用户为静止状态。

public void DisablePlayerInputAndResetMovement()
{
    DisablePlayerInpupt();
    ResetMovement();

    // Send event to any listeners for player movement input
    EventHandler.CallMovementEvent(xInput, yInput, isWalking, isRunning, isIdle, isCarrying, toolEffect,
            isUsingToolRight, isUsingToolLeft, isUsingToolUp, isUsingToolDown,
            isLiftingToolRight, isLiftingToolLeft, isLiftingToolUp, isLiftingToolDown,
            isPickingRight, isPickingLeft, isPickingUp, isPickingDown,
            isSwingToolRight, isSwingToolLeft, isSwingToolUp, isSwingToolDown,
            false, false, false, false);
}

private void ResetMovement()
{
    // Reset movement
    xInput = 0f;
    yInput = 0f;
    isRunning = false;
    isWalking = false;
    isIdle = true;
}

Player.cs完整的代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : SingletonMonobehaviour<Player>
{
    private float xInput;
    private float yInput;
    private bool isWalking;
    private bool isRunning;
    private bool isIdle;
    private bool isCarrying = false;
    private ToolEffect toolEffect = ToolEffect.none;
    private bool isUsingToolRight;
    private bool isUsingToolLeft;
    private bool isUsingToolUp;
    private bool isUsingToolDown;
    private bool isLiftingToolRight;
    private bool isLiftingToolLeft;
    private bool isLiftingToolUp;
    private bool isLiftingToolDown;
    private bool isPickingRight;
    private bool isPickingLeft;
    private bool isPickingUp;
    private bool isPickingDown;
    private bool isSwingToolRight;
    private bool isSwingToolLeft;
    private bool isSwingToolUp;
    private bool isSwingToolDown;

    private Camera mainCamera;

    private Rigidbody2D rigidbody2D;

    private Direction playerDirection;

    private float movementSpeed;

    private bool _playerInputIsDisabled = false;

    public bool PlayerInputIsDisabled { get => _playerInputIsDisabled; set => _playerInputIsDisabled = value; }

    protected override void Awake()
    {
        base.Awake();

        rigidbody2D = GetComponent<Rigidbody2D>();

        mainCamera = Camera.main;
    }

    private void Update()
    {
        #region  Player Input

        if (!PlayerInputIsDisabled)
        {
            ResetAnimationTrigger();

            PlayerMovementInput();

            PlayerWalkInput();

            // Send event to any listeners for player movement input
            EventHandler.CallMovementEvent(xInput, yInput, isWalking, isRunning, isIdle, isCarrying, toolEffect,
                    isUsingToolRight, isUsingToolLeft, isUsingToolUp, isUsingToolDown,
                    isLiftingToolRight, isLiftingToolLeft, isLiftingToolUp, isLiftingToolDown,
                    isPickingRight, isPickingLeft, isPickingUp, isPickingDown,
                    isSwingToolRight, isSwingToolLeft, isSwingToolUp, isSwingToolDown,
                    false, false, false, false);
        }


        #endregion Player Input
    }

    private void FixedUpdate()
    {
        PlayerMovement();
    }

    private void PlayerMovement()
    {
        Vector2 move = new Vector2(xInput * movementSpeed * Time.deltaTime, yInput * movementSpeed * Time.deltaTime);
        rigidbody2D.MovePosition(rigidbody2D.position + move);
    }


    private void ResetAnimationTrigger()
    {
        toolEffect = ToolEffect.none;
        isUsingToolRight = false;
        isUsingToolLeft = false;
        isUsingToolUp = false;
        isUsingToolDown = false;
        isLiftingToolRight = false;
        isLiftingToolLeft = false;
        isLiftingToolUp = false;
        isLiftingToolDown = false;
        isPickingRight = false;
        isPickingLeft = false;
        isPickingUp = false;
        isPickingDown = false;
        isSwingToolRight = false;
        isSwingToolLeft = false;
        isSwingToolUp = false;
        isSwingToolDown = false;
    }

    private void PlayerMovementInput()
    {
        xInput = Input.GetAxisRaw("Horizontal");
        yInput = Input.GetAxisRaw("Vertical");

        // 斜着移动
        if (xInput != 0 && yInput != 0) 
        {
            xInput = xInput * 0.71f;
            yInput = yInput * 0.71f;
        }

        // 在移动
        if (xInput != 0 || yInput != 0) 
        {
            isRunning = true;
            isWalking = false;
            isIdle = false;
            movementSpeed = Settings.runningSpeed;

            // Capture player direction for save game
            if (xInput < 0)
            {
                playerDirection = Direction.left;
            }
            else if (xInput > 0)
            {
                playerDirection = Direction.right;
            }
            else if (yInput < 0)
            {
                playerDirection = Direction.down;
            }
            else
            {
                playerDirection = Direction.up;
            }

        }
        else if(xInput == 0 && yInput == 0)
        {
            isRunning = false;
            isWalking = false;
            isIdle = true;
        }
    }

    // 按住Shift键移动为walk
    private void PlayerWalkInput()
    {
        if(Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
        {
            isRunning = false;
            isWalking = true;
            isIdle = false;
            movementSpeed = Settings.walkingSpeed;
        }
        else
        {
            isRunning = true;
            isWalking = false;
            isIdle = false;
            movementSpeed = Settings.runningSpeed;
        }
    }

    public Vector3 GetPlayerViewportPosition()
    {
        // Vector3 viewport position for player (0,0) viewport bottom left, (1,1) viewport top right
        return mainCamera.WorldToViewportPoint(gameObject.transform.position);
    }

    public void DisablePlayerInputAndResetMovement()
    {
        DisablePlayerInpupt();
        ResetMovement();

        // Send event to any listeners for player movement input
        EventHandler.CallMovementEvent(xInput, yInput, isWalking, isRunning, isIdle, isCarrying, toolEffect,
                isUsingToolRight, isUsingToolLeft, isUsingToolUp, isUsingToolDown,
                isLiftingToolRight, isLiftingToolLeft, isLiftingToolUp, isLiftingToolDown,
                isPickingRight, isPickingLeft, isPickingUp, isPickingDown,
                isSwingToolRight, isSwingToolLeft, isSwingToolUp, isSwingToolDown,
                false, false, false, false);
    }

    private void ResetMovement()
    {
        // Reset movement
        xInput = 0f;
        yInput = 0f;
        isRunning = false;
        isWalking = false;
        isIdle = true;
    }

    public void DisablePlayerInpupt()
    {
        PlayerInputIsDisabled = true;
    }


    public void EnablePlayerInput()
    {
        PlayerInputIsDisabled = false;
    }
}

(3)修改UIInventoryBar.cs

创建对可拖动物体的引用。

public GameObject inventoryBarDraggedItem;

当我们从UIInventorySlot中取出道具时会填充该字段。

此时UIInventoryBar.cs的完整代码为:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIInventoryBar : MonoBehaviour
{
    [SerializeField] private Sprite blank16x16sprite = null; // 插槽默认图案
    [SerializeField] private UIInventorySlot[] inventorySlot = null; // 所有插槽集合
    public GameObject inventoryBarDraggedItem;

    private RectTransform rectTransform;

    private bool _isInventoryBarPositionBottom = true;

    public bool IsInventoryBarPositionBottom { get { return _isInventoryBarPositionBottom;} set { _isInventoryBarPositionBottom = value; } }

    private void Awake()
    {
        rectTransform = GetComponent<RectTransform>();
    }

    private void OnDisable()
    {
        EventHandler.InventoryUpdatedEvent -= InventoryUpdated;
    }

    private void InventoryUpdated(InventoryLocation inventoryLocation, List<InventoryItem> inventoryList)
    {
        if (inventoryLocation == InventoryLocation.player)
        {
            ClearInventorySlots();

            if (inventorySlot.Length > 0 && inventoryList.Count > 0)
            {
                for (int i = 0; i < inventorySlot.Length; i++)
                {
                    if (i < inventoryList.Count)
                    {
                        int itemCode = inventoryList[i].itemCode;

                        ItemDetails itemDetails = InventoryManager.Instance.GetItemDetails(itemCode);

                        if (itemDetails != null)
                        {
                            // add images and details to inventory item slot
                            inventorySlot[i].inventorySlotImage.sprite = itemDetails.itemSprite;
                            inventorySlot[i].textMeshProUGUI.text = inventoryList[i].itemQuantity.ToString();
                            inventorySlot[i].itemDetails = itemDetails;
                            inventorySlot[i].itemQuantity = inventoryList[i].itemQuantity;
                        }
                    }
                    else
                    {
                        break;
                    }

                }
            }
        }
    }

    private void ClearInventorySlots()
    {
        if(inventorySlot.Length > 0)
        {
            // loop through inventory slots and update with blank sprite
            for(int i = 0; i < inventorySlot.Length; i++)
            {
                inventorySlot[i].inventorySlotImage.sprite = blank16x16sprite;
                inventorySlot[i].textMeshProUGUI.text = "";
                inventorySlot[i].itemDetails = null;
                inventorySlot[i].itemQuantity = 0;
            }
        }
    }

    private void OnEnable()
    {
        EventHandler.InventoryUpdatedEvent += InventoryUpdated;
    }

    private void Update()
    {
        // Switch inventory bar position depending on player position
        SwitchInventoryBarPosition();
    }

    private void SwitchInventoryBarPosition()
    {
        Vector3 playerViewportPosition = Player.Instance.GetPlayerViewportPosition();

        if (playerViewportPosition.y > 0.3f && IsInventoryBarPositionBottom == false)
        {
            rectTransform.pivot = new Vector2(0.5f, 0f);
            rectTransform.anchorMin = new Vector2(0.5f, 0f);
            rectTransform.anchorMax = new Vector2(0.5f, 0f);
            rectTransform.anchoredPosition = new Vector2(0f, 2.5f);

            IsInventoryBarPositionBottom = true;
        }
        else if (playerViewportPosition.y <= 0.3f && IsInventoryBarPositionBottom == true) 
        {
            rectTransform.pivot = new Vector2(0.5f, 1f);
            rectTransform.anchorMin = new Vector2(0.5f, 1f);
            rectTransform.anchorMax = new Vector2(0.5f, 1f);
            rectTransform.anchoredPosition = new Vector2(0f, -2.5f);

            IsInventoryBarPositionBottom = false;
        }
    }
}

(4)创建Dragged Item预制体

在Hierarchy -> PersistentScene -> UI -> MainGameUICanvas -> UICanvasGroup -> UIInventoryBar下创建InventoryDraggedItem空物体。设置其属性如下:

设置Sort Order=2是为了:当在UIInventoryBar上拖动时,能够更高优先级展示,从而不被Slot上的物体覆盖。

在InventoryDraggedItem下再创建Item空对象,其属性设置如下:

然后将InventoryDraggedItem拖到Assets -> Prefabs -> UI下

然后再删除Hierarchy中的InventoryDraggedItem对象。

点击InventoryDraggedItem预制体,设置其Render Mode为Screen Space - Overlay,勾选Pixel Perfect。

(5)设置inventoryBarDraggedItem属性

点击Hierarchy中的UIInventoryBar对象,将InventoryDraggedItem预制体拖入inventoryBarDraggedItem属性。

4、优化UIInventorySlot脚本

using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;

public class UIInventorySlot : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    private Camera mainCamera;
    private Transform parentItem; // 场景中的物体父类
    private GameObject draggedItem; // 被拖动的物体

    public Image inventorySlotHighlight;
    public Image inventorySlotImage;
    public TextMeshProUGUI textMeshProUGUI;

    [SerializeField] private UIInventoryBar inventoryBar = null;
    [SerializeField] private GameObject itemPrefab = null;

    [HideInInspector] public ItemDetails itemDetails;
    [HideInInspector] public int itemQuantity;

    private void Start()
    {
        mainCamera = Camera.main;
        parentItem = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        if(itemDetails != null) 
        {
            // Disable keyboard input
            Player.Instance.DisablePlayerInputAndResetMovement();

            // Instatiate gameobject as dragged item
            draggedItem = Instantiate(inventoryBar.inventoryBarDraggedItem, inventoryBar.transform);

            // Get image for dragged item
            Image draggedItemImage = draggedItem.GetComponentInChildren<Image>();
            draggedItemImage.sprite = inventorySlotImage.sprite;
        }
    }

    public void OnDrag(PointerEventData eventData)
    {
        // move game object as dragged item
        if(!draggedItem != null)
        {
            draggedItem.transform.position = Input.mousePosition;
        }
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        // Destroy game object as dragged item
        if (draggedItem != null) 
        {
            Destroy(draggedItem);

            // if drag ends over inventory bar, get item drag is over and swap then
            if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.GetComponent<UIInventorySlot>() != null) 
            {
                
            }
            else
            {
                // else attemp to drop the item if it can be dropped
                if (itemDetails.canBeDropped)
                {
                    DropSelectedItemAtMousePosition();
                }
            }

            // Enable player input
            Player.Instance.EnablePlayerInput();
        }
    }

    /// <summary>
    /// Drops the item(if selected) at the current mouse position. called by the DropItem event
    /// </summary>
    private void DropSelectedItemAtMousePosition()
    {
        if(itemDetails != null)
        {
            Vector3 worldPosition = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, -mainCamera.transform.position.z));

            // Create item from prefab at mouse position
            GameObject itemGameObject = Instantiate(itemPrefab, worldPosition, Quaternion.identity, parentItem);
            Item item = itemGameObject.GetComponent<Item>();
            item.ItemCode = itemDetails.itemCode;

            // Remove item from player's inventory
            InventoryManager.Instance.RemoveItem(InventoryLocation.player, item.ItemCode);
        }
    }
}

1)针对draggedItemImage.sprite = inventorySlotImage.sprite;的代码:

当点击/拖动某个具体Slot时,就会执行该Slot对应的UIInventorySlot脚本,也可以获取到对应的Slot Image信息。

2)针对item.ItemCode = itemDetails.itemCode;

itemDetails属性为UIInventorySlot的public属性。

当添加一个物体时,会触发CallInventoryUpdatedEvent事件,从而更新每个Slot的itemDetails值。

5、优化InventoryManager脚本

增加RemoveItem和RemoveItemAtPosition两个函数。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InventoryManager : SingletonMonobehaviour<InventoryManager>
{
    private Dictionary<int, ItemDetails> itemDetailsDictionary;

    public List<InventoryItem>[] inventoryLists; // 每个位置的库存清单

    // 每个位置的库存数。 The index of the array is the inventory list(from the
    // InventoryLocation enum), and the value is the capacity of that inventory list
    [HideInInspector] public int[] inventoryListCapacityIntArray; 


    [SerializeField] private SO_ItemList itemList = null;

    protected override void Awake()
    {
        base.Awake();

        // Create Inventory lists
        CreateInventoryLists();

        // Create item details dictionary
        CreateItemDetailsDictionary();
    }

    private void CreateInventoryLists()
    {
        inventoryLists = new List<InventoryItem>[(int)InventoryLocation.count];

        for (int i = 0; i < (int)InventoryLocation.count; i++)
        {
            inventoryLists[i] = new List<InventoryItem>();
        }

        // initialize inventory list capacity array
        inventoryListCapacityIntArray = new int[(int)InventoryLocation.count];

        // initialize player inventory list capacity
        inventoryListCapacityIntArray[(int)InventoryLocation.player] = Settings.playerInitialInventoryCapacity;
    }

    /// <summary>
    /// Populates the itemDetailsDictionary from the scriptable object items list
    /// </summary>
    private void CreateItemDetailsDictionary()
    {
        itemDetailsDictionary = new Dictionary<int, ItemDetails>();

        foreach (ItemDetails itemDetails in itemList.itemDetails) 
        {
            itemDetailsDictionary.Add(itemDetails.itemCode, itemDetails);
        }
    }

    /// <summary>
    /// Add an item to the inventory list for the inventoryLocation and then destroy the gameObjectToDelete
    /// 角色拾取到物品后,物品需要消失掉
    /// </summary>
    /// <param name="inventoryLocation"></param>
    /// <param name="item"></param>
    /// <param name="gameObjectToDelete"></param>
    public void AddItem(InventoryLocation inventoryLocation, Item item, GameObject gameObjectToDelete)
    {
        AddItem(inventoryLocation, item);

        Destroy(gameObjectToDelete);
    }


    /// <summary>
    /// Add an item to the inventory list for the inventoryLocation
    /// </summary>
    /// <param name="inventoryLocation"></param>
    /// <param name="item"></param>
    public void AddItem(InventoryLocation inventoryLocation, Item item)
    {
        int itemCode = item.ItemCode;
        List<InventoryItem> inventoryList = inventoryLists[(int)inventoryLocation];

        // Check if inventory already contains the item
        int itemPosition = FindItemInInventory(inventoryLocation, itemCode);

        if(itemPosition != -1)
        {
            AddItemPosition(inventoryList, itemCode, itemPosition);
        }
        else
        {
            AddItemPosition(inventoryList, itemCode);
        }

        // Send event that inventory has been updated
        EventHandler.CallInventoryUpdatedEvent(inventoryLocation, inventoryLists[(int)inventoryLocation]);  
    }

    /// <summary>
    /// Add item to position in the inventory
    /// </summary>
    /// <param name="inventoryList"></param>
    /// <param name="itemCode"></param>
    /// <param name="position"></param>
    /// <exception cref="NotImplementedException"></exception>
    private void AddItemPosition(List<InventoryItem> inventoryList, int itemCode, int position)
    {
        InventoryItem inventoryItem = new InventoryItem();

        int quantity = inventoryList[position].itemQuantity + 1;
        inventoryItem.itemQuantity = quantity;
        inventoryItem.itemCode = itemCode;
        inventoryList[position] = inventoryItem;

        Debug.ClearDeveloperConsole();
        //DebugPrintInventoryList(inventoryList);
    }

    /// <summary>
    /// Remove an item from the inventory, and create a game object at the position it was dropped
    /// </summary>
    /// <param name="inventoryLocation"></param>
    /// <param name="itemCode"></param>
    public void RemoveItem(InventoryLocation inventoryLocation, int itemCode)
    {
        List<InventoryItem> inventoryList = inventoryLists[(int)inventoryLocation];

        // Check if inventory already contains the item
        int itemPosition = FindItemInInventory(inventoryLocation, itemCode);

        if(itemPosition != -1)
        {
            RemoveItemAtPosition(inventoryList, itemCode, itemPosition);
        }

        // Send event that inventory has been updated
        EventHandler.CallInventoryUpdatedEvent(inventoryLocation, inventoryLists[(int)inventoryLocation]);

    }

    private void RemoveItemAtPosition(List<InventoryItem> inventoryList, int itemCode, int position)
    {
        InventoryItem inventoryItem = new InventoryItem();

        int quantity = inventoryList[position].itemQuantity - 1;

        if(quantity > 0)
        {
            inventoryItem.itemQuantity = quantity;
            inventoryItem.itemCode = itemCode;
            inventoryList[position] = inventoryItem;
        }
        else
        {
            inventoryList.RemoveAt(position);   
        }
    }

    private void DebugPrintInventoryList(List<InventoryItem> inventoryList)
    {
        foreach(InventoryItem inventoryItem in inventoryList)
        {
            Debug.Log("Item Description:" + InventoryManager.Instance.GetItemDetails(inventoryItem.itemCode).itemDescription + "    Item Quantity:" + inventoryItem.itemQuantity);
        }

        Debug.Log("*******************************************************************************");
    }

    /// <summary>
    /// Add item to the end of the inventory 
    /// </summary>
    /// <param name="inventoryList"></param>
    /// <param name="itemCode"></param>
    /// <exception cref="NotImplementedException"></exception>
    private void AddItemPosition(List<InventoryItem> inventoryList, int itemCode)
    {
        InventoryItem inventoryItem = new InventoryItem(); 

        inventoryItem.itemCode = itemCode;
        inventoryItem.itemQuantity = 1;
        inventoryList.Add(inventoryItem);

        //DebugPrintInventoryList(inventoryList);
    }



    /// <summary>
    /// Find if an itemCode is already in the inventory. Returns the item position
    /// in the inventory list, or -1 if the item is not in the inventory
    /// </summary>
    /// <param name="inventoryLocation"></param>
    /// <param name="itemCode"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    private int FindItemInInventory(InventoryLocation inventoryLocation, int itemCode)
    {
        List<InventoryItem> inventoryList = inventoryLists[(int)inventoryLocation];

        for (int i = 0; i < inventoryList.Count; i++)
        {
            if(inventoryList[i].itemCode == itemCode)
            {
                return i;
            }
        }

        return -1;
    }




    /// <summary>
    /// Returns the itemDetails (from the SO_ItemList) for the itemCode, or null if the item doesn't exist
    /// </summary>
    /// <param name="itemCode"></param>
    /// <returns></returns>
    public ItemDetails GetItemDetails(int itemCode) 
    {
        ItemDetails itemDetails;

        if(itemDetailsDictionary.TryGetValue(itemCode, out itemDetails))
        {
            return itemDetails;
        }
        else
        {
            return null;
        }
    }
}

6、填充InventorySlot面板属性

这两个属性没法从prefab中直接选择对象进行设置,所以需要在InventorySlot实例化之后的对象们中一一进行设置。

先同时选中UIInventorySlot0~11,然后设置属性如下:

此时运行程序,即可实现从库存栏中拖拽物品到游戏场景中。

7、交换库存栏中商品的位置

(1)修改UIInventorySlot脚本

首先,添加slotNumber用于标记插槽的序列号。

[SerializeField] private int slotNumber = 0; // 插槽的序列号

然后当点击结束处为slot对象时,交换两个slot的位置。

此时UIInventorySlot的完整代码如下:

using UnityEngine;
using TMPro;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System;

public class UIInventorySlot : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    private Camera mainCamera;
    private Transform parentItem; // 场景中的物体父类
    private GameObject draggedItem; // 被拖动的物体

    public Image inventorySlotHighlight;
    public Image inventorySlotImage;
    public TextMeshProUGUI textMeshProUGUI;

    [SerializeField] private UIInventoryBar inventoryBar = null;
    [SerializeField] private GameObject itemPrefab = null;
    [SerializeField] private int slotNumber = 0; // 插槽的序列号

    [HideInInspector] public ItemDetails itemDetails;
    [HideInInspector] public int itemQuantity;

    private void Start()
    {
        mainCamera = Camera.main;
        parentItem = GameObject.FindGameObjectWithTag(Tags.ItemsParentTransform).transform;
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        if(itemDetails != null) 
        {
            // Disable keyboard input
            Player.Instance.DisablePlayerInputAndResetMovement();

            // Instatiate gameobject as dragged item
            draggedItem = Instantiate(inventoryBar.inventoryBarDraggedItem, inventoryBar.transform);

            // Get image for dragged item
            Image draggedItemImage = draggedItem.GetComponentInChildren<Image>();
            draggedItemImage.sprite = inventorySlotImage.sprite;
        }
    }

    public void OnDrag(PointerEventData eventData)
    {
        // move game object as dragged item
        if(!draggedItem != null)
        {
            draggedItem.transform.position = Input.mousePosition;
        }
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        // Destroy game object as dragged item
        if (draggedItem != null) 
        {
            Destroy(draggedItem);

            // if drag ends over inventory bar, get item drag is over and swap then
            if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.GetComponent<UIInventorySlot>() != null) 
            {
                // get the slot number where the drag ended
                int toSlotNumber = eventData.pointerCurrentRaycast.gameObject.GetComponent<UIInventorySlot>().slotNumber;

                // Swap inventory items in inventory list
                InventoryManager.Instance.SwapInventoryItems(InventoryLocation.player, slotNumber, toSlotNumber);
            }
            else
            {
                // else attemp to drop the item if it can be dropped
                if (itemDetails.canBeDropped)
                {
                    DropSelectedItemAtMousePosition();
                }
            }

            // Enable player input
            Player.Instance.EnablePlayerInput();
        }
    }

    /// <summary>
    /// Drops the item(if selected) at the current mouse position. called by the DropItem event
    /// </summary>
    private void DropSelectedItemAtMousePosition()
    {
        if(itemDetails != null)
        {
            Vector3 worldPosition = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, -mainCamera.transform.position.z));

            // Create item from prefab at mouse position
            GameObject itemGameObject = Instantiate(itemPrefab, worldPosition, Quaternion.identity, parentItem);
            Item item = itemGameObject.GetComponent<Item>();
            item.ItemCode = itemDetails.itemCode;

            // Remove item from player's inventory
            InventoryManager.Instance.RemoveItem(InventoryLocation.player, item.ItemCode);
        }
    }
}

(2)修改InventoryManager脚本

新增如下代码:

public void SwapInventoryItems(InventoryLocation inventoryLocation, int fromItem, int toItem)
{
    // if fromItem index and toItemIndex are within the bounds of the list, not the same, and greater than or equal to zero
    if(fromItem < inventoryLists[(int)inventoryLocation].Count && toItem < inventoryLists[(int)inventoryLocation].Count
        && fromItem != toItem && fromItem >= 0 && toItem >= 0)
    {
        InventoryItem fromInventoryItem = inventoryLists[(int)inventoryLocation][fromItem];
        InventoryItem toInventoryItem = inventoryLists[(int)inventoryLocation][toItem];

        inventoryLists[(int)inventoryLocation][toItem] = fromInventoryItem;
        inventoryLists[(int)inventoryLocation][fromItem] = toInventoryItem;

        // Send event that inventory has been updated
        EventHandler.CallInventoryUpdatedEvent(inventoryLocation, inventoryLists[(int)inventoryLocation]);
    }
}

此时InventoryManager.cs完整代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InventoryManager : SingletonMonobehaviour<InventoryManager>
{
    private Dictionary<int, ItemDetails> itemDetailsDictionary;

    public List<InventoryItem>[] inventoryLists; // 每个位置的库存清单

    // 每个位置的库存数。 The index of the array is the inventory list(from the
    // InventoryLocation enum), and the value is the capacity of that inventory list
    [HideInInspector] public int[] inventoryListCapacityIntArray; 


    [SerializeField] private SO_ItemList itemList = null;

    protected override void Awake()
    {
        base.Awake();

        // Create Inventory lists
        CreateInventoryLists();

        // Create item details dictionary
        CreateItemDetailsDictionary();
    }

    private void CreateInventoryLists()
    {
        inventoryLists = new List<InventoryItem>[(int)InventoryLocation.count];

        for (int i = 0; i < (int)InventoryLocation.count; i++)
        {
            inventoryLists[i] = new List<InventoryItem>();
        }

        // initialize inventory list capacity array
        inventoryListCapacityIntArray = new int[(int)InventoryLocation.count];

        // initialize player inventory list capacity
        inventoryListCapacityIntArray[(int)InventoryLocation.player] = Settings.playerInitialInventoryCapacity;
    }

    /// <summary>
    /// Populates the itemDetailsDictionary from the scriptable object items list
    /// </summary>
    private void CreateItemDetailsDictionary()
    {
        itemDetailsDictionary = new Dictionary<int, ItemDetails>();

        foreach (ItemDetails itemDetails in itemList.itemDetails) 
        {
            itemDetailsDictionary.Add(itemDetails.itemCode, itemDetails);
        }
    }

    /// <summary>
    /// Add an item to the inventory list for the inventoryLocation and then destroy the gameObjectToDelete
    /// 角色拾取到物品后,物品需要消失掉
    /// </summary>
    /// <param name="inventoryLocation"></param>
    /// <param name="item"></param>
    /// <param name="gameObjectToDelete"></param>
    public void AddItem(InventoryLocation inventoryLocation, Item item, GameObject gameObjectToDelete)
    {
        AddItem(inventoryLocation, item);

        Destroy(gameObjectToDelete);
    }


    /// <summary>
    /// Add an item to the inventory list for the inventoryLocation
    /// </summary>
    /// <param name="inventoryLocation"></param>
    /// <param name="item"></param>
    public void AddItem(InventoryLocation inventoryLocation, Item item)
    {
        int itemCode = item.ItemCode;
        List<InventoryItem> inventoryList = inventoryLists[(int)inventoryLocation];

        // Check if inventory already contains the item
        int itemPosition = FindItemInInventory(inventoryLocation, itemCode);

        if(itemPosition != -1)
        {
            AddItemPosition(inventoryList, itemCode, itemPosition);
        }
        else
        {
            AddItemPosition(inventoryList, itemCode);
        }

        // Send event that inventory has been updated
        EventHandler.CallInventoryUpdatedEvent(inventoryLocation, inventoryLists[(int)inventoryLocation]);  
    }

    /// <summary>
    /// Add item to position in the inventory
    /// </summary>
    /// <param name="inventoryList"></param>
    /// <param name="itemCode"></param>
    /// <param name="position"></param>
    /// <exception cref="NotImplementedException"></exception>
    private void AddItemPosition(List<InventoryItem> inventoryList, int itemCode, int position)
    {
        InventoryItem inventoryItem = new InventoryItem();

        int quantity = inventoryList[position].itemQuantity + 1;
        inventoryItem.itemQuantity = quantity;
        inventoryItem.itemCode = itemCode;
        inventoryList[position] = inventoryItem;

        Debug.ClearDeveloperConsole();
        //DebugPrintInventoryList(inventoryList);
    }

    /// <summary>
    /// Remove an item from the inventory, and create a game object at the position it was dropped
    /// </summary>
    /// <param name="inventoryLocation"></param>
    /// <param name="itemCode"></param>
    public void RemoveItem(InventoryLocation inventoryLocation, int itemCode)
    {
        List<InventoryItem> inventoryList = inventoryLists[(int)inventoryLocation];

        // Check if inventory already contains the item
        int itemPosition = FindItemInInventory(inventoryLocation, itemCode);

        if(itemPosition != -1)
        {
            RemoveItemAtPosition(inventoryList, itemCode, itemPosition);
        }

        // Send event that inventory has been updated
        EventHandler.CallInventoryUpdatedEvent(inventoryLocation, inventoryLists[(int)inventoryLocation]);

    }

    private void RemoveItemAtPosition(List<InventoryItem> inventoryList, int itemCode, int position)
    {
        InventoryItem inventoryItem = new InventoryItem();

        int quantity = inventoryList[position].itemQuantity - 1;

        if(quantity > 0)
        {
            inventoryItem.itemQuantity = quantity;
            inventoryItem.itemCode = itemCode;
            inventoryList[position] = inventoryItem;
        }
        else
        {
            inventoryList.RemoveAt(position);   
        }
    }

    /// <summary>
    /// Swap item at fromItem index with item at toItem index in inventoryLocation inventory list
    /// </summary>
    /// <param name="inventoryLocation"></param>
    /// <param name="fromItem"></param>
    /// <param name="toItem"></param>
    public void SwapInventoryItems(InventoryLocation inventoryLocation, int fromItem, int toItem)
    {
        // if fromItem index and toItemIndex are within the bounds of the list, not the same, and greater than or equal to zero
        if(fromItem < inventoryLists[(int)inventoryLocation].Count && toItem < inventoryLists[(int)inventoryLocation].Count
            && fromItem != toItem && fromItem >= 0 && toItem >= 0)
        {
            InventoryItem fromInventoryItem = inventoryLists[(int)inventoryLocation][fromItem];
            InventoryItem toInventoryItem = inventoryLists[(int)inventoryLocation][toItem];

            inventoryLists[(int)inventoryLocation][toItem] = fromInventoryItem;
            inventoryLists[(int)inventoryLocation][fromItem] = toInventoryItem;

            // Send event that inventory has been updated
            EventHandler.CallInventoryUpdatedEvent(inventoryLocation, inventoryLists[(int)inventoryLocation]);
        }
    }


    private void DebugPrintInventoryList(List<InventoryItem> inventoryList)
    {
        foreach(InventoryItem inventoryItem in inventoryList)
        {
            Debug.Log("Item Description:" + InventoryManager.Instance.GetItemDetails(inventoryItem.itemCode).itemDescription + "    Item Quantity:" + inventoryItem.itemQuantity);
        }

        Debug.Log("*******************************************************************************");
    }

    /// <summary>
    /// Add item to the end of the inventory 
    /// </summary>
    /// <param name="inventoryList"></param>
    /// <param name="itemCode"></param>
    /// <exception cref="NotImplementedException"></exception>
    private void AddItemPosition(List<InventoryItem> inventoryList, int itemCode)
    {
        InventoryItem inventoryItem = new InventoryItem(); 

        inventoryItem.itemCode = itemCode;
        inventoryItem.itemQuantity = 1;
        inventoryList.Add(inventoryItem);

        //DebugPrintInventoryList(inventoryList);
    }



    /// <summary>
    /// Find if an itemCode is already in the inventory. Returns the item position
    /// in the inventory list, or -1 if the item is not in the inventory
    /// </summary>
    /// <param name="inventoryLocation"></param>
    /// <param name="itemCode"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    private int FindItemInInventory(InventoryLocation inventoryLocation, int itemCode)
    {
        List<InventoryItem> inventoryList = inventoryLists[(int)inventoryLocation];

        for (int i = 0; i < inventoryList.Count; i++)
        {
            if(inventoryList[i].itemCode == itemCode)
            {
                return i;
            }
        }

        return -1;
    }




    /// <summary>
    /// Returns the itemDetails (from the SO_ItemList) for the itemCode, or null if the item doesn't exist
    /// </summary>
    /// <param name="itemCode"></param>
    /// <returns></returns>
    public ItemDetails GetItemDetails(int itemCode) 
    {
        ItemDetails itemDetails;

        if(itemDetailsDictionary.TryGetValue(itemCode, out itemDetails))
        {
            return itemDetails;
        }
        else
        {
            return null;
        }
    }
}

(3)更新Inspector中的slotNumber值

依次修改UIInventorySlot0~11中的SlotNumber值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/950381.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Sprint Boot教程之五十:Spring Boot JpaRepository 示例

Spring Boot JpaRepository 示例 Spring Boot建立在 Spring 之上&#xff0c;包含 Spring 的所有功能。由于其快速的生产就绪环境&#xff0c;使开发人员能够直接专注于逻辑&#xff0c;而不必费力配置和设置&#xff0c;因此如今它正成为开发人员的最爱。Spring Boot 是一个基…

C++ STL map和set的使用

序列式容器和关联式容器 想必大家已经接触过一些容器如&#xff1a;list&#xff0c;vector&#xff0c;deque&#xff0c;array&#xff0c;forward_list&#xff0c;string等&#xff0c;这些容器统称为系列容器。因为逻辑结构为线性的&#xff0c;两个位置的存储的值一般是…

人工智能及深度学习的一些题目(三)

1、【填空题】 使用RNNCTC模型进行语音识别&#xff0c;在产生预测输出时&#xff0c;对于输入的音频特征序列通过网络预测产生对应的字母序列&#xff0c;可以使用&#xff08; beamsearch &#xff09;算法进行最优路径搜索。 2、【填空题】 逻辑回归模型属于有监督学习中的&…

《C++11》右值引用深度解析:性能优化的秘密武器

C11引入了一个新的概念——右值引用&#xff0c;这是一个相当深奥且重要的概念。为了理解右值引用&#xff0c;我们需要先理解左值和右值的概念&#xff0c;然后再理解左值引用和右值引用。本文将详细解析这些概念&#xff0c;并通过实例进行说明&#xff0c;以揭示右值引用如何…

cp命令详解

&#x1f3dd;️专栏&#xff1a;计算机操作系统 &#x1f305;主页&#xff1a;猫咪-9527主页 “欲穷千里目&#xff0c;更上一层楼。会当凌绝顶&#xff0c;一览众山小。” 目录 1. 基本功能 2. 命令语法 3. 常用选项 4. 常见用法示例 4.1 复制单个文件 4.2 递归复制目录…

Git的学习和常见问题

文章目录 1.初始化配置2.新建仓库3.添加和提交文件4.git reset 回退版本5.git diff 查看差异6.git rm 删除文件7.文件 .gitigonre8.克隆远程仓库9.将已有的本地仓库关联到远程仓库10.分支的基本操作11.解决合并冲突配置问题 最近基于GeekHour的视频学习Git&#xff0c;记录了一…

《Mcal》--MCU模块

一、MCU模块的主要功能 控制系统时钟的产生。控制系统通用模块&#xff0c;该模块会涉及到Adc、Ftm等外设的配置。控制外设时钟。控制MCU运行的模式。初始化定义RAM Section。 比较重要的是时钟的配置。 二、系统时钟的配置 1、芯片时钟树 要想弄明白时钟配置&#xff0c;需…

【每日学点鸿蒙知识】查看触摸热区范围、直接赋值到剪贴板、组件截图、横竖屏切换、防截图等

1、如何查看触摸热区范围&#xff1f; 前只能通过自定义的方式获取responseRegion。参考文档&#xff1a;触摸热区设置 Entry Component struct TouchTargetExample {State text: string State x:number 0State y:number 0State reg_width:string 50%State reg_height:st…

ThinkPHP 8高效构建Web应用-获取请求对象

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…

记一次k8s下容器启动失败,容器无日志问题排查

问题 背景 本地开发时&#xff0c;某应用增加logback-spring.xml配置文件&#xff0c;加入必要的依赖&#xff1a; <dependency><groupId>net.logstash.logback</groupId><artifactId>logstash-logback-encoder</artifactId><version>8…

STM32烧写失败之Contents mismatch at: 0800005CH (Flash=FFH Required=29H) !

一&#xff09;问题&#xff1a;用ULINK2给STM32F103C8T6下载程序&#xff0c;下载方式设置如下&#xff1a; 出现下面两个问题&#xff1a; 1&#xff09;下载问题界面如下&#xff1a; 这个错误的信息大概可以理解为&#xff0c;在0x08000063地址上读取到flash存储为FF&am…

vscode通过ssh连接服务器实现免密登录

一、通过ssh连接服务器 1、打开vscode&#xff0c;进入拓展&#xff08;CtrlShiftX&#xff09;&#xff0c;下载拓展Remote - SSH。 2、点击远程资源管理器选项卡&#xff0c;选择远程&#xff08;隧道/SSH&#xff09;类别。 3、点击SSH配置。 4、在中间上部分弹出的配置文件…

在Nvidia Jetson ADX Orin中使用TensorRT-LLM运行llama3-8b

目录 背景&#xff1a;步骤 1.获取模型权重第 2 步&#xff1a;准备第 3 步&#xff1a;构建 TensorRT-LLM 引擎 背景&#xff1a; 大型语言模型 &#xff08;LLM&#xff09; 推理的关键瓶颈在于 GPU 内存资源短缺。因此&#xff0c;各种加速框架主要强调减少峰值 GPU 内存使…

Unity Shader学习日记 part4 Shader 基础结构

其实在这一篇之前&#xff0c;应该还有一个关于坐标空间转换的内容&#xff0c;但是内容囤积的有些多&#xff0c;就先把Shader的基础结构先记录一下。 笔记主要记录在代码中&#xff0c;所以知识点主要是图和代码的展示。 Unity Shader分类 在Unity中&#xff0c;Shader的种…

特征点检测与匹配——MATLAB R2022b

特征点检测与匹配在计算机视觉中的作用至关重要,它为图像处理、物体识别、增强现实等领域提供了坚实的基础。 目录 Harris角点检测 SIFT(尺度不变特征变换) SURF(加速稳健特征) ORB(Oriented FAST and Rotated BRIEF) 总结 特征点检测与匹配是计算机视觉中的一项基…

Airflow:HttpSensor实现API驱动数据流程

数据管道工作流通常依赖于api来访问、获取和处理来自外部系统的数据。为了处理这些场景&#xff0c;Apache Airflow提供了HttpSensor&#xff0c;这是一个内置的Sensor&#xff0c;用于监视HTTP请求的状态&#xff0c;并在满足指定条件时触发后续任务。在这篇博文中&#xff0c…

图数据库 | 17、高可用分布式设计(上)

我们在前面的文章中&#xff0c;探索了多种可能的系统扩展方式&#xff0c;以及每种扩展方式的优劣。 本篇文章将通过具体的架构设计方案来对每一种方案的设计、投入产出比、各项指标与功能&#xff0c;以及孰优孰劣等进行评价。 在设计高性能、高可用图数据库的时候&#xf…

JAVA学习记录1

文章为个人学习记录&#xff0c;仅供参考&#xff0c;如有错误请指出。 什么是JAVA&#xff1f; JAVA是一种高级的编程语言&#xff0c;可以用于开发大部分场景的软件&#xff0c;但主要用于服务器的开发。 什么是JDK&#xff1f; 类似于python使用PyCharm来编写代码&#…

css中的部分文字特性

文章目录 一、writing-mode二、word-break三、word-spacing;四、white-space五、省略 总结归纳常见文字特性&#xff0c;后续补充 一、writing-mode 默认horizontal-tbwriting-mode: vertical-lr; 从第一排开始竖着排&#xff0c;到底部再换第二排&#xff0c;文字与文字之间从…

Android wifi常见问题及分析

参考 Android Network/WiFi 那些事儿 前言 本文将讨论几个有意思的网络问题&#xff0c;同时介绍 Android 上常见WiFi 问题的分析思路。 网络基础Q & A 一. 网络分层缘由 分层想必大家很熟悉&#xff0c;是否想过为何需要这样分层&#xff1f; 网上大多都是介绍每一层…