Dostosuj sterowanie ekranowe, aby zaimplementować pad kierunkowy

Strona zaktualizowana :
Data utworzenia strony :

Środowisko weryfikacji

Windows
  • Okna 11
Edytor Unity
  • 2020.3.25f1
Pakiet systemu wejściowego
  • 1.2.0

Wymagania wstępne dotyczące tej porady

Poniższe ustawienia zostały wcześniej wprowadzone jako przesłanka do opisu tej wskazówki.

Powinieneś również zapoznać się z następującymi wskazówkami:

Drążek sterujący na ekranie to standardowa operacja orientacji

On-Screen Stick jest zaimplementowany jako wirtualny drążek, replikując działanie kija podobnego do tego, które można znaleźć na kontrolerze fizycznym. Na przykład, jeśli chcesz przesunąć go w prawo, możesz najpierw dotknąć ekranu, aby dotknąć drążka, a następnie przesunąć go w prawo, aby powalić drążek.

Łatwo to sobie wyobrazić jako operację drążka, ale wręcz przeciwnie, gdy chcesz natychmiast przesunąć go w prawo, (1) dotknąć, (2) przesunąć w prawo, Ponieważ wymaga to dwuetapowej operacji, reakcja nieuchronnie będzie nieco opóźniona. Jest to szczególnie ważne, gdy musisz szybko zmienić kierunek z rzędu.

D-pad określający kierunek w momencie dotyku (niestandardowy)

Najlepsza metoda wprowadzania szybkiego kierunku to taka, która może określić kierunek w momencie dotknięcia, umieszczając coś w rodzaju pada kierunkowego lub strzałki. Nie jest to zrobione przez Unity, ale w mojej grze Little Saber umieszczam strzałek, abyś mógł szybko poruszać się w kierunku, którego dotykasz. Oczywiście możesz również przesuwać w górę lub w lewo podczas dotykania, aby przełączać ruchy.

Jednak standard sterowania ekranowego nie jest łatwy do umieszczenia i wdrożenia, ponieważ istnieją tylko drążki ekranowe i przyciski ekranowe.

Możesz także utworzyć pseudo-D-pad, układając cztery przyciski, jak pokazano na poniższym rysunku, ale jest to niewygodne, ponieważ nie można wprowadzać po przekątnej. Jeśli umieścisz 8 przycisków, możliwa jest operacja po przekątnej, ale operacja kierunku przepływu, taka jak "← ↙ ↓", nadal nie jest możliwa.

Tworzenie padu kierunkowego z dostosowywaniem sterowania ekranowego

Jak widać, sterowanie ekranowe jest standardowo dostarczane tylko z drążkiem i przyciskiem, ale możesz dostosować brakujące funkcje za pomocą własnych skryptów. Więc tutaj chciałbym stworzyć pad kierunkowy z dostosowaniem sterowania ekranowego.

Plan działania

Wykorzystamy mapę akcji, ale użyjemy tej stworzonej w poprzednich wskazówkach taką, jaka jest, więc procedura zostanie pominięta.

Napisałeś też trochę kodu.

Pozycjonowanie obiektów

Umieść obszar tekstowy, aby wyświetlić wprowadzone dane wejściowe, oraz przycisk, który zastępuje pad kierunkowy. W takim przypadku przyciski są rozmieszczone, ale można je zastąpić obrazem, który jest łatwiejszy do zrozumienia.

Skrypt do wyświetlania danych wejściowych

Ponieważ sterowanie ekranowe zastępuje dotyk fizyczną interakcją z kontrolerem, Utwórz skrypt, który wyświetla dane wejściowe w tekście podczas działania mapy działania.

Treść jest taka sama jak poprzednio, więc pominę wyjaśnienie.

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

Po skonfigurowaniu najpierw sprawdź, czy działa z klawiaturą lub gamepadem.

Dostosowywanie elementów sterujących na ekranie

To prowadzi nas do głównego tematu tych wskazówek. Dostosowywanie formantu ekranowego jest skryptem, więc najpierw utwórz skrypt. Nazwa jest arbitralna, ale w tym przypadku OnScreenDpad jest .

Skrypt wygląda tak:

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

Klasę OnScreenControl dostosowywania Formant ekranowy tworzy się, dziedzicząc z klasy Dostosuj. Ponadto, aby odbierać różne zdarzenia dotykowe, dziedzicz interfejs docelowy. Tutaj przetwarzane są "podczas dotykania", "podczas poruszania się podczas dotykania", "po zwolnieniu dotyku" i "przed przeciąganiem", więc opisane są również następujące interfejsy.

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
Zawartość interfejsu
IPointerDownHandler Po dotknięciu
IPointerUpHandler Po zwolnieniu przycisku dotykowego
IDragHandler Gdy poruszasz się podczas dotykania
IInitializePotentialDragHandler Przed rozpoczęciem przeciągania

Następujące pola są deklarowane:

controlPathInternalOnScreenControl jest wymagany, jeśli dziedziczysz po klasie. Zawiera ciąg przycisków urządzenia wejściowego, na który należy zmapować, ale w zasadzie można go zapisać takim, jakim jest. InputControl Atrybut powinien jednak zawierać wartość, która ma zostać zachowana (w tym przypadku Vector2).

_objectPosition_objectSizeHalf i przytrzymaj wcześniej połowę pozycji i rozmiaru przycisku i użyj go później do obliczeń.

[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;

Metoda, wywoływana Start po zainicjowaniu obiektu, uzyskuje połowę położenia i rozmiaru przycisku na obszarze roboczym. Rozmiar uwzględnia również skalę. Start Jest przetwarzany przez metodę, ale jeśli można go poprawnie wykorzystać w obliczeniach na końcu, czas akwizycji może być w dowolnym miejscu.

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

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

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

OnInitializePotentialDrag jest wywoływany, gdy chcesz rozpocząć przeciąganie po dotknięciu. Podczas przeciągania OnDrag wywoływana jest metoda. Próg jest ustawiony, aby w pewnym stopniu zapobiec osądowi przeciągania, tak aby palec nie poruszał się trochę, a osąd przeciągania nie występował z powodu operacji zwykłego dotykania.

Tym razem, ponieważ jest to dane wejściowe, które zakładają, że chcesz wykonać operację dostrajania, wyłącz to ustawienie progu i natychmiast dokonaj oceny przeciągania. eventData.useDragThreshold false Możesz zastąpić próg, ustawiając na .

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

Poniżej znajdują się zdarzenia wywoływane odpowiednio podczas dotykania, przeciągania i zwalniania. OnPointerDown OnDrag Ponieważ i , każdy wykonuje Operate to samo przetwarzanie danych wejściowych, więc tworzymy osobną metodę i wywołujemy ją. OnPointerUp Teraz przekaż go do metody, aby SendValueToControl Vector2.zero powiadomić kontrolkę, że przestała pisać.

/// <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 Metoda jest podstawową operacją wejściową D-pad.

Przede wszystkim pozycję dotyku można uzyskać za pomocą , ale ponieważ ta współrzędna jest współrzędną eventData.position ekranu gry, RectTransformUtility.ScreenPointToLocalPointInRectangle Metoda konwersji na współrzędne kanwy. Wartości, które mają zostać przekazane, to "canvas", "touch position", "camera" i " RectTransformreceiving variable".

Po uzyskaniu "pozycji dotykowej na płótnie" przekonwertuj ją na pozycję przycisku jako źródło. Wystarczy odjąć pozycję ()_objectPosition obiektu.

Następnie, ponieważ pozycja dotykowa jest nadal liczbą na kanwie, przekonwertuj tę wartość na stosunek 0 ~ 1. Jeśli podzielisz przez połowę rozmiaru przycisku, pozycja dotyku w zakresie przycisku wyniesie 0 ~ 1. Jeśli przeciągniesz dotykiem, wartość będzie wynosić 1 lub więcej, ponieważ akceptuje operacje nawet poza przyciskiem. Vector2.ClampMagnitude Zrób to w metodzie, aby nie przekraczała 1.

Na koniec przekaż wartość SendValueToControl wejściową przekonwertowaną na 0 ~ 1 do metody. Sterowanie ekranowe zrobi resztę za Ciebie.

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

Dołącz utworzony skrypt do przycisku. Ta akcja jest ustawiona jako działanie lewego drążka gamepada.

Spróbuj przenieść grę i dotknij przycisków. Myślę, że widać, że uzyskana wartość zmienia się w zależności od pozycji dotyku. Widać również, że nawet jeśli przeciągniesz podczas dotykania, wartość zmienia się w zależności od pozycji przeciągania.