Anpassa skärmkontrollen för att implementera styrknappen

Sidan uppdaterad :
Datum för skapande av sida :

Verifiering miljö

Windows
  • Fönster 11
Unity-redaktör
  • 2020.3.25F1
Paket för inmatningssystem
  • 1.2.0

Förutsättningar för det här tipset

Följande inställningar har gjorts i förväg som en förutsättning för beskrivningen av detta tips.

Du bör också känna till följande tips:

Kontrollsticka på skärmen är standardorienteringsoperationen

On-Screen Stick implementeras som en virtuell pinne som replikerar driften av en pinne som liknar den som finns på en fysisk styrenhet. Om du till exempel vill flytta den åt höger kan du först trycka på skärmen för att röra vid pinnen och sedan skjuta den åt höger för att slå ner pinnen.

Det är lätt att föreställa sig som en stickoperation, men tvärtom, när du vill flytta den till höger omedelbart, (1) peka, (2) glida åt höger, Eftersom det kräver en tvåstegsoperation kommer svaret oundvikligen att bli lite försenat. Detta gäller särskilt när du behöver göra en snabb riktningsändring i rad.

Styrknapp som bestämmer riktningen vid beröringstillfället (icke-standard)

Den bästa inmatningsmetoden för snabb riktningsinmatning är en som kan bestämma riktningen vid beröringstillfället genom att placera något som en D-pad eller piltangent. Det är inte gjort av Unity, men i mitt spel Little Saber placerar jag piltangenterna så att du snabbt kan röra dig i den riktning du berör. Naturligtvis kan du också glida uppåt eller åt vänster medan du trycker för att byta rörelser.

Standarden för skärmkontroll är dock inte lätt att placera och implementera eftersom det bara finns On-Screen Sticks och On-Screen Buttons.

Du kan också skapa en pseudo-D-pad genom att ordna fyra knappar som visas i bilden nedan, men det är obekvämt eftersom du inte kan mata diagonalt. Om du placerar 8 knappar är diagonal drift möjlig, men flödesriktningsoperation som "← ↙ ↓" är fortfarande inte möjlig.

Skapa en riktningsplatta med anpassning av skärmkontroll

Som du kan se kommer On-Screen Control bara som standard med Stick and Button, men du kan anpassa de saknade funktionerna med dina egna skript. Så här skulle jag vilja skapa en riktningsplatta med anpassning av skärmkontroll.

Åtgärd karta

Vi kommer att använda åtgärdskartan, men vi kommer att använda den som skapades i föregående tips som den är, så proceduren utelämnas.

Du har också skrivit lite kod.

Placera objekt

Placera ett textområde för att visa dina inmatningar och en knapp som ersätter styrknappen. I det här fallet är knapparna ordnade, men du kan ersätta dem med en bild som är lättare att förstå.

Skript för att visa indata

Eftersom Skärmkontroll ersätter touch med interaktion med fysisk handkontroll Skapa ett skript som visar dina indata i text när åtgärdskartan fungerar.

Innehållet är detsamma som tidigare, så jag kommer att utelämna förklaringen.

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 ställt in det, kontrollera först om det fungerar med tangentbordet eller gamepad.

Anpassa kontroller på skärmen

Detta leder oss till huvudämnet för dessa tips. Att anpassa skärmkontrollen är ett skript, så skapa först ett skript. Namnet är godtyckligt, men i det här fallet OnScreenDpad är det .

Skriptet ser ut så här:

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 skapar en OnScreenControl anpassningsklass för Skärmkontroll genom att ärva från klassen Anpassa. Dessutom, för att ta emot olika beröringshändelser, ärva målgränssnittet. Här bearbetas "vid beröring", "när du rör dig medan du rör", "när beröringen släpps" och "innan du drar", så följande gränssnitt beskrivs också.

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
Gränssnittets innehåll
IPointerDownHandler Vid beröring
IPointerUpHandler När du släpper touchen
IDragHandler När du rör dig medan du rör vid
IInitializePotentialDragHandler Innan du börjar dra

Följande fält deklareras:

controlPathInternalOnScreenControl krävs om du ärver från en klass. Den innehåller en sträng av vilken knapp på inmatningsenheten som ska mappas till, men i princip kan du skriva den som den är. InputControl Attributet bör dock innehålla det värde som ska behållas (Vector2 i det här fallet).

_objectPosition_objectSizeHalf och håll halva knappens position och storlek i förväg och använd den senare för beräkningar.

[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, som anropas Start efter att objektet har initierats, får halva positionen och storleken på knappen på duken. Storleken tar också hänsyn till skalan. Start Den bearbetas med en metod, men om den kan användas korrekt i beräkningen i slutändan kan förvärvstiden vara var som helst.

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

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

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

OnInitializePotentialDrag anropas när du vill börja dra efter tryckgester. När du drar OnDrag anropas metoden. En tröskel är inställd för att förhindra dragbedömning i viss utsträckning så att fingret inte rör sig lite och dragdomen inte uppstår på grund av att man helt enkelt rör.

Den här gången, eftersom det är en ingång som förutsätter att du vill utföra en finjusteringsåtgärd, inaktiverar du den här tröskelinställningen och gör omedelbart en dragbedömning. eventData.useDragThreshold false Du kan åsidosätta tröskelvärdet genom att ange till .

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

Nedan visas händelserna som kallas när du rör, drar respektive släpper. OnPointerDown OnDrag Eftersom och , var och en utför Operate samma ingångsbehandling, så vi skapar en separat metod och kallar den. OnPointerUp Skicka den nu till metoden för att meddela kontrollen att SendValueToControl Vector2.zero den har slutat skriva.

/// <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 är kärnan D-pad ingångsoperation.

Först och främst kan beröringspositionen erhållas med , men eftersom denna koordinat är koordinaten för eventData.position spelskärmen, RectTransformUtility.ScreenPointToLocalPointInRectangle metod för att konvertera till dukkoordinater. Värdena som ska skickas är "duk", "beröringsposition", "kamera" och " RectTransformmottagningsvariabel".

Efter att ha fått "beröringspositionen på duken", konvertera den till knappens position som ursprung. Subtrahera bara objektets_objectPosition position ().

Därefter, eftersom beröringspositionen fortfarande är ett nummer på duken, konvertera detta värde till ett förhållande på 0 ~ 1. Om du delar med halva knappens storlek blir beröringspositionen inom knappens intervall 0 ~ 1. Om du drar med touch blir värdet 1 eller mer eftersom det accepterar åtgärder även utanför knappen. Vector2.ClampMagnitude Gör detta i metoden så att den inte överstiger 1.

Slutligen skickar du ingångsvärdet SendValueToControl konverterat till 0 ~ 1 till metoden. Kontrollen på skärmen gör resten åt 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);
}

Koppla skriptet du skapade till knappen. Den här åtgärden är inställd på att behandlas som funktionen av den vänstra styrspaken på Gamepad.

Prova att flytta spelet och tryck på knapparna. Jag tror att du kan se att det erhållna värdet ändras beroende på beröringspositionen. Du kan också se att även om du drar medan du trycker på ändras värdet beroende på dragpositionen.