Personalitzeu el control en pantalla per implementar el D-pad

Pàgina actualitzada :
Data de creació de la pàgina :

Entorn de verificació

Windows
  • Finestres 11
Editor d'unitat
  • 25.3.2020
Paquet del sistema d'entrada
  • 1.2.0

Requisits previs per a aquest consell

La configuració següent s'ha fet amb antelació com a premissa per a la descripció d'aquest consell.

També hauríeu de conèixer els consells següents:

On-Screen Control Stick és l'operació d'orientació estàndard

El On-Screen Stick s'implementa com un llapis virtual, replicant el funcionament d'un pal similar al que es troba en un controlador físic. Per exemple, si voleu moure'l cap a la dreta, primer podeu tocar la pantalla per tocar el pal i, a continuació, lliscar-lo cap a la dreta per tombar el pal.

És fàcil imaginar com una operació de pal, però al contrari, quan es vol moure cap a la dreta immediatament, (1) tocar, (2) lliscar cap a la dreta, Com que requereix una operació de dos passos, la resposta inevitablement es retardarà una mica. Això és especialment cert quan necessiteu fer un canvi ràpid de direcció seguit.

D-pad que determina la direcció en el moment del tacte (no estàndard)

El millor mètode d'entrada per a una entrada de direcció ràpida és aquell que pot determinar la direcció en el moment de tocar col·locant alguna cosa com un teclat D o una tecla de fletxa. No està fet per Unity, però en el meu joc Little Saber, col·loco les tecles de fletxa perquè puguis moure't ràpidament en la direcció que toques. Per descomptat, també podeu lliscar cap amunt o cap a l'esquerra mentre toqueu per canviar de moviment.

Tanmateix, l'estàndard de control en pantalla no és fàcil de col·locar i implementar perquè només hi ha pals a la pantalla i botons a la pantalla.

També podeu crear un pseudo-D-pad organitzant quatre botons com es mostra a la figura següent, però és inconvenient perquè no podeu introduir diagonalment. Si col·loqueu 8 botons, és possible operar en diagonal, però encara no és possible operar amb la direcció del flux com ara "← ↙ ↓".

Creeu un coixinet direccional amb la personalització del control en pantalla

Com podeu veure, el control en pantalla només ve de sèrie amb Stick and Button, però podeu personalitzar les funcions que falten amb els vostres propis scripts. Així que aquí m'agradaria crear un coixinet direccional amb personalització de control en pantalla.

Mapa d'accions

Utilitzarem el mapa d'accions, però utilitzarem tal com està el creat en els consells anteriors, de manera que s'omet el procediment.

També has escrit algun codi.

Col·locació d'objectes

Col·loqueu una àrea de text per mostrar l'entrada i un botó que substitueixi el coixinet D. En aquest cas, els botons estan organitzats, però podeu substituir-los per una imatge més fàcil d'entendre.

Script per mostrar l'entrada

Com que el control en pantalla substitueix el tacte per la interacció del controlador físic, Creeu un script que mostri l'entrada al text mentre funciona el mapa d'accions.

El contingut és el mateix que abans, així que ometreé l'explicació.

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

Després de configurar-lo, comproveu primer si funciona amb el teclat o el gamepad.

Personalització dels controls en pantalla

Això ens porta al tema principal d'aquests consells. La personalització del control en pantalla és un script, així que primer creeu un script. El nom és arbitrari, però en aquest cas OnScreenDpad és .

El guió té aquest aspecte:

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

Creeu una OnScreenControl classe de personalització de control en pantalla heretant de la classe Personalitza. A més, per rebre diversos esdeveniments tàctils, hereteu la interfície de destinació. Aquí, es processen "quan es toca", "quan es mou mentre es toca", "quan es deixa anar el tacte" i "abans d'arrossegar", de manera que també es descriuen respectivament les interfícies següents.

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
de
Contingutsla interfície
IPointerDownHandler Quan es toca
IPointerUpHandler Quan deixeu anar el tacte
IDragHandler Quan et mous mentre toques
IInitializePotentialDragHandler Abans de començar a arrossegar

Es declaren els següents camps:

controlPathInternalOnScreenControl és obligatori si heretes d'una classe. Té una cadena de quin botó del dispositiu d'entrada assignar, però bàsicament podeu escriure-la tal com és. InputControl Tanmateix, l'atribut ha de contenir el valor que s'ha de conservar (Vector2 en aquest cas).

_objectPosition_objectSizeHalf i manteniu la meitat de la posició i la mida del botó per endavant i utilitzeu-lo més tard per als càlculs.

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

El mètode, anomenat Start després d'inicialitzar l'objecte, obté la meitat de la posició i la mida del botó del llenç. La mida també té en compte l'escala. Start Es processa mitjançant un mètode, però si es pot utilitzar correctament en el càlcul al final, el temps d'adquisició pot estar en qualsevol lloc.

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

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

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

OnInitializePotentialDrag es diu quan es vol començar a arrossegar després de tocar-lo. Mentre s'arrossega OnDrag , s'anomena mètode. S'estableix un llindar per evitar el judici d'arrossegament fins a cert punt perquè el dit no es mogui una mica i el judici d'arrossegament no es produeixi a causa de l'operació de simplement tocar.

Aquesta vegada, com que es tracta d'una entrada que assumeix que voleu realitzar una operació d'afinació, desactiveu aquesta configuració de llindar i feu immediatament un judici d'arrossegament. eventData.useDragThreshold false Podeu substituir el llindar definint a .

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

A continuació es mostren els esdeveniments anomenats en tocar, arrossegar i deixar anar, respectivament. OnPointerDown OnDrag Com que i , cadascun realitza Operate el mateix processament d'entrada, de manera que creem un mètode separat i l'anomenem. OnPointerUp Ara, passeu-lo al mètode per SendValueToControl Vector2.zero notificar al control que ha deixat d'escriure.

/// <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 El mètode és l'operació d'entrada D-pad del nucli.

En primer lloc, la posició tàctil es pot obtenir amb , però com que aquesta coordenada és la coordenada de la eventData.position pantalla del joc, RectTransformUtility.ScreenPointToLocalPointInRectangle mètode per convertir en coordenades de llenç. Els valors a passar són "llenç", "posició tàctil", "càmera" i " RectTransformvariable de recepció".

Després d'obtenir la "posició tàctil al llenç", convertiu-la a la posició del botó com a origen. Només cal restar la posició () de l'objecte_objectPosition.

A continuació, com que la posició tàctil encara és un número al llenç, convertiu aquest valor en una proporció de 0 ~ 1. Si dividiu per la meitat la mida del botó, la posició tàctil dins del rang del botó serà 0 ~ 1. Si arrossegueu amb el tacte, el valor serà 1 o més perquè accepta operacions fins i tot fora del botó. Vector2.ClampMagnitude Feu-ho en el mètode perquè no superi 1.

Finalment, passeu el valor SendValueToControl d'entrada convertit a 0 ~ 1 al mètode. El control en pantalla fa la resta per tu.

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

Adjunteu l'script que heu creat al botó. Aquesta acció està configurada per ser tractada com l'operació del pal esquerre del Gamepad.

Proveu de moure el joc i toqueu els botons. Crec que es pot veure que el valor obtingut canvia en funció de la posició tàctil. A més, podeu veure que, fins i tot si arrossegueu mentre toqueu, el valor canvia en funció de la posició d'arrossegament.