ترميز نظام جرد بسيط مع سحب واجهة المستخدم وإسقاطها في الوحدة

تسمح العديد من الألعاب للاعبين بجمع عدد كبير من العناصر وحملها (مثل ألعاب RTS/MOBA/RPG، وألعاب تمثيل الأدوار، وما إلى ذلك)، وهنا يأتي دور المخزون.

Inventory عبارة عن جدول عناصر يوفر وصولاً سريعًا إلى عناصر اللاعب وطريقة بسيطة لتنظيمها.

ديابلو 3 نظام الجرد

في هذا المنشور، سوف نتعلم كيفية برمجة نظام Inventory System البسيط مع التقاط العناصر وسحب وإسقاط واجهة المستخدم في Unity.

الخطوة 1: إنشاء البرامج النصية

يتطلب هذا البرنامج التعليمي 3 نصوص برمجية:

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 + "'");
            }
        }
    }
}

الخطوة 2: إعداد المشغل ونظام الجرد

لنبدأ بإعداد المشغل الخاص بنا:

  • قم بإنشاء GameObject جديد وقم بتسميته "Player"
  • قم بإنشاء كبسولة جديدة (GameObject -> كائن ثلاثي الأبعاد -> كبسولة) قم بإزالة مكون مصادم الكبسولة ثم حرك الكبسولة داخل الكائن "Player" وأخيرًا قم بتغيير موضعه إلى (0، 1، 0)
  • حرك الكاميرا الرئيسية داخل الكائن "Player" وقم بتغيير موضعه إلى (0، 1.64، 0)

  • قم بإرفاق البرنامج النصي SC_CharacterController بالكائن "Player" (سيضيف تلقائيًا مكونًا آخر يسمى Character Controller، ويغير قيمته المركزية إلى (0، 1، 0))
  • قم بتعيين الكاميرا الرئيسية لمتغير "Player Camera" في SC_CharacterController

لنقم الآن بإعداد عناصر الالتقاط - ستكون هذه العناصر جاهزة للعناصر التي يمكن التقاطها في اللعبة.

في هذا البرنامج التعليمي، سأستخدم أشكالًا بسيطة (مكعب، وأسطوانة، وكرة) ولكن يمكنك إضافة نماذج مختلفة، وربما بعض الجسيمات، وما إلى ذلك.

  • قم بإنشاء GameObject جديد وقم بتسميته "SimpleItem"
  • أنشئ مكعبًا جديدًا (GameObject -> 3D Object -> Cube)، وقم بتصغيره إلى (0.4، 0.4، 0.4) ثم انقله داخل "SimpleItem" GameObject
  • حدد "SimpleItem" وأضف مكون Rigidbody والبرنامج النصي SC_PickItem

ستلاحظ وجود متغيرين في SC_PickItem:

اسم العنصر - this should be a unique name.
معاينة العنصر - a Texture that will be displayed in the Inventory UI, preferably you should assign the image that represents the item.

في حالتي، اسم العنصر هو "Cube" ومعاينة العنصر عبارة عن مربع أبيض:

كرر نفس الخطوات للعنصرين الآخرين.

بالنسبة لعنصر الاسطوانة:

  • قم بتكرار كائن "SimpleItem" وقم بتسميته "SimpleItem 2"
  • قم بإزالة المكعب الفرعي وقم بإنشاء أسطوانة جديدة (GameObject -> 3D Object -> Cylinder). انقله إلى داخل "SimpleItem 2" وقم بقياسه إلى (0.4، 0.4، 0.4).
  • قم بتغيير اسم العنصر في SC_PickItem إلى "Cylinder" ومعاينة العنصر إلى صورة أسطوانة

بالنسبة لعنصر المجال:

  • قم بتكرار كائن "SimpleItem" وقم بتسميته "SimpleItem 3"
  • قم بإزالة المكعب الفرعي وقم بإنشاء كرة جديدة (GameObject -> 3D Object -> Sphere). انقله إلى داخل "SimpleItem 3" وقم بقياسه إلى (0.4، 0.4، 0.4).
  • قم بتغيير اسم العنصر في SC_PickItem إلى "Sphere" ومعاينة العنصر إلى صورة كرة

الآن احفظ كل عنصر في المبنى الجاهز:

العناصر جاهزة الآن.

الخطوة الأخيرة هي إعداد نظام المخزون:

  • قم بإرفاق SC_InventorySystem بالكائن "Player"
  • قم بتعيين متغير Crosshair Texture (يمكنك استخدام الصورة أدناه أو الحصول على نسيج Crosshair Texture عالي الجودة من هنا):

  • قم بتعيين SC_CharacterController للمتغير "Player Controller" في SC_InventorySystem
  • بالنسبة إلى "Available Items"، قم بتعيين العناصر الجاهزة التي تم إنشاؤها مسبقًا (ملاحظة: يجب أن تكون هذه مثيلات جاهزة من عرض المشروع وليست كائنات المشهد):

نظام المخزون جاهز الآن، دعونا نختبره:

Sharp Coder مشغل فديوهات

كل شيء يعمل كما هو متوقع!