تخصيص عنصر التحكم على الشاشة لتنفيذ لوحة الاتجاهات

تحديث الصفحة :
تاريخ إنشاء الصفحة :

بيئة التحقق

نوافذ
  • ويندوز ١١
محرر الوحدة
  • 2020.3.25f1
حزمة نظام الإدخال
  • 1.2.0

المتطلبات الأساسية لهذه النصيحة

تم إجراء الإعدادات التالية مسبقا كمقدمة لوصف هذه النصيحة.

يجب أن تكون أيضا على دراية بالنصائح التالية:

عصا التحكم على الشاشة هي عملية التوجيه القياسية

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

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

لوحة الاتجاهات التي تحدد الاتجاه في لحظة اللمس (غير قياسي)

أفضل طريقة إدخال لإدخال الاتجاه السريع هي تلك التي يمكنها تحديد الاتجاه في لحظة اللمس عن طريق وضع شيء مثل لوحة التوجيه أو مفتاح السهم. إنها ليست من صنع Unity ، ولكن في لعبتي Little Saber ، أضع مفاتيح الأسهم بحيث يمكنك التحرك بسرعة في الاتجاه الذي تلمسه. بالطبع ، يمكنك أيضا التمرير لأعلى أو لليسار أثناء اللمس لتبديل الحركات.

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

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

إنشاء لوحة اتجاه مع تخصيص التحكم على الشاشة

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

خريطة العمل

سنستخدم خريطة العمل ، لكننا سنستخدم الخريطة التي تم إنشاؤها في النصائح السابقة كما هي ، لذلك يتم حذف الإجراء.

لقد كتبت أيضا بعض التعليمات البرمجية.

تحديد موضع الكائنات

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

برنامج نصي لعرض الإدخال

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

المحتوى هو نفسه كما كان من قبل ، لذلك سأحذف الشرح.

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;

public class InputActionScript : MonoBehaviour
{
  /// <summary>情報を表示させるテキストオブジェクト。</summary>
  [SerializeField] private Text TextObject;

  /// <summary>アクションマップから自動生成されたクラス。</summary>
  private InputActionSample _actionMap;

  private void Awake()
  {
    // 各操作を行ったときに呼ばれるイベントを設定する
    _actionMap = new InputActionSample();
    _actionMap.Action2D.Move.performed += context => OnMove(context);
    _actionMap.Action2D.Attack.performed += context => OnAttack(context);
    _actionMap.Action2D.Move.canceled += context => OnMove(context);
    _actionMap.Action2D.Attack.canceled += context => OnAttack(context);
  }

  private void OnEnable()
  {
    // このオブジェクトが有効になったときにアクションマップを有効にする
    _actionMap.Enable();
  }

  private void OnDisable()
  {
    // このオブジェクトが無効になったときにアクションマップが余計な動作を起こさないように無効にする
    _actionMap.Disable();
  }

  /// <summary>
  /// Move 操作をした時に呼ばれるメソッドです。
  /// </summary>
  /// <param name="context">コールバックパラメータ。</param>
  public void OnMove(InputAction.CallbackContext context)
  {
    // Move の入力量を取得
    var vec = context.ReadValue<Vector2>();
    TextObject.text = $"Move:({vec.x:f2}, {vec.y:f2})\n{TextObject.text}";
  }

  /// <summary>
  /// Attack 操作をした時に呼ばれるメソッドです。
  /// </summary>
  /// <param name="context">コールバックパラメータ。</param>
  public void OnAttack(InputAction.CallbackContext context)
  {
    // Attack ボタンの状態を取得
    var value = context.ReadValueAsButton();
    TextObject.text = $"Attack:{value}\n{TextObject.text}";
  }
}

بعد إعداده ، تحقق أولا مما إذا كان يعمل مع لوحة المفاتيح أو لوحة الألعاب.

تخصيص عناصر التحكم التي تظهر على الشاشة

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

يبدو البرنامج النصي كما يلي:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.OnScreen;

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
{
  [InputControl(layout = "Vector2")]
  [SerializeField]
  private string _controlPath;
  /// <summary><see cref="OnScreenControl"/> で定義された値。</summary>
  protected override string controlPathInternal { get => _controlPath; set => _controlPath = value; }

  /// <summary>オブジェクトの位置。</summary>
  private Vector2 _objectPosition;

  /// <summary>オブジェクトのサイズの半分 (スケールも含む)。</summary>
  private Vector2 _objectSizeHalf;


  /// <summary>
  /// オブジェクトが動作する最初のタイミングで1回だけ呼ばれます。
  /// </summary>
  private void Start()
  {
    var rectTransform = (RectTransform)base.transform;

    // オブジェクトの位置を取得
    _objectPosition = rectTransform.anchoredPosition;

    // オブジェクトのサイズの半分を取得 (スケールサイズも考慮)
    _objectSizeHalf = rectTransform.sizeDelta * rectTransform.localScale / 2f;
  }

  /// <summary>ドラッグの初期化処理として呼ばれます。</summary>
  /// <param name="eventData">タッチ情報。</param>
  public void OnInitializePotentialDrag(PointerEventData eventData)
  {
    // タッチのスライド操作を即座に発生させたいのでドラッグ開始までの閾値を無効にします
    eventData.useDragThreshold = false;
  }

  /// <summary>タッチしたタイミングで呼ばれます。</summary>
  /// <param name="eventData">タッチ情報。</param>
  public void OnPointerDown(PointerEventData eventData)
  {
    Operate(eventData);
  }

  /// <summary>タッチした後ドラッグするたびに呼ばれます。</summary>
  /// <param name="eventData">タッチ情報。</param>
  public void OnDrag(PointerEventData eventData)
  {
    Operate(eventData);
  }

  /// <summary>タッチを離したときに呼ばれます。</summary>
  /// <param name="eventData">タッチ情報。</param>
  public void OnPointerUp(PointerEventData eventData)
  {
    // 入力をやめた扱いにしたいので zero を渡します
    SendValueToControl(Vector2.zero);
  }

  /// <summary>
  /// 方向パッドの入力処理を行います。
  /// </summary>
  /// <param name="eventData">タッチ情報。</param>
  private void Operate(PointerEventData eventData)
  {
    // タッチ位置を Canvas 上の位置に変換します
    RectTransformUtility.ScreenPointToLocalPointInRectangle(
      transform.parent.GetComponentInParent<RectTransform>(),
      eventData.position,
      eventData.pressEventCamera,
      out Vector2 localPoint);

    // Dpad の中心を原点としたタッチ位置
    Vector2 positionInDpad = localPoint - _objectPosition;

    // タッチ位置をオブジェクトサイズの半分で割り 0~1 の範囲に収めます
    Vector2 positionRate = Vector2.ClampMagnitude(positionInDpad / _objectSizeHalf, 1);

    // 入力値を OnScreenControl に渡してその後の処理を任せます。
    SendValueToControl(positionRate);
  }
}

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

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
محتويات الواجهة
IPointerDownHandler عند لمسها
آي بي أو إنتر هاندلر عند تحرير اللمسة
IDragHandler عندما تتحرك أثناء اللمس
IInitializePotentialDragHandler قبل البدء في السحب

يتم الإعلان عن الحقول التالية:

controlPathInternalOnScreenControl مطلوب إذا كنت ترث من فصل. يحتوي على سلسلة من زر جهاز الإدخال الذي سيتم تعيينه ، ولكن يمكنك كتابته كما هو. InputControl ومع ذلك ، يجب أن تحتوي السمة على القيمة المراد الاحتفاظ بها (Vector2 في هذه الحالة).

_objectPosition_objectSizeHalf واضغط مع الاستمرار على نصف موضع وحجم الزر مقدما واستخدمه لاحقا لإجراء العمليات الحسابية.

[InputControl(layout = "Vector2")]
[SerializeField]
private string _controlPath;
/// <summary><see cref="OnScreenControl"/> で定義された値。</summary>
protected override string controlPathInternal { get => _controlPath; set => _controlPath = value; }

/// <summary>オブジェクトの位置。</summary>
private Vector2 _objectPosition;

/// <summary>オブジェクトのサイズの半分 (スケールも含む)。</summary>
private Vector2 _objectSizeHalf;

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

// <summary>
// オブジェクトが動作する最初のタイミングで1回だけ呼ばれます。
// </summary>
private void Start()
{
  var rectTransform = (RectTransform)base.transform;

  // オブジェクトの位置を取得
  _objectPosition = rectTransform.anchoredPosition;

  // オブジェクトのサイズの半分を取得 (スケールサイズも考慮)
  _objectSizeHalf = rectTransform.sizeDelta * rectTransform.localScale / 2f;
}

OnInitializePotentialDrag عندما تريد بدء السحب بعد اللمس. أثناء السحب ، OnDrag تسمى الطريقة. يتم تعيين عتبة لمنع حكم السحب إلى حد ما بحيث لا يتحرك الإصبع قليلا ولا يحدث حكم السحب بسبب عملية اللمس ببساطة.

هذه المرة ، نظرا لأنه إدخال يفترض أنك تريد إجراء عملية ضبط دقيق ، قم بتعطيل إعداد العتبة هذا وقم بإصدار حكم سحب على الفور. eventData.useDragThreshold false يمكنك تجاوز الحد عن طريق التعيين إلى .

/// <summary>ドラッグの初期化処理として呼ばれます。</summary>
/// <param name="eventData">タッチ情報。</param>
public void OnInitializePotentialDrag(PointerEventData eventData)
{
  // タッチのスライド操作を即座に発生させたいのでドラッグ開始までの閾値を無効にします
  eventData.useDragThreshold = false;
}

فيما يلي الأحداث التي يتم استدعاؤها عند اللمس والسحب والتحرير على التوالي. OnPointerDown OnDrag منذ و ، يقوم Operate كل منها بنفس معالجة الإدخال ، لذلك نقوم بإنشاء طريقة منفصلة ونسميها. OnPointerUp الآن ، قم بتمريره إلى الطريقة لإعلام SendValueToControl Vector2.zero عنصر التحكم بأنه توقف عن الكتابة.

/// <summary>タッチしたタイミングで呼ばれます。</summary>
/// <param name="eventData">タッチ情報。</param>
public void OnPointerDown(PointerEventData eventData)
{
  Operate(eventData);
}

/// <summary>タッチした後ドラッグするたびに呼ばれます。</summary>
/// <param name="eventData">タッチ情報。</param>
public void OnDrag(PointerEventData eventData)
{
  Operate(eventData);
}

/// <summary>タッチを離したときに呼ばれます。</summary>
/// <param name="eventData">タッチ情報。</param>
public void OnPointerUp(PointerEventData eventData)
{
  // 入力をやめた扱いにしたいので zero を渡します
  SendValueToControl(Vector2.zero);
}

Operate الطريقة هي عملية إدخال D-pad الأساسية.

بادئ ذي بدء ، يمكن الحصول على موضع اللمس باستخدام ، ولكن نظرا لأن هذا الإحداثي هو إحداثيات eventData.position شاشة اللعبة ، RectTransformUtility.ScreenPointToLocalPointInRectangle طريقة التحويل إلى إحداثيات قماش. القيم المراد تمريرها هي "قماش" و "موضع اللمس" و "الكاميرا" و " RectTransformمتغير الاستقبال".

بعد الحصول على "موضع اللمس على اللوحة القماشية" ، قم بتحويله إلى موضع الزر كأصل. فقط اطرح موضع () الكائن_objectPosition.

بعد ذلك ، نظرا لأن موضع اللمس لا يزال رقما على اللوحة القماشية ، قم بتحويل هذه القيمة إلى نسبة 0 ~ 1. إذا قسمت على نصف حجم الزر ، فسيكون موضع اللمس ضمن نطاق الزر 0 ~ 1. إذا قمت بالسحب باللمس ، فستكون القيمة 1 أو أكثر لأنها تقبل العمليات حتى خارج الزر. Vector2.ClampMagnitude افعل ذلك في الطريقة بحيث لا تتجاوز 1.

أخيرا ، قم بتمرير قيمة SendValueToControl الإدخال المحولة إلى 0 ~ 1 إلى الطريقة. يقوم عنصر التحكم على الشاشة بالباقي نيابة عنك.

/// <summary>
/// 方向パッドの入力処理を行います。
/// </summary>
/// <param name="eventData">タッチ情報。</param>
private void Operate(PointerEventData eventData)
{
  // タッチ位置を Canvas 上の位置に変換します
  RectTransformUtility.ScreenPointToLocalPointInRectangle(
    transform.parent.GetComponentInParent<RectTransform>(),
    eventData.position,
    eventData.pressEventCamera,
    out Vector2 localPoint);

  // Dpad の中心を原点としたタッチ位置
  Vector2 positionInDpad = localPoint - _objectPosition;

  // タッチ位置をオブジェクトサイズの半分で割り 0~1 の範囲に収めます
  Vector2 positionRate = Vector2.ClampMagnitude(positionInDpad / _objectSizeHalf, 1);

  // 入力値を OnScreenControl に渡してその後の処理を任せます。
  SendValueToControl(positionRate);
}

قم بإرفاق البرنامج النصي الذي أنشأته بالزر. تم تعيين هذا الإجراء ليتم التعامل معه على أنه تشغيل العصا اليسرى للوحة الألعاب.

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