Prispôsobenie ovládacieho prvku na obrazovke na implementáciu smerového ovládača

Stránka aktualizovaná :
Dátum vytvorenia strany :

Prostredie overovania

Windows
  • Windows 11
Editor jednoty
  • 2020.3.25f1
Vstupný systémový balík
  • 1.2.0

Predpoklady pre tento tip

Nasledujúce nastavenia boli vykonané vopred ako predpoklad pre popis tohto tipu.

Mali by ste byť tiež oboznámení s nasledujúcimi tipmi:

Ovládacia páčka na obrazovke je štandardná orientačná operácia

On-Screen Stick je implementovaný ako virtuálna palica, ktorá replikuje činnosť palice podobnej tej, ktorá sa nachádza na fyzickom ovládači. Ak ju napríklad chcete presunúť doprava, môžete sa najprv dotknúť obrazovky, aby ste sa dotkli páčky, a potom ju posunutím doprava zraziť.

Je ľahké si to predstaviť ako operáciu palice, ale naopak, keď ju chcete okamžite presunúť doprava, (1) dotknite sa, (2) posuňte doprava, Keďže vyžaduje dvojstupňovú operáciu, reakcia bude nevyhnutne trochu oneskorená. To platí najmä vtedy, keď potrebujete urobiť rýchlu zmenu smeru v rade.

Smerový ovládač, ktorý určuje smer dotyku (neštandardný)

Najlepšia metóda vstupu pre rýchle zadávanie smeru je taká, ktorá dokáže určiť smer v okamihu dotyku umiestnením niečoho ako smerový ovládač alebo kláves so šípkou. Nie je vyrobený spoločnosťou Unity, ale v mojej hre Little Saber umiestňujem klávesy so šípkami, aby ste sa mohli rýchlo pohybovať smerom, ktorého sa dotknete. Samozrejme, môžete sa tiež posunúť nahor alebo doľava pri dotyku a prepínať pohyby.

Štandard ovládania na obrazovke však nie je ľahké umiestniť a implementovať, pretože existujú iba páčky na obrazovke a tlačidlá na obrazovke.

Môžete tiež vytvoriť pseudo-D-pad usporiadaním štyroch tlačidiel, ako je znázornené na obrázku nižšie, ale je to nepohodlné, pretože nemôžete zadávať diagonálne. Ak umiestnite 8 tlačidiel, diagonálne ovládanie je možné, ale operácia smeru prúdenia, napríklad "← ↙ ↓", stále nie je možná.

Vytvorenie smerového ovládača s prispôsobením ovládania na obrazovke

Ako vidíte, ovládanie na obrazovke je štandardne dodávané iba s Stick and Button, ale chýbajúce funkcie môžete prispôsobiť vlastnými skriptami. Takže tu by som chcel vytvoriť smerový ovládač s prispôsobením ovládania na obrazovke.

Akčný plán

Použijeme akčnú mapu, ale použijeme mapu vytvorenú v predchádzajúcich tipoch tak, ako je, takže postup je vynechaný.

Napísali ste aj nejaký kód.

Umiestňovanie objektov

Umiestnite textovú oblasť na zobrazenie vstupu a tlačidlo, ktoré nahradí smerový ovládač. V takom prípade sú tlačidlá usporiadané, ale môžete ich nahradiť obrázkom, ktorý je zrozumiteľnejší.

Skript na zobrazenie vstupu

Keďže ovládanie na obrazovke nahrádza dotyk fyzickou interakciou ovládača, Vytvorte skript, ktorý zobrazuje váš vstup v texte počas práce mapy akcií.

Obsah je rovnaký ako predtým, takže vysvetlenie vynechám.

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 nastavení najskôr skontrolujte, či funguje s klávesnicou alebo gamepadom.

Prispôsobenie ovládacích prvkov na obrazovke

To nás privádza k hlavnej téme týchto tipov. Prispôsobenie ovládacieho prvku na obrazovke je skript, preto najprv vytvorte skript. Názov je ľubovoľný, ale v tomto prípade OnScreenDpad je to .

Skript vyzerá takto:

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

Triedu OnScreenControl prispôsobenia ovládania na obrazovke vytvoríte dedením z triedy prispôsobenia. Okrem toho, ak chcete prijímať rôzne dotykové udalosti, zdediť cieľové rozhranie. Tu sa spracovávajú "pri dotyku", "pri pohybe pri dotyku", "pri uvoľnení dotyku" a "pred ťahaním", takže sú popísané aj nasledujúce rozhrania.

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
Obsah rozhrania
IPointerDownHandler Pri dotyku
IPointerUpHandler Keď dotyk uvoľníte
IDragHandler Keď sa pohybujete pri dotyku
IInitializePotentialDragHandler Skôr než začnete presúvať

Deklarujú sa tieto polia:

controlPathInternalOnScreenControl sa vyžaduje, ak dedíte z triedy. Obsahuje reťazec, na ktoré tlačidlo vstupného zariadenia sa má mapovať, ale v podstate ho môžete napísať tak, ako je. InputControl Tento atribút by však mal obsahovať hodnotu, ktorá sa má zachovať (v tomto prípade Vector2).

_objectPosition_objectSizeHalf a vopred podržte polovicu polohy a veľkosti tlačidla a neskôr ho použite na výpočty.

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

Metóda, nazývaná Start po inicializácii objektu, získa polovicu polohy a veľkosti tlačidla na plátne. Veľkosť zohľadňuje aj stupnicu. Start Spracováva sa metódou, ale ak sa dá správne použiť pri výpočte na konci, načasovanie akvizície môže byť kdekoľvek.

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

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

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

OnInitializePotentialDrag sa volá, keď chcete začať ťahať po dotyku. Počas ťahania OnDrag sa metóda nazýva. Prahová hodnota je nastavená tak, aby do určitej miery zabránila úsudku ťahu, aby sa prst trochu nepohyboval a úsudok ťahu nenastal v dôsledku operácie jednoduchého dotyku.

Tentokrát, keďže ide o vstup, ktorý predpokladá, že chcete vykonať operáciu jemného doladenia, vypnite toto nastavenie prahovej hodnoty a okamžite vykonajte úsudok ťahania. eventData.useDragThreshold false Prahovú hodnotu môžete prepísať nastavením na .

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

Nižšie sú uvedené udalosti, ktoré sa nazývajú pri dotyku, pretiahnutí a uvoľnení. OnPointerDown OnDrag Keďže a , každý vykonáva Operate rovnaké spracovanie vstupov, preto vytvoríme samostatnú metódu a nazveme ju. OnPointerUp Teraz ho odovzdajte metóde, aby ste SendValueToControl Vector2.zero upozornili ovládací prvok, že prestal písať.

/// <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 Metóda je vstupná operácia jadra smerového ovládača.

Po prvé, dotykovú polohu je možné získať pomocou , ale keďže táto súradnica je súradnicou hernej eventData.position obrazovky, RectTransformUtility.ScreenPointToLocalPointInRectangle metóda prevodu na súradnice plátna. Hodnoty, ktoré sa majú odovzdať, sú "plátno", "dotyková pozícia", "fotoaparát" a " RectTransformprijímacia premenná".

Po získaní "dotykovej polohy na plátne" ju preveďte na polohu tlačidla ako počiatok. Stačí odpočítať polohu () objektu_objectPosition.

Ďalej, keďže dotyková pozícia je stále číslom na plátne, skonvertujte túto hodnotu na pomer 0 ~ 1. Ak vydelíte polovicou veľkosti tlačidla, dotyková poloha v dosahu tlačidla bude 0~1. Ak ťaháte dotykom, hodnota bude 1 alebo viac, pretože prijíma operácie aj mimo tlačidla. Vector2.ClampMagnitude Urobte to v metóde tak, aby neprekročila 1.

Nakoniec odovzdajte vstupnú hodnotu SendValueToControl prevedenú na 0 ~ 1 metóde. Ovládanie na obrazovke sa postará o zvyšok za vás.

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

Pripojte vytvorený skript k tlačidlu. Táto akcia sa má považovať za činnosť ľavej páčky gamepadu.

Skúste hru presunúť a dotknúť sa tlačidiel. Myslím, že môžete vidieť, že získaná hodnota sa mení v závislosti od dotykovej polohy. Môžete tiež vidieť, že aj keď ťaháte počas dotyku, hodnota sa mení v závislosti od polohy ťahania.