Настройте экранное управление, чтобы реализовать крестовину

Страница обновлена :
Дата создания страницы :

Среда верификации

Виндоус
  • Windows 11
Редактор Unity
  • 2020.3.25f1
Входной системный пакет
  • 1.2.0

Предпосылки для этого совета

Следующие настройки были сделаны заранее в качестве предпосылки для описания этого совета.

Вы также должны быть знакомы со следующими советами:

Экранный мини-джойстик является стандартной операцией ориентации

Экранный джойстик реализован в виде виртуального джойстика, воспроизводя работу джойстика, аналогичную той, что используется на физическом контроллере. Например, если вы хотите переместить его вправо, вы можете сначала коснуться экрана, чтобы коснуться джойстика, а затем сдвинуть его вправо, чтобы сбить джойстик вниз.

Это легко представить как операцию джойстика, но наоборот, когда вы хотите немедленно переместить ее вправо, (1) коснуться, (2) сдвинуть вправо, Поскольку для этого требуется двухэтапная операция, ответ неизбежно будет немного запаздывающим. Это особенно актуально, когда нужно сделать быструю смену направления подряд.

крестовина, определяющая направление в момент касания (нестандартная)

Лучший метод ввода для быстрого ввода направления — это тот, который может определить направление в момент касания, поместив что-то вроде крестовины или клавиши со стрелкой. Это не сделано Unity, но в моей игре Little Sabre я размещаю клавиши со стрелками, чтобы вы могли быстро двигаться в том направлении, в котором вы касаетесь. Конечно, вы также можете скользить вверх или влево во время касания, чтобы переключать движения.

Однако стандарт экранного управления нелегко разместить и реализовать, потому что есть только экранные джойстики и экранные кнопки.

Вы также можете создать псевдо-D-pad, расположив четыре кнопки, как показано на рисунке ниже, но это неудобно, потому что вы не можете вводить по диагонали. Если вы разместите 8 кнопок, диагональное управление возможно, но управление направлением потока, такое как «← ↙ ↓», по-прежнему невозможно.

Создание навигационной панели с настройкой экранного управления

Как видите, экранное управление входит в стандартную комплектацию только с джойстиком и кнопкой, но вы можете настроить недостающие функции с помощью собственных сценариев. Итак, здесь я хотел бы создать навигационную панель с настройкой экранного управления.

Карта действий

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

Вы также написали код.

Позиционирование объектов

Поместите текстовую область для отображения вводимых данных и кнопку, заменяющую крестовину. В этом случае кнопки располагаются, но вы можете заменить их изображением, которое более понятно.

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

Поскольку экранное управление заменяет сенсорный экран взаимодействием с физическим контроллером, Создайте сценарий, который отображает вводимые данные в виде текста по мере работы карты действий.

Содержание такое же, как и раньше, поэтому я опущу объяснение.

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

Класс настройки On-Screen Control создается 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 Метод представляет собой основную операцию ввода крестовины.

Прежде всего, положение касания можно получить с помощью , но поскольку эта координата 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);
}

Прикрепите к кнопке созданный вами сценарий. Это действие должно рассматриваться как операция левого джойстика геймпада.

Попробуйте перемещать игру и нажимать на кнопки. Я думаю, вы можете видеть, что полученное значение меняется в зависимости от положения касания. Кроме того, вы можете видеть, что даже если вы перетаскиваете во время касания, значение меняется в зависимости от положения перетаскивания.