Personalizar o controle na tela para implementar o D-pad

Página atualizada :
Data de criação de página :

Ambiente de verificação

Windows
  • Janelas 11
Unity Editor
  • 25.3.2020F1
Pacote do sistema de entrada
  • 1.2.0

Pré-requisitos para esta dica

As configurações a seguir foram feitas com antecedência como premissa para a descrição desta dica.

Você também deve estar familiarizado com as seguintes dicas:

O Stick de Controle na Tela é a operação de orientação padrão

O On-Screen Stick é implementado como um stick virtual, replicando o funcionamento de um stick semelhante ao encontrado em um controlador físico. Por exemplo, se você quiser movê-lo para a direita, você pode primeiro tocar na tela para tocar no stick e, em seguida, deslizá-lo para a direita para derrubá-lo para baixo.

É fácil imaginar como uma operação de vara, mas, pelo contrário, quando você quer movê-lo para a direita imediatamente, (1) toque, (2) deslize para a direita, Uma vez que requer uma operação em duas etapas, a resposta será inevitavelmente um pouco atrasada. Isso é especialmente verdadeiro quando você precisa fazer uma mudança rápida de direção em uma linha.

D-pad que determina a direção no momento do toque (não padrão)

O melhor método de entrada para entrada rápida de direção é aquele que pode determinar a direção no momento do toque, colocando algo como um D-pad ou uma tecla de seta. Não é feito por Unity, mas no meu jogo Little Saber, eu coloco as setas para que você possa se mover rapidamente na direção que você toca. Claro, você também pode deslizar para cima ou para a esquerda enquanto toca para alternar os movimentos.

No entanto, o padrão de controle na tela não é fácil de colocar e implementar porque existem apenas manípulos na tela e botões na tela.

Você também pode criar um pseudo-D-pad organizando quatro botões como mostrado na figura abaixo, mas é inconveniente porque você não pode inserir diagonalmente. Se você colocar 8 botões, a operação diagonal é possível, mas a operação de direção de fluxo como "← ↙ ↓" ainda não é possível.

Criar um direcional com a personalização do Controle na Tela

Como você pode ver, o Controle na Tela só vem de fábrica com Stick e Button, mas você pode personalizar os recursos ausentes com seus próprios scripts. Então aqui eu gostaria de criar um direcional com personalização do Controle na Tela.

Mapa de Ação

Usaremos o mapa de ação, mas usaremos o criado nas dicas anteriores como está, então o procedimento é omitido.

Você também escreveu algum código.

Posicionando objetos

Coloque uma área de texto para exibir sua entrada e um botão que substitua o D-pad. Nesse caso, os botões são organizados, mas você pode substituí-los por uma imagem mais fácil de entender.

Script para exibir a entrada

Como o Controle na Tela substitui o toque pela interação física do controlador, Crie um script que exiba sua entrada em texto à medida que o mapa de ação funciona.

O conteúdo é o mesmo de antes, por isso vou omitir a explicação.

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

Depois de configurá-lo, primeiro verifique se ele funciona com seu teclado ou gamepad.

Personalizando controles na tela

Isso nos leva ao tema principal dessas dicas. Personalizar o controle na tela é um script, portanto, primeiro crie um script. O nome é arbitrário, mas neste caso OnScreenDpad é .

O script tem a seguinte aparência:

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

Você cria uma OnScreenControl classe de personalização de controle na tela herdando da classe Personalizar. Além disso, para receber vários eventos de toque, herde a interface de destino. Aqui, "ao tocar", "ao mover-se ao tocar", "quando o toque é liberado" e "antes de arrastar" são processados, de modo que as seguintes interfaces também são descritas, respectivamente.

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
Conteúdo da Interface
IPointerDownHandler Quando tocado
IPointerUpHandler Quando você solta o toque
IDragHandler Quando você se move ao tocar
IInitializePotentialDragHandler Antes de começar a arrastar

São declarados os seguintes campos:

controlPathInternalOnScreenControl é obrigatório se você herdar de uma classe. Ele contém uma sequência de qual botão do dispositivo de entrada para mapear, mas basicamente você pode escrevê-lo como está. InputControl No entanto, o atributo deve conter o valor a ser retido (Vector2 neste caso).

_objectPosition_objectSizeHalf e segure metade da posição e do tamanho do botão com antecedência e use-o mais tarde para cálculos.

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

O método, chamado Start depois que o objeto é inicializado, obtém metade da posição e do tamanho do botão na tela. O tamanho também leva em conta a escala. Start Ele é processado por um método, mas se puder ser usado corretamente no cálculo no final, o tempo de aquisição pode ser em qualquer lugar.

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

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

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

OnInitializePotentialDrag é chamado quando você deseja começar a arrastar após o toque. Ao arrastar, OnDrag o método é chamado. Um limiar é definido para evitar o julgamento de arrasto até certo ponto, para que o dedo não se mova um pouco e o julgamento de arrasto não ocorra devido à operação de simplesmente tocar.

Desta vez, como é uma entrada que pressupõe que você deseja executar uma operação de ajuste fino, desabilite essa configuração de limite e faça imediatamente um julgamento de arraste. eventData.useDragThreshold false Você pode substituir o limite definindo como .

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

Abaixo estão os eventos chamados ao tocar, arrastar e soltar, respectivamente. OnPointerDown OnDrag Como e , cada um executa Operate o mesmo processamento de entrada, então criamos um método separado e o chamamos. OnPointerUp Agora, passe-o para o método para SendValueToControl Vector2.zero notificar o controle de que ele parou de digitar.

/// <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 O método é a operação de entrada D-pad principal.

Em primeiro lugar, a posição de toque pode ser obtida com , mas como esta coordenada é a eventData.position coordenada da tela do jogo, RectTransformUtility.ScreenPointToLocalPointInRectangle método para converter em coordenadas de tela. Os valores a serem passados são "canvas", "touch position", "camera" e " RectTransformreceiving variable".

Depois de obter a "posição de toque na tela", converta-a para a posição do botão como origem. Basta subtrair a posição () do_objectPosition objeto.

Em seguida, como a posição de toque ainda é um número na tela, converta esse valor em uma proporção de 0~1. Se você dividir pela metade o tamanho do botão, a posição de toque dentro do intervalo do botão será de 0 ~ 1. Se você arrastar com toque, o valor será 1 ou mais, pois ele aceita operações mesmo fora do botão. Vector2.ClampMagnitude Faça isso no método para que ele não exceda 1.

Finalmente, passe o valor SendValueToControl de entrada convertido para 0~1 para o método. O Controle na tela faz o resto por você.

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

Anexe o script criado ao botão. Esta ação está definida para ser tratada como a operação do manípulo esquerdo do Gamepad.

Tente mover o jogo e toque nos botões. Acho que dá para ver que o valor obtido muda dependendo da posição de toque. Além disso, você pode ver que, mesmo se você arrastar durante o toque, o valor muda dependendo da posição de arrastar.