Tilpas skærmkontrollen for at implementere D-blokken

Side opdateret :
Dato for oprettelse af side :

Miljø til bekræftelse

Windows
  • Windows 11
Enhedslistens redaktør
  • 2020.3.25f1
Input System Pakke
  • 1.2.0

Forudsætninger for dette tip

Følgende indstillinger er foretaget på forhånd som en forudsætning for beskrivelsen af dette tip.

Du bør også være bekendt med følgende tips:

On-Screen Control Stick er standardorienteringsoperationen

On-Screen Stick implementeres som en virtuel stick, der replikerer driften af en pind svarende til den, der findes på en fysisk controller. For eksempel, hvis du vil flytte den til højre, kan du først røre ved skærmen for at røre ved pinden og derefter skubbe den til højre for at slå pinden ned.

Det er let at forestille sig som en pindoperation, men tværtimod, når du straks vil flytte den til højre, (1) berøring, (2) skub til højre, Da det kræver en to-trins operation, vil svaret uundgåeligt blive lidt forsinket. Dette gælder især, når du skal foretage en hurtig retningsændring i træk.

D-blok, der bestemmer retningen i berøringsøjeblikket (ikke-standard)

Den bedste inputmetode til hurtig retningsinput er en, der kan bestemme retningen i berøringsøjeblikket ved at placere noget som en D-blok eller piletast. Det er ikke lavet af Unity, men i mit spil Little Saber placerer jeg piletasterne, så du hurtigt kan bevæge dig i den retning, du rører ved. Selvfølgelig kan du også glide op eller venstre, mens du rører for at skifte bevægelse.

Imidlertid er On-Screen Control-standarden ikke let at placere og implementere, fordi der kun er On-Screen Sticks og On-Screen Buttons.

Du kan også oprette en pseudo-D-pad ved at arrangere fire knapper som vist i figuren nedenfor, men det er ubelejligt, fordi du ikke kan indtaste diagonalt. Hvis du placerer 8 knapper, er diagonal betjening mulig, men flowretningsbetjening som "← ↙ ↓" er stadig ikke mulig.

Opret en retningsbestemt pude med tilpasning af skærmkontrol

Som du kan se, leveres skærmkontrol kun som standard med Stick og Button, men du kan tilpasse de manglende funktioner med dine egne scripts. Så her vil jeg gerne oprette en retningspude med tilpasning af skærmkontrol.

Handlingsoversigt

Vi vil bruge handlingskortet, men vi vil bruge det, der blev oprettet i de foregående tip, som det er, så proceduren udelades.

Du har også skrevet noget kode.

Placering af objekter

Placer et tekstområde for at få vist dit input og en knap, der erstatter D-blokken. I dette tilfælde er knapperne arrangeret, men du kan erstatte dem med et billede, der er lettere at forstå.

Script til visning af input

Da kontrol på skærmen erstatter berøring med fysisk controllerinteraktion, Opret et script, der viser dit input i tekst, mens handlingsoversigten fungerer.

Indholdet er det samme som før, så jeg vil udelade forklaringen.

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

Når du har konfigureret det, skal du først kontrollere, om det fungerer med dit tastatur eller gamepad.

Tilpasning af kontrolelementer på skærmen

Dette bringer os til hovedemnet for disse tip. Tilpasning af kontrolelementet på skærmen er et script, så opret først et script. Navnet er vilkårligt, men i dette tilfælde OnScreenDpad er det .

Scriptet ser sådan ud:

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

Du opretter en OnScreenControl tilpasningsklasse for kontrolelementer på skærmen ved at arve fra klassen Tilpas. For at modtage forskellige berøringshændelser skal du desuden arve målgrænsefladen. Her behandles "når du rører", "når du bevæger dig under berøring", "når berøringen frigives" og "før du trækker", så følgende grænseflader beskrives også henholdsvis.

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
Interface indhold
IPointerDownHandler Ved berøring
IPointerUpHandler Når du slipper berøringen
IDragHandler Når du bevæger dig, mens du rører
IInitializePotentialDragHandler Før du begynder at trække

Følgende felter erklæres:

controlPathInternalOnScreenControl er påkrævet, hvis du arver fra en klasse. Den indeholder en streng af hvilken knap på inputenheden, der skal kortlægges til, men dybest set kan du skrive den som den er. InputControl Attributten skal dog indeholde den værdi, der skal bevares (Vector2 i dette tilfælde).

_objectPosition_objectSizeHalf og hold halvdelen af knappens position og størrelse på forhånd og brug den senere til beregninger.

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

Metoden, der kaldes Start efter objektet er initialiseret, får halvdelen af positionen og størrelsen af knappen på lærredet. Størrelsen tager også højde for skalaen. Start Det behandles ved hjælp af en metode, men hvis det kan bruges korrekt i beregningen i sidste ende, kan erhvervelsestidspunktet være hvor som helst.

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

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

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

OnInitializePotentialDrag kaldes, når du vil begynde at trække efter berøring. Mens du trækker, OnDrag kaldes metoden. En tærskel er indstillet til at forhindre trækdom til en vis grad, så fingeren ikke bevæger sig lidt, og trækdommen ikke opstår på grund af operationen med blot at røre.

Denne gang, da det er et input, der antager, at du vil udføre en finjusteringsoperation, skal du deaktivere denne tærskelindstilling og straks foretage en trækdom. eventData.useDragThreshold false Du kan tilsidesætte tærsklen ved at indstille til .

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

Nedenfor er de begivenheder, der kaldes, når du henholdsvis rører, trækker og slipper. OnPointerDown OnDrag Siden og , hver udfører Operate den samme inputbehandling, så vi opretter en separat metode og kalder den. OnPointerUp Send det nu til metoden for at underrette kontrollen om, at den er stoppet med at SendValueToControl Vector2.zero skrive.

/// <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 Metoden er den centrale D-pad-indgangsoperation.

Først og fremmest berøringspositionen kan opnås med , men da denne koordinat er koordinaten på eventData.position spilskærmen, RectTransformUtility.ScreenPointToLocalPointInRectangle metode til at konvertere til lærredskoordinater. De værdier, der skal overføres, er "lærred", "berøringsposition", "kamera" og " RectTransformmodtagevariabel".

Når du har fået "berøringspositionen på lærredet", skal du konvertere den til knappens position som oprindelse. Bare træk objektets_objectPosition position () fra.

Dernæst, da berøringspositionen stadig er et tal på lærredet, skal du konvertere denne værdi til et forhold på 0 ~ 1. Hvis du dividerer med halvdelen af knappens størrelse, vil berøringspositionen inden for knappens rækkevidde være 0 ~ 1. Hvis du trækker med berøring, vil værdien være 1 eller mere, fordi den accepterer handlinger selv uden for knappen. Vector2.ClampMagnitude Gør dette i metoden, så den ikke overstiger 1.

Til sidst skal du sende inputværdien SendValueToControl konverteret til 0 ~ 1 til metoden. Skærmkontrollen gør resten for dig.

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

Vedhæft det script, du oprettede, til knappen. Denne handling er indstillet til at blive behandlet som betjeningen af den venstre pind på gamepad.

Prøv at flytte spillet, og tryk på knapperne. Jeg tror, du kan se, at den opnåede værdi ændres afhængigt af berøringspositionen. Du kan også se, at selvom du trækker, mens du rører, ændres værdien afhængigt af trækpositionen.