الوحدة كيفية جعل عناصر التحكم باللمس المحمول

تعد عناصر التحكم أحد أهم أجزاء لعبة الفيديو، وليس من المستغرب أنها ما يسمح للاعبين بالتفاعل مع عالم اللعبة.

عناصر التحكم في اللعبة هي إشارات يتم إرسالها من خلال تفاعل الأجهزة (الماوس/لوحة المفاتيح، وحدة التحكم، شاشة اللمس، وما إلى ذلك) والتي تتم معالجتها بعد ذلك بواسطة رمز اللعبة، مع تطبيق إجراءات معينة.

تحتوي أجهزة الكمبيوتر و وحدات تحكم الألعاب على أزرار فعلية يمكن الضغط عليها، ومع ذلك، تحتوي الأجهزة المحمولة الحديثة على عدد قليل فقط من الأزرار المادية، ويتم باقي التفاعل من خلال إيماءات اللمس، مما يعني أنه يجب عرض أزرار اللعبة على الشاشة. ولهذا السبب، عند إنشاء لعبة على الهاتف المحمول، من المهم إيجاد توازن بين وجود جميع الأزرار على الشاشة مع إبقائها سهلة الاستخدام وخالية من الفوضى.

ضوابط الوحدة المتنقلة

في هذا البرنامج التعليمي، سأعرض كيفية إنشاء عناصر تحكم متنقلة كاملة الميزات (عصا التحكم والأزرار) في Unity باستخدام UI Canvas.

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

يحتوي هذا البرنامج التعليمي على نصين برمجيين، SC_ClickTracker.cs وSC_MobileControls.cs. سوف يستمع البرنامج النصي الأول إلى أحداث النقر وسيقرأ البرنامج النصي الثاني القيم التي تم إنشاؤها من الأحداث.

SC_ClickTracker.cs

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

#if UNITY_EDITOR
using UnityEditor;
#endif

public class SC_ClickTracker : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
{
    public string buttonName = ""; //This should be an unique name of the button
    public bool isJoystick = false;
    public float movementLimit = 1; //How far the joystick can be moved (n x Joystick Width)
    public float movementThreshold = 0.1f; //Minimum distance (n x Joystick Width) that the Joystick need to be moved to trigger inputAxis (Must be less than movementLimit)

    //Reference variables
    RectTransform rt;
    Vector3 startPos;
    Vector2 clickPos;

    //Input variables
    Vector2 inputAxis = Vector2.zero;
    bool holding = false;
    bool clicked = false;

    void Start()
    {
        //Add this button to the list
        SC_MobileControls.instance.AddButton(this);

        rt = GetComponent<RectTransform>();
        startPos = rt.anchoredPosition3D;
    }

    //Do this when the mouse is clicked over the selectable object this script is attached to.
    public void OnPointerDown(PointerEventData eventData)
    {
        //Debug.Log(this.gameObject.name + " Was Clicked.");

        holding = true;

        if (!isJoystick)
        {
            clicked = true;
            StartCoroutine(StopClickEvent());
        }
        else
        {
            //Initialize Joystick movement
            clickPos = eventData.pressPosition;
        }
    }

    WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame();

    //Wait for next update then release the click event
    IEnumerator StopClickEvent()
    {
        yield return waitForEndOfFrame;

        clicked = false;
    }

    //Joystick movement
    public void OnDrag(PointerEventData eventData)
    {
        //Debug.Log(this.gameObject.name + " The element is being dragged");

        if (isJoystick)
        {
            Vector3 movementVector = Vector3.ClampMagnitude((eventData.position - clickPos) / SC_MobileControls.instance.canvas.scaleFactor, (rt.sizeDelta.x * movementLimit) + (rt.sizeDelta.x * movementThreshold));
            Vector3 movePos = startPos + movementVector;
            rt.anchoredPosition = movePos;

            //Update inputAxis
            float inputX = 0;
            float inputY = 0;
            if (Mathf.Abs(movementVector.x) > rt.sizeDelta.x * movementThreshold)
            {
                inputX = (movementVector.x - (rt.sizeDelta.x * movementThreshold * (movementVector.x > 0 ? 1 : -1))) / (rt.sizeDelta.x * movementLimit);
            }
            if (Mathf.Abs(movementVector.y) > rt.sizeDelta.x * movementThreshold)
            {
                inputY = (movementVector.y - (rt.sizeDelta.x * movementThreshold * (movementVector.y > 0 ? 1 : -1))) / (rt.sizeDelta.x * movementLimit);
            }
            inputAxis = new Vector2(inputX, inputY);
        }
    }

    //Do this when the mouse click on this selectable UI object is released.
    public void OnPointerUp(PointerEventData eventData)
    {
        //Debug.Log(this.gameObject.name + " The mouse click was released");

        holding = false;

        if (isJoystick)
        {
            //Reset Joystick position
            rt.anchoredPosition = startPos;
            inputAxis = Vector2.zero;
        }
    }

    public Vector2 GetInputAxis()
    {
        return inputAxis;
    }

    public bool GetClickedStatus()
    {
        return clicked;
    }

    public bool GetHoldStatus()
    {
        return holding;
    }
}

#if UNITY_EDITOR
//Custom Editor
[CustomEditor(typeof(SC_ClickTracker))]
public class SC_ClickTracker_Editor : Editor
{
    public override void OnInspectorGUI()
    {
        SC_ClickTracker script = (SC_ClickTracker)target;

        script.buttonName = EditorGUILayout.TextField("Button Name", script.buttonName);
        script.isJoystick = EditorGUILayout.Toggle("Is Joystick", script.isJoystick);
        if (script.isJoystick)
        {
            script.movementLimit = EditorGUILayout.FloatField("Movement Limit", script.movementLimit);
            script.movementThreshold = EditorGUILayout.FloatField("Movement Threshold", script.movementThreshold);
        }
    }
}
#endif

SC_MobileControls.cs

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

public class SC_MobileControls : MonoBehaviour
{
    [HideInInspector]
    public Canvas canvas;
    List<SC_ClickTracker> buttons = new List<SC_ClickTracker>();

    public static SC_MobileControls instance;

    void Awake()
    {
        //Assign this script to static variable, so it can be accessed from other scripts. Make sure there is only one SC_MobileControls in the Scene.
        instance = this;

        canvas = GetComponent<Canvas>();
    }

    public int AddButton(SC_ClickTracker button)
    {
        buttons.Add(button);

        return buttons.Count - 1;
    }

    public Vector2 GetJoystick(string joystickName)
    {
        for(int i = 0; i < buttons.Count; i++)
        {
            if(buttons[i].buttonName == joystickName)
            {
                return buttons[i].GetInputAxis();
            }
        }

        Debug.LogError("Joystick with a name '" + joystickName + "' not found. Make sure SC_ClickTracker is assigned to the button and the name is matching.");

        return Vector2.zero;
    }

    public bool GetMobileButton(string buttonName)
    {
        for (int i = 0; i < buttons.Count; i++)
        {
            if (buttons[i].buttonName == buttonName)
            {
                return buttons[i].GetHoldStatus();
            }
        }

        Debug.LogError("Button with a name '" + buttonName + "' not found. Make sure SC_ClickTracker is assigned to the button and the name is matching.");

        return false;
    }

    public bool GetMobileButtonDown(string buttonName)
    {
        for (int i = 0; i < buttons.Count; i++)
        {
            if (buttons[i].buttonName == buttonName)
            {
                return buttons[i].GetClickedStatus();
            }
        }

        Debug.LogError("Button with a name '" + buttonName + "' not found. Make sure SC_ClickTracker is assigned to the button and the name is matching.");

        return false;
    }
}

الخطوة 2: إعداد عناصر التحكم المحمولة

  • قم بإنشاء لوحة قماشية جديدة (GameObject -> UI -> Canvas)
  • قم بتغيير 'UI Scale Mode' في Canvas Scaler إلى 'Scale With Screen Size' وقم بتغيير الدقة المرجعية إلى الدقة التي تعمل بها (في حالتي هي 1000 × 600)
  • قم بإرفاق البرنامج النصي SC_MobileControls بكائن Canvas
  • انقر بزر الماوس الأيمن على كائن قماش -> واجهة المستخدم -> الصورة
  • إعادة تسمية الصورة التي تم إنشاؤها حديثا إلى "JoystickLeft"
  • قم بتغيير "JoystickLeft" Sprite إلى دائرة فارغة (لا تنس تغيير Texture Type إلى 'Sprite (2D and UI)' بعد استيراده إلى Unity)

  • قم بتعيين "JoystickLeft" قيم التحويل المستطيل كما في لقطة الشاشة أدناه:

  • في مكون الصورة، قم بتعيين Color alpha إلى 0.5 لجعل الكائن شفافًا قليلاً:

  • قم بتكرار الكائن "JoystickLeft" وأعد تسميته إلى "JoystickLeftButton"
  • انقل "JoystickLeftButton" داخل الكائن "JoystickLeft"
  • قم بتغيير "JoystickLeftButton" Sprite إلى دائرة مملوءة:

  • قم بتعيين قيم "JoystickLeftButton" Rect Transform كما في لقطة الشاشة أدناه:

  • إضافة مكون الزر إلى "JoystickLeftButton"
  • في مكون الزر، قم بتغيير الانتقال إلى 'None'
  • قم بإرفاق البرنامج النصي SC_ClickTracker إلى "JoystickLeftButton"
  • في SC_ClickTracker، قم بتعيين اسم الزر على أي اسم فريد (في حالتي قمت بتعيينه على 'JoystickLeft') وقم بتمكين مربع الاختيار 'Is Joystick'.

زر عصا التحكم جاهز. يمكنك الحصول على أي عدد من أذرع التحكم (في حالتي سيكون لدي 2، واحد على اليسار للتحكم في الحركة والآخر على اليمين للتحكم في الدوران).

  • انسخ "JoystickLeft" وأعد تسميته إلى "JoystickRight"
  • قم بتوسيع "JoystickRight" وإعادة تسمية "JoystickLeftButton" إلى "JoystickRightButton"
  • قم بتعيين "JoystickRight" قيم التحويل المستطيل كما في لقطة الشاشة أدناه:

  • حدد الكائن "JoystickRightButton" وفي SC_ClickTracker قم بتغيير اسم الزر إلى 'JoystickRight'

عصا التحكم الثانية جاهزة.

لنقم الآن بإنشاء زر عادي:

  • انقر بزر الماوس الأيمن على كائن قماش -> واجهة المستخدم -> الزر
  • إعادة تسمية كائن الزر إلى "SprintButton"
  • قم بتغيير "SprintButton" Sprite إلى دائرة ذات تأثير مائل:

  • قم بتعيين "SprintButton" قيم التحويل المستطيل كما في لقطة الشاشة أدناه:

  • قم بتغيير لون الصورة "SprintButton" ألفا إلى 0.5
  • قم بإرفاق البرنامج النصي SC_ClickTracker بالكائن "SprintButton"
  • في SC_ClickTracker قم بتغيير اسم الزر إلى 'Sprinting'
  • حدد كائن النص داخل "SprintButton" وقم بتغيير نصه إلى 'Sprint'، وقم أيضًا بتغيير حجم الخط إلى 'Bold'

زر الوحدة موبايل

الزر جاهز.

سنقوم بإنشاء زر آخر يسمى "Jump":

  • قم بتكرار الكائن "SprintButton" وأعد تسميته إلى "JumpButton"
  • قم بتغيير قيمة "JumpButton" Pos Y إلى 250
  • في SC_ClickTracker قم بتغيير اسم الزر إلى 'Jumping'
  • قم بتغيير النص داخل "JumpButton" إلى 'Jump'

والزر الأخير هو "Action":

  • قم بتكرار الكائن "JumpButton" وأعد تسميته إلى "ActionButton"
  • قم بتغيير قيمة "ActionButton" Pos X إلى -185
  • في SC_ClickTracker قم بتغيير اسم الزر إلى 'Action'
  • قم بتغيير النص داخل "ActionButton" إلى 'Action'

الخطوة 3: تنفيذ عناصر التحكم المتنقلة

إذا اتبعت الخطوات المذكورة أعلاه، فيمكنك الآن استخدام هذه الوظائف لتنفيذ عناصر التحكم المحمولة في البرنامج النصي الخاص بك:

if(SC_MobileControls.instance.GetMobileButtonDown("BUTTON_NAME")){
	//Mobile button has been pressed one time, equivalent to if(Input.GetKeyDown(KeyCode...))
}

if(SC_MobileControls.instance.GetMobileButton("BUTTON_NAME")){
	//Mobile button is being held pressed, equivalent to if(Input.GetKey(KeyCode...))
}

//Get normalized direction of a on-screen Joystick
//Could be compared to: new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")) or new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"))
Vector2 inputAxis = SC_MobileControls.instance.GetJoystick("JOYSTICK_NAME");

على سبيل المثال، سأقوم بتنفيذ عناصر التحكم في الهاتف المحمول باستخدام وحدة تحكم FPS من هذا البرنامج التعليمي. اتبع هذا البرنامج التعليمي أولاً، فهو بسيط إلى حد ما.

إذا اتبعت هذا البرنامج التعليمي، فسيكون لديك الآن الكائن "FPSPlayer" بالإضافة إلى Canvas مع عناصر التحكم المحمولة.

سنحافظ على عناصر التحكم في سطح المكتب مع تنفيذ عناصر التحكم في الأجهزة المحمولة أيضًا، مما يجعلها مشتركة بين الأنظمة الأساسية:

  • افتح البرنامج النصي SC_FPSController، وقم بالتمرير حتى السطر 28، وقم بإزالة هذا الجزء (ستؤدي إزالة هذا الجزء إلى منع قفل المؤشر وسيسمح بالنقر فوق عناصر التحكم المحمولة في المحرر.):
        // Lock cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
  • قم بالتمرير حتى السطر 39 واستبدل:
        bool isRunning = Input.GetKey(KeyCode.LeftShift);
        float curSpeedX = canMove ? (isRunning ? runningSpeed : walkingSpeed) * Input.GetAxis("Vertical") : 0;
        float curSpeedY = canMove ? (isRunning ? runningSpeed : walkingSpeed) * Input.GetAxis("Horizontal") : 0;
  • مع:
        bool isRunning = Input.GetKey(KeyCode.LeftShift) || SC_MobileControls.instance.GetMobileButton("Sprinting");
        float curSpeedX = canMove ? (isRunning ? runningSpeed : walkingSpeed) * (Input.GetAxis("Vertical") + SC_MobileControls.instance.GetJoystick("JoystickLeft").y) : 0;
        float curSpeedY = canMove ? (isRunning ? runningSpeed : walkingSpeed) * (Input.GetAxis("Horizontal") + SC_MobileControls.instance.GetJoystick("JoystickLeft").x) : 0;
  • قم بالتمرير لأسفل حتى السطر 45 واستبدل:
        if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
  • مع:
        if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
  • قم بالتمرير لأسفل حتى السطر 68 واستبدل:
            rotationX += -Input.GetAxis("Mouse Y") * lookSpeed;
            rotationX = Mathf.Clamp(rotationX, -lookXLimit, lookXLimit);
            playerCamera.transform.localRotation = Quaternion.Euler(rotationX, 0, 0);
            transform.rotation *= Quaternion.Euler(0, Input.GetAxis("Mouse X") * lookSpeed, 0);
  • مع:
#if UNITY_IPHONE || UNITY_ANDROID || UNITY_EDITOR
            rotationX += -(SC_MobileControls.instance.GetJoystick("JoystickRight").y) * lookSpeed;
#else
            rotationX += -Input.GetAxis("Mouse Y") * lookSpeed;
#endif
            rotationX = Mathf.Clamp(rotationX, -lookXLimit, lookXLimit);
            playerCamera.transform.localRotation = Quaternion.Euler(rotationX, 0, 0);
#if UNITY_IPHONE || UNITY_ANDROID || UNITY_EDITOR
            transform.rotation *= Quaternion.Euler(0, SC_MobileControls.instance.GetJoystick("JoystickRight").x * lookSpeed, 0);
#else
            transform.rotation *= Quaternion.Euler(0, Input.GetAxis("Mouse X") * lookSpeed, 0);
#endif

نظرًا لأن حركة المظهر ستتداخل مع اختبار عصا التحكم في المحرر، فإننا نستخدم #if لـ التجميع الخاص بالنظام الأساسي لفصل منطق الهاتف المحمول عن بقية الأنظمة الأساسية.

وحدة التحكم Mobile FPS جاهزة الآن، فلنختبرها:

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

كما ترون، جميع أذرع التحكم والأزرار فعالة (باستثناء الزر "Action"، الذي لم يتم تنفيذه بسبب عدم وجود ميزة مناسبة له).