Mã hóa một hệ thống kiểm kê đơn giản bằng tính năng kéo và thả giao diện người dùng trong Unity

Nhiều trò chơi cho phép người chơi thu thập và mang theo một số lượng lớn vật phẩm (ví dụ: trò chơi RTS/MOBA/RPG, trò chơi nhập vai hành động, v.v.), đó là lúc Kho đồ phát huy tác dụng.

Inventory là một bảng gồm các phần tử cung cấp khả năng truy cập nhanh vào các vật phẩm của người chơi và cách đơn giản để sắp xếp chúng.

Hệ thống kiểm kê Diablo 3

Trong bài đăng này, chúng ta sẽ học cách lập trình một Hệ thống khoảng không quảng cáo đơn giản với tính năng Nhận vật phẩm và Kéo và thả giao diện người dùng trong Unity.

Bước 1: Tạo tập lệnh

Hướng dẫn này yêu cầu 3 tập lệnh:

SC_CharacterController.cs

//You are free to use this script in Free or Commercial projects
//sharpcoderblog.com @2019

using UnityEngine;

[RequireComponent(typeof(CharacterController))]

public class SC_CharacterController : MonoBehaviour
{
    public float speed = 7.5f;
    public float jumpSpeed = 8.0f;
    public float gravity = 20.0f;
    public Camera playerCamera;
    public float lookSpeed = 2.0f;
    public float lookXLimit = 60.0f;

    CharacterController characterController;
    Vector3 moveDirection = Vector3.zero;
    Vector2 rotation = Vector2.zero;

    [HideInInspector]
    public bool canMove = true;

    void Start()
    {
        characterController = GetComponent<CharacterController>();
        rotation.y = transform.eulerAngles.y;
    }

    void Update()
    {
        if (characterController.isGrounded)
        {
            // We are grounded, so recalculate move direction based on axes
            Vector3 forward = transform.TransformDirection(Vector3.forward);
            Vector3 right = transform.TransformDirection(Vector3.right);
            float curSpeedX = speed * Input.GetAxis("Vertical");
            float curSpeedY = speed * Input.GetAxis("Horizontal");
            moveDirection = (forward * curSpeedX) + (right * curSpeedY);

            if (Input.GetButton("Jump"))
            {
                moveDirection.y = jumpSpeed;
            }
        }

        // Apply gravity. Gravity is multiplied by deltaTime twice (once here, and once below
        // when the moveDirection is multiplied by deltaTime). This is because gravity should be applied
        // as an acceleration (ms^-2)
        moveDirection.y -= gravity * Time.deltaTime;

        // Move the controller
        characterController.Move(moveDirection * Time.deltaTime);

        // Player and Camera rotation
        if (canMove)
        {
            rotation.y += Input.GetAxis("Mouse X") * lookSpeed;
            rotation.x += -Input.GetAxis("Mouse Y") * lookSpeed;
            rotation.x = Mathf.Clamp(rotation.x, -lookXLimit, lookXLimit);
            playerCamera.transform.localRotation = Quaternion.Euler(rotation.x, 0, 0);
            transform.eulerAngles = new Vector2(0, rotation.y);
        }
    }
}

SC_PickItem.cs

//You are free to use this script in Free or Commercial projects
//sharpcoderblog.com @2019

using UnityEngine;

public class SC_PickItem : MonoBehaviour
{
    public string itemName = "Some Item"; //Each item must have an unique name
    public Texture itemPreview;

    void Start()
    {
        //Change item tag to Respawn to detect when we look at it
        gameObject.tag = "Respawn";
    }

    public void PickItem()
    {
        Destroy(gameObject);
    }
}

SC_InventorySystem.cs

//You are free to use this script in Free or Commercial projects
//sharpcoderblog.com @2019

using UnityEngine;

public class SC_InventorySystem : MonoBehaviour
{
    public Texture crosshairTexture;
    public SC_CharacterController playerController;
    public SC_PickItem[] availableItems; //List with Prefabs of all the available items

    //Available items slots
    int[] itemSlots = new int[12];
    bool showInventory = false;
    float windowAnimation = 1;
    float animationTimer = 0;

    //UI Drag & Drop
    int hoveringOverIndex = -1;
    int itemIndexToDrag = -1;
    Vector2 dragOffset = Vector2.zero;

    //Item Pick up
    SC_PickItem detectedItem;
    int detectedItemIndex;

    // Start is called before the first frame update
    void Start()
    {
        Cursor.visible = false;
        Cursor.lockState = CursorLockMode.Locked;

        //Initialize Item Slots
        for (int i = 0; i < itemSlots.Length; i++)
        {
            itemSlots[i] = -1;
        }
    }

    // Update is called once per frame
    void Update()
    {
        //Show/Hide inventory
        if (Input.GetKeyDown(KeyCode.Tab))
        {
            showInventory = !showInventory;
            animationTimer = 0;

            if (showInventory)
            {
                Cursor.visible = true;
                Cursor.lockState = CursorLockMode.None;
            }
            else
            {
                Cursor.visible = false;
                Cursor.lockState = CursorLockMode.Locked;
            }
        }

        if (animationTimer < 1)
        {
            animationTimer += Time.deltaTime;
        }

        if (showInventory)
        {
            windowAnimation = Mathf.Lerp(windowAnimation, 0, animationTimer);
            playerController.canMove = false;
        }
        else
        {
            windowAnimation = Mathf.Lerp(windowAnimation, 1f, animationTimer);
            playerController.canMove = true;
        }

        //Begin item drag
        if (Input.GetMouseButtonDown(0) && hoveringOverIndex > -1 && itemSlots[hoveringOverIndex] > -1)
        {
            itemIndexToDrag = hoveringOverIndex;
        }

        //Release dragged item
        if (Input.GetMouseButtonUp(0) && itemIndexToDrag > -1)
        {
            if (hoveringOverIndex < 0)
            {
                //Drop the item outside
                Instantiate(availableItems[itemSlots[itemIndexToDrag]], playerController.playerCamera.transform.position + (playerController.playerCamera.transform.forward), Quaternion.identity);
                itemSlots[itemIndexToDrag] = -1;
            }
            else
            {
                //Switch items between the selected slot and the one we are hovering on
                int itemIndexTmp = itemSlots[itemIndexToDrag];
                itemSlots[itemIndexToDrag] = itemSlots[hoveringOverIndex];
                itemSlots[hoveringOverIndex] = itemIndexTmp;

            }
            itemIndexToDrag = -1;
        }

        //Item pick up
        if (detectedItem && detectedItemIndex > -1)
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                //Add the item to inventory
                int slotToAddTo = -1;
                for (int i = 0; i < itemSlots.Length; i++)
                {
                    if (itemSlots[i] == -1)
                    {
                        slotToAddTo = i;
                        break;
                    }
                }
                if (slotToAddTo > -1)
                {
                    itemSlots[slotToAddTo] = detectedItemIndex;
                    detectedItem.PickItem();
                }
            }
        }
    }

    void FixedUpdate()
    {
        //Detect if the Player is looking at any item
        RaycastHit hit;
        Ray ray = playerController.playerCamera.ViewportPointToRay(new Vector3(0.5F, 0.5F, 0));

        if (Physics.Raycast(ray, out hit, 2.5f))
        {
            Transform objectHit = hit.transform;

            if (objectHit.CompareTag("Respawn"))
            {
                if ((detectedItem == null || detectedItem.transform != objectHit) && objectHit.GetComponent<SC_PickItem>() != null)
                {
                    SC_PickItem itemTmp = objectHit.GetComponent<SC_PickItem>();

                    //Check if item is in availableItemsList
                    for (int i = 0; i < availableItems.Length; i++)
                    {
                        if (availableItems[i].itemName == itemTmp.itemName)
                        {
                            detectedItem = itemTmp;
                            detectedItemIndex = i;
                        }
                    }
                }
            }
            else
            {
                detectedItem = null;
            }
        }
        else
        {
            detectedItem = null;
        }
    }

    void OnGUI()
    {
        //Inventory UI
        GUI.Label(new Rect(5, 5, 200, 25), "Press 'Tab' to open Inventory");

        //Inventory window
        if (windowAnimation < 1)
        {
            GUILayout.BeginArea(new Rect(10 - (430 * windowAnimation), Screen.height / 2 - 200, 302, 430), GUI.skin.GetStyle("box"));

            GUILayout.Label("Inventory", GUILayout.Height(25));

            GUILayout.BeginVertical();
            for (int i = 0; i < itemSlots.Length; i += 3)
            {
                GUILayout.BeginHorizontal();
                //Display 3 items in a row
                for (int a = 0; a < 3; a++)
                {
                    if (i + a < itemSlots.Length)
                    {
                        if (itemIndexToDrag == i + a || (itemIndexToDrag > -1 && hoveringOverIndex == i + a))
                        {
                            GUI.enabled = false;
                        }

                        if (itemSlots[i + a] > -1)
                        {
                            if (availableItems[itemSlots[i + a]].itemPreview)
                            {
                                GUILayout.Box(availableItems[itemSlots[i + a]].itemPreview, GUILayout.Width(95), GUILayout.Height(95));
                            }
                            else
                            {
                                GUILayout.Box(availableItems[itemSlots[i + a]].itemName, GUILayout.Width(95), GUILayout.Height(95));
                            }
                        }
                        else
                        {
                            //Empty slot
                            GUILayout.Box("", GUILayout.Width(95), GUILayout.Height(95));
                        }

                        //Detect if the mouse cursor is hovering over item
                        Rect lastRect = GUILayoutUtility.GetLastRect();
                        Vector2 eventMousePositon = Event.current.mousePosition;
                        if (Event.current.type == EventType.Repaint && lastRect.Contains(eventMousePositon))
                        {
                            hoveringOverIndex = i + a;
                            if (itemIndexToDrag < 0)
                            {
                                dragOffset = new Vector2(lastRect.x - eventMousePositon.x, lastRect.y - eventMousePositon.y);
                            }
                        }

                        GUI.enabled = true;
                    }
                }
                GUILayout.EndHorizontal();
            }
            GUILayout.EndVertical();

            if (Event.current.type == EventType.Repaint && !GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
            {
                hoveringOverIndex = -1;
            }

            GUILayout.EndArea();
        }

        //Item dragging
        if (itemIndexToDrag > -1)
        {
            if (availableItems[itemSlots[itemIndexToDrag]].itemPreview)
            {
                GUI.Box(new Rect(Input.mousePosition.x + dragOffset.x, Screen.height - Input.mousePosition.y + dragOffset.y, 95, 95), availableItems[itemSlots[itemIndexToDrag]].itemPreview);
            }
            else
            {
                GUI.Box(new Rect(Input.mousePosition.x + dragOffset.x, Screen.height - Input.mousePosition.y + dragOffset.y, 95, 95), availableItems[itemSlots[itemIndexToDrag]].itemName);
            }
        }

        //Display item name when hovering over it
        if (hoveringOverIndex > -1 && itemSlots[hoveringOverIndex] > -1 && itemIndexToDrag < 0)
        {
            GUI.Box(new Rect(Input.mousePosition.x, Screen.height - Input.mousePosition.y - 30, 100, 25), availableItems[itemSlots[hoveringOverIndex]].itemName);
        }

        if (!showInventory)
        {
            //Player crosshair
            GUI.color = detectedItem ? Color.green : Color.white;
            GUI.DrawTexture(new Rect(Screen.width / 2 - 4, Screen.height / 2 - 4, 8, 8), crosshairTexture);
            GUI.color = Color.white;

            //Pick up message
            if (detectedItem)
            {
                GUI.color = new Color(0, 0, 0, 0.84f);
                GUI.Label(new Rect(Screen.width / 2 - 75 + 1, Screen.height / 2 - 50 + 1, 150, 20), "Press 'F' to pick '" + detectedItem.itemName + "'");
                GUI.color = Color.green;
                GUI.Label(new Rect(Screen.width / 2 - 75, Screen.height / 2 - 50, 150, 20), "Press 'F' to pick '" + detectedItem.itemName + "'");
            }
        }
    }
}

Bước 2: Thiết lập hệ thống người chơi và kho đồ

Hãy bắt đầu bằng cách thiết lập Trình phát của chúng tôi:

  • Tạo một GameObject mới và gọi nó "Player"
  • Tạo một Capsule mới (GameObject -> Đối tượng 3D -> Capsule) loại bỏ thành phần Capsule Collider sau đó di chuyển Capsule bên trong Đối tượng "Player" và cuối cùng thay đổi vị trí của nó thành (0, 1, 0)
  • Di chuyển Camera chính bên trong Đối tượng "Player" và thay đổi vị trí của nó thành (0, 1.64, 0)

  • Đính kèm tập lệnh SC_CharacterController vào đối tượng "Player" (nó sẽ tự động thêm một thành phần khác gọi là Bộ điều khiển ký tự, thay đổi giá trị trung tâm của nó thành (0, 1, 0))
  • Gán Camera chính cho biến "Player Camera" tại SC_CharacterController

Bây giờ hãy thiết lập các vật phẩm Pick Up - đây sẽ là Prefabs của các vật phẩm có thể nhặt được trong trò chơi.

Đối với hướng dẫn này, tôi sẽ sử dụng các hình dạng đơn giản (Khối lập phương, Hình trụ và Hình cầu) nhưng bạn có thể thêm các mô hình khác nhau, có thể là một số hạt, v.v.

  • Tạo một GameObject mới và gọi nó "SimpleItem"
  • Tạo một Khối lập phương mới (GameObject -> Đối tượng 3D -> Khối), thu nhỏ nó xuống (0,4, 0,4, 0,4) sau đó di chuyển nó vào trong "SimpleItem" GameObject
  • Chọn "SimpleItem" và thêm thành phần Rigidbody và tập lệnh SC_PickItem

Bạn sẽ nhận thấy có 2 biến trong SC_PickItem:

Tên mục - this should be a unique name.
Xem trước mục - a Texture that will be displayed in the Inventory UI, preferably you should assign the image that represents the item.

Trong trường hợp của tôi, Tên mục là "Cube" và Xem trước mục là hình vuông màu trắng:

Lặp lại các bước tương tự cho 2 mục còn lại.

Đối với hạng mục Xi lanh:

  • Nhân đôi một đối tượng "SimpleItem" và đặt tên cho nó "SimpleItem 2"
  • Xóa khối lập phương con và tạo một Hình trụ mới (GameObject -> Đối tượng 3D -> Hình trụ). Di chuyển nó vào bên trong "SimpleItem 2" và Chia tỷ lệ thành (0,4, 0,4, 0,4).
  • Thay đổi Tên mục trong SC_PickItem thành "Cylinder" và Xem trước mục thành hình ảnh hình trụ

Đối với vật phẩm hình cầu:

  • Nhân đôi một đối tượng "SimpleItem" và đặt tên cho nó "SimpleItem 3"
  • Loại bỏ Khối con và tạo Hình cầu mới (GameObject -> Đối tượng 3D -> Hình cầu). Di chuyển nó vào bên trong "SimpleItem 3" và chia tỷ lệ thành (0,4, 0,4, 0,4).
  • Thay đổi Tên mục trong SC_PickItem thành "Sphere" và Xem trước mục thành hình ảnh hình cầu

Bây giờ Lưu từng mục vào Prefab:

Các mặt hàng hiện đã sẵn sàng.

Bước cuối cùng là thiết lập Hệ thống tồn kho:

  • Đính kèm SC_InventorySystem vào đối tượng "Player"
  • Gán một biến Hoạ tiết hình chữ thập (Bạn có thể sử dụng hình ảnh bên dưới hoặc lấy họa tiết hình chữ thập chất lượng cao từ here):

  • Gán SC_CharacterController cho biến "Player Controller" trong SC_InventorySystem
  • Đối với "Available Items", chỉ định mục Prefabs đã tạo trước đó (Lưu ý: Đây phải là các phiên bản Prefab từ chế độ xem Dự án chứ không phải đối tượng Cảnh):

Hệ thống kiểm kê hiện đã sẵn sàng, hãy kiểm tra nó:

Sharp Coder Trình phát video

Mọi thứ hoạt động như mong đợi!

Nguồn
📁InventorySystem.unitypackage159.83 KB
Bài viết được đề xuất
Tạo hệ thống chế tạo vật phẩm và kho đồ trong Unity
Tạo hệ thống đạn 2D đơn giản trong Unity
Hệ thống chọn và thả không có hàng tồn kho trong Unity
Cách kích hoạt đoạn cắt cảnh trong Unity
Mở ngăn kéo và tủ bằng các phím cụ thể trong Unity
Tạo Trình mô phỏng giao thông trong Unity
Tạo đồ sưu tầm và tăng sức mạnh trong Unity