ปรับแต่งการควบคุมบนหน้าจอเพื่อใช้ D-pad

ปรับปรุงหน้า :
วันที่สร้างเพจ :

สภาพแวดล้อมการตรวจสอบ

หน้าต่าง
  • หน้าต่าง 11
บรรณาธิการ Unity
  • ปี 2020.3.25f1
แพ็คเกจระบบอินพุต
  • 1.2.0

ข้อกําหนดเบื้องต้นสําหรับเคล็ดลับนี้

การตั้งค่าต่อไปนี้ได้ทําไว้ล่วงหน้าเพื่อเป็นหลักฐานสําหรับคําอธิบายของเคล็ดลับนี้

คุณควรทําความคุ้นเคยกับเคล็ดลับต่อไปนี้:

Control Stick บนหน้าจอเป็นการดําเนินการวางแนวมาตรฐาน

On-Screen Stick ถูกนํามาใช้เป็นแท่งเสมือนโดยจําลองการทํางานของแท่งที่คล้ายกับที่พบในตัวควบคุมทางกายภาพ ตัวอย่างเช่นหากคุณต้องการเลื่อนไปทางขวาคุณสามารถสัมผัสหน้าจอเพื่อสัมผัสแท่งก่อนจากนั้นเลื่อนไปทางขวาเพื่อเคาะแท่งลง

มันง่ายที่จะจินตนาการว่าเป็นการดําเนินการติด แต่ในทางตรงกันข้ามเมื่อคุณต้องการย้ายไปทางขวาทันที (1) สัมผัส (2) เลื่อนไปทางขวา เนื่องจากต้องมีการดําเนินการสองขั้นตอนการตอบสนองจะล่าช้าเล็กน้อยอย่างหลีกเลี่ยงไม่ได้ นี่เป็นเรื่องจริงโดยเฉพาะอย่างยิ่งเมื่อคุณต้องการเปลี่ยนทิศทางอย่างรวดเร็วติดต่อกัน

D-pad ที่กําหนดทิศทางในขณะที่สัมผัส (ไม่ได้มาตรฐาน)

วิธีการป้อนข้อมูลที่ดีที่สุดสําหรับการป้อนข้อมูลทิศทางอย่างรวดเร็วคือวิธีที่สามารถกําหนดทิศทางในขณะที่สัมผัสโดยวางบางอย่างเช่น D-pad หรือปุ่มลูกศร มันไม่ได้ทําโดย Unity แต่ในเกม Little Saber ของฉันฉันวางปุ่มลูกศรเพื่อให้คุณสามารถเคลื่อนที่ไปในทิศทางที่คุณสัมผัสได้อย่างรวดเร็ว แน่นอนคุณยังสามารถเลื่อนขึ้นหรือซ้ายในขณะที่สัมผัสเพื่อสลับการเคลื่อนไหว

อย่างไรก็ตามมาตรฐานการควบคุมบนหน้าจอไม่ใช่เรื่องง่ายที่จะวางและนําไปใช้เนื่องจากมีเฉพาะแท่งบนหน้าจอและปุ่มบนหน้าจอ

คุณยังสามารถสร้าง pseudo-D-pad โดยจัดเรียงปุ่มสี่ปุ่มดังแสดงในรูปด้านล่าง แต่ไม่สะดวกเพราะคุณไม่สามารถป้อนข้อมูลในแนวทแยงมุมได้ หากคุณวางปุ่ม 8 ปุ่ม การทํางานในแนวทแยงมุมจะเป็นไปได้ แต่การทํางานของทิศทางการไหล เช่น "← ↙ ↓" ยังไม่สามารถทําได้

สร้างแผ่นทิศทางด้วยการปรับแต่งการควบคุมบนหน้าจอ

อย่างที่คุณเห็นการควบคุมบนหน้าจอมาพร้อมกับ Stick และ Button มาตรฐานเท่านั้น แต่คุณสามารถปรับแต่งคุณสมบัติที่ขาดหายไปด้วยสคริปต์ของคุณเอง ดังนั้นที่นี่ฉันต้องการสร้างแผ่นทิศทางด้วยการปรับแต่งการควบคุมบนหน้าจอ

แผนที่การกระทํา

เราจะใช้แผนที่การกระทํา แต่เราจะใช้แผนที่ที่สร้างขึ้นในเคล็ดลับก่อนหน้านี้ตามที่เป็นอยู่ดังนั้นขั้นตอนจะถูกละเว้น

คุณได้เขียนโค้ดด้วย

วัตถุกําหนดตําแหน่ง

วางพื้นที่ข้อความเพื่อแสดงอินพุตของคุณและปุ่มที่แทนที่ D-pad ในกรณีนี้ปุ่มจะถูกจัดเรียง แต่คุณสามารถแทนที่ด้วยภาพที่เข้าใจง่ายกว่า

สคริปต์เพื่อแสดงอินพุต

เนื่องจากการควบคุมบนหน้าจอแทนที่การสัมผัสด้วยการโต้ตอบของตัวควบคุมทางกายภาพ สร้างสคริปต์ที่แสดงข้อมูลที่คุณป้อนเป็นข้อความในขณะที่แผนที่การดําเนินการทํางาน

เนื้อหาเหมือนเดิมดังนั้นฉันจะละเว้นคําอธิบาย

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

หลังจากตั้งค่าแล้วให้ตรวจสอบก่อนว่าใช้งานได้กับแป้นพิมพ์หรือเกมแพดของคุณหรือไม่

การปรับแต่งการควบคุมบนหน้าจอ

สิ่งนี้นําเราไปสู่หัวข้อหลักของเคล็ดลับเหล่านี้ การปรับแต่งตัวควบคุมบนหน้าจอเป็นสคริปต์ ดังนั้นก่อนอื่นให้สร้างสคริปต์ ชื่อเป็นโดยพลการ แต่ในกรณีนี้ OnScreenDpad คือ .

สคริปต์มีลักษณะดังนี้:

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

OnScreenControlคุณสามารถสร้างคลาสการปรับแต่งตัวควบคุมบนหน้าจอ โดยการสืบทอดจากคลาสกําหนดเอง นอกจากนี้ในการรับเหตุการณ์การสัมผัสต่างๆให้สืบทอดอินเทอร์เฟซเป้าหมาย ที่นี่ "เมื่อสัมผัส", "เมื่อเคลื่อนไหวขณะสัมผัส", "เมื่อปล่อยการสัมผัส" และ "ก่อนลาก" จะถูกประมวลผลดังนั้นอินเทอร์เฟซต่อไปนี้จึงอธิบายตามลําดับ

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
เนื้อหาอินเทอร์เฟซ
IPointerDownHandler เมื่อสัมผัส
IPointerUpHandler เมื่อคุณปล่อยการสัมผัส
IDragHandler เมื่อคุณเคลื่อนไหวขณะสัมผัส
IInitializePotentialDragHandler ก่อนที่คุณจะเริ่มลาก

มีการประกาศฟิลด์ต่อไปนี้:

controlPathInternalOnScreenControlจําเป็นหากคุณสืบทอดมาจากชั้นเรียน มันถือสตริงของปุ่มของอุปกรณ์อินพุตที่จะแมป แต่โดยทั่วไปคุณสามารถเขียนได้เหมือนเดิม InputControlอย่างไรก็ตาม แอตทริบิวต์ควรมีค่าที่จะเก็บไว้ (Vector2 ในกรณีนี้)

_objectPosition_objectSizeHalfและถือตําแหน่งและขนาดของปุ่มครึ่งหนึ่งล่วงหน้าและใช้ในภายหลังสําหรับการคํานวณ

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

วิธีการที่เรียกว่า Start หลังจากเริ่มต้นวัตถุจะได้รับตําแหน่งและขนาดของปุ่มครึ่งหนึ่งบนผืนผ้าใบ ขนาดยังคํานึงถึงขนาดด้วย Start มันถูกประมวลผลโดยวิธีการ แต่ถ้าสามารถใช้อย่างถูกต้องในการคํานวณในที่สุดระยะเวลาการได้มาสามารถอยู่ที่ใดก็ได้

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

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

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

OnInitializePotentialDrag จะถูกเรียกเมื่อคุณต้องการเริ่มลากหลังจากสัมผัส ในขณะที่ลาก OnDrag วิธีการที่เรียกว่า มีการกําหนดเกณฑ์เพื่อป้องกันการตัดสินการลากในระดับหนึ่งเพื่อให้นิ้วไม่ขยับเล็กน้อยและการตัดสินการลากจะไม่เกิดขึ้นเนื่องจากการทํางานของการสัมผัสเพียงอย่างเดียว

คราวนี้เนื่องจากเป็นอินพุตที่ถือว่าคุณต้องการดําเนินการปรับแต่งให้ปิดใช้งานการตั้งค่าเกณฑ์นี้และทําการตัดสินลากทันที eventData.useDragThreshold false คุณสามารถแทนที่เกณฑ์ได้โดยตั้งค่าเป็น

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

ด้านล่างนี้คือเหตุการณ์ที่เรียกว่าเมื่อสัมผัสลากและปล่อยตามลําดับ OnPointerDown OnDragตั้งแต่และ แต่ละคนดําเนินการOperateประมวลผลอินพุตเดียวกันดังนั้นเราจึงสร้างวิธีการแยกต่างหากและเรียกมันว่า OnPointerUpตอนนี้ส่งต่อไปยังวิธีการแจ้งSendValueToControlVector2.zeroตัวควบคุมว่าหยุดพิมพ์แล้ว

/// <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 วิธีการคือการดําเนินการอินพุต D-pad หลัก

ก่อนอื่นสามารถรับตําแหน่งสัมผัสได้ แต่เนื่องจากพิกัดนี้เป็นพิกัดของeventData.positionหน้าจอเกม RectTransformUtility.ScreenPointToLocalPointInRectangleวิธีการแปลงเป็นพิกัดผ้าใบ ค่าที่จะส่งผ่านคือ "canvas", "touch position", "camera" และ "receiving variable"RectTransform

หลังจากได้รับ "ตําแหน่งสัมผัสบนผืนผ้าใบ" ให้แปลงเป็นตําแหน่งของปุ่มเป็นจุดเริ่มต้น เพียงแค่ลบตําแหน่ง () ของ_objectPositionวัตถุ

ถัดไปเนื่องจากตําแหน่งสัมผัสยังคงเป็นตัวเลขบนผืนผ้าใบให้แปลงค่านี้เป็นอัตราส่วน 0 ~ 1 หากคุณหารด้วยขนาดครึ่งหนึ่งของปุ่มตําแหน่งสัมผัสภายในช่วงของปุ่มจะเป็น 0 ~ 1 หากคุณลากด้วยการสัมผัสค่าจะเป็น 1 หรือมากกว่าเนื่องจากยอมรับการทํางานแม้อยู่นอกปุ่ม Vector2.ClampMagnitude ทําสิ่งนี้ในวิธีการเพื่อไม่ให้เกิน 1

สุดท้ายส่งผ่านค่า SendValueToControl อินพุตที่แปลงเป็น 0 ~ 1 ไปยังวิธีการ การควบคุมบนหน้าจอจะจัดการส่วนที่เหลือให้คุณ

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

แนบสคริปต์ที่คุณสร้างไว้กับปุ่ม การกระทํานี้ถูกตั้งค่าให้ถือว่าเป็นการทํางานของแท่งด้านซ้ายของ Gamepad

ลองย้ายเกมและแตะปุ่ม ฉันคิดว่าคุณจะเห็นว่าค่าที่ได้รับการเปลี่ยนแปลงขึ้นอยู่กับตําแหน่งสัมผัส นอกจากนี้คุณจะเห็นว่าแม้ว่าคุณจะลากขณะสัมผัสค่าจะเปลี่ยนไปตามตําแหน่งลาก