התאם אישית את הבקרה על המסך כדי ליישם את ה- D-pad

עודכן דף :
תאריך יצירת דף :

סביבת אימות

חלונות
  • חלונות 11
עורך Unity
  • 2020.3.25F1
חבילת מערכת קלט
  • 1.2.0

תנאים מוקדמים לטיפ זה

ההגדרות הבאות נקבעו מראש כהנחת יסוד לתיאור עצה זו.

כדאי שתכירו גם את הטיפים הבאים:

On-Screen Control Stick היא פעולת הכיוון הסטנדרטית

ה-On-Screen Stick מיושם כמקל וירטואלי, ומשכפל את פעולתו של מקל דומה לזו שנמצאת בבקר פיזי. לדוגמה, אם ברצונך להזיז אותו ימינה, באפשרותך לגעת תחילה במסך כדי לגעת במקל, ולאחר מכן להחליק אותו ימינה כדי להפיל את המקל.

קל לדמיין אותו כפעולת מקל, אבל להיפך, כשרוצים להזיז אותו ימינה מיד, (1) לגעת, (2) להחליק ימינה, מכיוון שזה דורש פעולה דו-שלבית, התגובה בהכרח תתעכב מעט. זה נכון במיוחד כאשר אתה צריך לעשות שינוי כיוון מהיר ברציפות.

D-pad הקובע כיוון ברגע המגע (לא סטנדרטי)

שיטת הקלט הטובה ביותר לקלט כיוון מהיר היא שיטה שיכולה לקבוע את הכיוון ברגע הנגיעה על ידי הצבת משהו כמו D-pad או מקש חץ. זה לא נעשה על ידי יוניטי, אבל במשחק שלי Little Saber, אני ממקם את מקשי החצים כך שתוכל לנוע במהירות בכיוון שאתה נוגע בו. כמובן, אתה יכול גם להחליק למעלה או שמאלה תוך כדי נגיעה כדי להחליף תנועות.

עם זאת, תקן 'שליטה על המסך' אינו קל להצבה וליישום מכיוון שיש רק מקלות על המסך ולחצנים על המסך.

אתה יכול גם ליצור pseudo-D-pad על ידי סידור ארבעה לחצנים כפי שמוצג באיור להלן, אבל זה לא נוח כי אתה לא יכול להזין באלכסון. אם תמקם 8 לחצנים, פעולה אלכסונית אפשרית, אך פעולת כיוון זרימה כגון "← ↙ ↓" עדיין אינה אפשרית.

יצירת לוח כיוונים עם התאמה אישית של 'בקרת מסך'

כפי שניתן לראות, 'שליטה על המסך' כלולה כסטנדרט רק עם 'מוט וכפתור', אך באפשרותך להתאים אישית את התכונות החסרות באמצעות קבצי Script משלך. אז כאן אני רוצה ליצור לוח כיוונים עם התאמה אישית של On-Screen Control.

מפת פעולה

נשתמש במפת הפעולה, אך נשתמש בזו שנוצרה בטיפים הקודמים כפי שהיא, ולכן ההליך מושמט.

כתבת גם קצת קוד.

מיקום אובייקטים

מקם אזור טקסט כדי להציג את הקלט ולחצן שיחליף את ה- D-pad. במקרה זה, הלחצנים מסודרים, אך ניתן להחליף אותם בתמונה קלה יותר להבנה.

קובץ Script להצגת קלט

מאחר ש'שליטה על המסך' מחליפה מגע באינטראקציה של בקר פיזי, צור סקריפט שיציג את הקלט שלך בטקסט בזמן שמפת הפעולות פועלת.

התוכן זהה לקודם, ולכן אשמיט את ההסבר.

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}";
  }
}

לאחר הגדרתו, בדוק תחילה אם הוא פועל עם המקלדת או לוח המשחק שלך.

התאמה אישית של פקדים על המסך

זה מביא אותנו לנושא העיקרי של טיפים אלה. התאמה אישית של הפקד על המסך היא קובץ Script, לכן צור תחילה קובץ Script. השם שרירותי, אבל במקרה 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 כשנוגעים בו
IPointerUpHandler כאשר אתה משחרר את המגע
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);
}

צרף את הסקריפט שיצרת ללחצן. פעולה זו מוגדרת להיות מטופלת כפעולה של המקל השמאלי של Gamepad.

נסה להזיז את המשחק וגע בכפתורים. אני חושב שאתה יכול לראות שהערך המתקבל משתנה בהתאם למיקום המגע. כמו כן, ניתן לראות שגם אם תגרור תוך כדי נגיעה, הערך ישתנה בהתאם למיקום הגרירה.