Настроювання екранного елемента керування для реалізації D-pad

Сторінка оновлюється :
Дата створення сторінки :

Середовище перевірки

Вікна
  • вікна 11
Редактор єдності
  • 2020.3.25f1
Пакет системи введення
  • 1.2.0

Передумови для цієї поради

Наступні настройки були зроблені заздалегідь як передумова для опису цієї поради.

Також слід ознайомитися з наступними порадами:

Екранний Control Stick - це стандартна операція орієнтації

On-Screen Stick реалізований як віртуальний флеш-накопичувач, повторюючи роботу флешки, подібну до тієї, що знаходиться на фізичному контролері. Наприклад, якщо ви хочете перемістити його вправо, ви можете спочатку доторкнутися до екрана, щоб доторкнутися до палиці, а потім посуньте її вправо, щоб збити палицю.

Її легко уявити як операцію палицею, але навпаки, коли ви хочете негайно перемістити її вправо, (1) торкнутися, (2) посуньте вправо, Оскільки для цього потрібна двоетапна операція, відповідь неминуче буде трохи відстрочена. Особливо це актуально, коли потрібно зробити швидку зміну напрямку поспіль.

D-pad, що визначає напрямок в момент дотику (нестандартний)

Найкращим методом введення для швидкого введення напрямку є той, який може визначити напрямок у момент дотику, розмістивши щось на зразок D-pad або клавіші зі стрілкою. Це зроблено не Unity, але в моїй грі Little Saber я розміщую клавіші зі стрілками, щоб можна було швидко рухатися в напрямку, до якого торкаєшся. Звичайно, ви також можете ковзати вгору або вліво, торкаючись, щоб перемикати рухи.

Однак стандарт екранного керування нелегко розмістити та впровадити, оскільки є лише екранні палички та екранні кнопки.

Також можна створити псевдо-D-pad, розташувавши чотири кнопки, як показано на малюнку нижче, але це незручно, оскільки неможливо ввести по діагоналі. Якщо розмістити 8 кнопок, діагональ можлива, але операція напрямку потоку, така як «← ↙ ↓», все ще неможлива.

Створення панелі напрямків із налаштуванням екранного керування

Як бачите, екранне керування входить до стандартної комплектації лише з Stick і Button, але ви можете налаштувати відсутні функції за допомогою власних сценаріїв. Тож тут я хотів би створити панель напрямків із налаштуванням екранного керування.

Карта дій

Ми будемо використовувати карту дій, але ми будемо використовувати ту, яка створена в попередніх порадах як є, тому процедура опущена.

Ви також написали якийсь код.

Позиціонування об'єктів

Розмістіть текстову область для відображення введених даних і кнопку, яка замінює D-pad. В цьому випадку кнопки розташовуються, але можна замінити їх на зображення, яке легше зрозуміти.

Сценарій для відображення вхідних даних

Оскільки екранне керування замінює дотик взаємодією з фізичним контролером, Створіть сценарій, який відображатиме введені дані в тексті під час роботи мапи дій.

Зміст такий же, як і раніше, тому пояснення опустю.

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 клас настроювання екранного елемента керування, успадковуючи його від класу Customize. Крім того, щоб отримувати різні сенсорні події, успадкуйте цільовий інтерфейс. Тут обробляються «при торканні», «при русі під час торкання», «коли дотик відпущений» і «перед перетягуванням», тому відповідно описані і наступні інтерфейси.

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

Прикріпіть до кнопки створений вами сценарій. Ця дія розглядається як операція лівої палички геймпада.

Спробуйте перемістити гру і торкнутися кнопок. Думаю, ви бачите, що отримане значення змінюється в залежності від положення дотику. Крім того, ви можете бачити, що навіть якщо ви перетягуєте під час дотику, значення змінюється залежно від позиції перетягування.