Tùy chỉnh Điều khiển trên màn hình để triển khai D-pad

Trang Cập Nhật :
Ngày tạo trang :

Môi trường xác minh

Windows
  • cửa sổ 11
Biên tập viên Unity
  • 2020.3.25f1
Gói hệ thống đầu vào
  • 1.2.0

Điều kiện tiên quyết cho mẹo này

Các cài đặt sau đây đã được thực hiện trước làm tiền đề cho mô tả về mẹo này.

Bạn cũng nên làm quen với các mẹo sau:

Thanh điều khiển trên màn hình là hoạt động định hướng tiêu chuẩn

On-Screen Stick được triển khai như một thanh ảo, sao chép hoạt động của một thanh tương tự như được tìm thấy trên bộ điều khiển vật lý. Ví dụ: nếu bạn muốn di chuyển nó sang phải, trước tiên bạn có thể chạm vào màn hình để chạm vào cây gậy, sau đó trượt nó sang phải để đánh bật cây gậy xuống.

Thật dễ dàng để tưởng tượng như một thao tác thanh, nhưng ngược lại, khi bạn muốn di chuyển nó sang phải ngay lập tức, (1) chạm, (2) trượt sang phải, Vì nó đòi hỏi một hoạt động hai bước, phản hồi chắc chắn sẽ bị chậm trễ một chút. Điều này đặc biệt đúng khi bạn cần thay đổi hướng nhanh chóng liên tiếp.

D-pad xác định hướng tại thời điểm chạm (không chuẩn)

Phương pháp nhập liệu tốt nhất để nhập hướng nhanh là phương pháp có thể xác định hướng tại thời điểm chạm bằng cách đặt một cái gì đó như D-pad hoặc phím mũi tên. Nó không được tạo ra bởi Unity, nhưng trong trò chơi Little Saber của tôi, tôi đặt các phím mũi tên để bạn có thể nhanh chóng di chuyển theo hướng bạn chạm vào. Tất nhiên, bạn cũng có thể trượt lên hoặc sang trái trong khi chạm để chuyển động.

Tuy nhiên, tiêu chuẩn On-Screen Control không dễ đặt và thực hiện vì chỉ có On-Screen Sticks và On-Screen Buttons.

Bạn cũng có thể tạo một pseudo-D-pad bằng cách sắp xếp bốn nút như trong hình bên dưới, nhưng nó bất tiện vì bạn không thể nhập theo đường chéo. Nếu bạn đặt 8 nút, có thể thao tác đường chéo, nhưng thao tác hướng dòng chảy như "← ↙ ↓" vẫn không thể thực hiện được.

Tạo bàn phím định hướng với tùy chỉnh Điều khiển trên màn hình

Như bạn có thể thấy, Điều khiển trên màn hình chỉ đạt tiêu chuẩn với Thanh và Nút, nhưng bạn có thể tùy chỉnh các tính năng còn thiếu bằng các tập lệnh của riêng mình. Vì vậy, ở đây tôi muốn tạo một bàn phím định hướng với tùy chỉnh Điều khiển trên màn hình.

Bản đồ hành động

Chúng tôi sẽ sử dụng bản đồ hành động, nhưng chúng tôi sẽ sử dụng bản đồ được tạo trong các mẹo trước đó, vì vậy quy trình bị bỏ qua.

Bạn cũng đã viết một số mã.

Định vị đối tượng

Đặt một vùng văn bản để hiển thị đầu vào của bạn và một nút thay thế D-pad. Trong trường hợp này, các nút được sắp xếp, nhưng bạn có thể thay thế chúng bằng một hình ảnh dễ hiểu hơn.

Tập lệnh để hiển thị đầu vào

Vì Điều khiển trên màn hình thay thế cảm ứng bằng tương tác bộ điều khiển vật lý, Tạo tập lệnh hiển thị đầu vào của bạn bằng văn bản khi bản đồ hành động hoạt động.

Nội dung vẫn như trước nên mình sẽ bỏ qua phần giải thích.

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

Sau khi thiết lập, trước tiên hãy kiểm tra xem nó có hoạt động với bàn phím hoặc gamepad của bạn không.

Tùy chỉnh các điều khiển trên màn hình

Điều này đưa chúng ta đến chủ đề chính của những lời khuyên này. Tùy chỉnh Điều khiển trên màn hình là một tập lệnh, vì vậy trước tiên hãy tạo tập lệnh. Tên là tùy ý, nhưng trong trường hợp OnScreenDpad này là .

Kịch bản trông như thế này:

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

Bạn tạo một OnScreenControl lớp tùy chỉnh điều khiển trên màn hình bằng cách kế thừa từ lớp Tùy chỉnh. Ngoài ra, để nhận các sự kiện cảm ứng khác nhau, kế thừa giao diện đích. Ở đây, "khi chạm", "khi di chuyển trong khi chạm", "khi cảm ứng được nhả ra" và "trước khi kéo" được xử lý, vì vậy các giao diện sau cũng được mô tả tương ứng.

public class OnScreenDpad
  : OnScreenControl, IPointerDownHandler, IPointerUpHandler, IDragHandler, IInitializePotentialDragHandler
Nội dung giao diện
IPointerDownHandler Khi chạm vào
IPointerUpHandler Khi bạn nhả cảm ứng
IDragHandler Khi bạn di chuyển trong khi chạm
IInitializePotentialDragHandler Trước khi bạn bắt đầu kéo

Các trường sau đây được khai báo:

controlPathInternalOnScreenControl là bắt buộc nếu bạn kế thừa từ một lớp. Nó giữ một chuỗi nút nào của thiết bị đầu vào để ánh xạ đến, nhưng về cơ bản bạn có thể viết nó như hiện tại. InputControl Tuy nhiên, thuộc tính phải chứa giá trị được giữ lại (Vector2 trong trường hợp này).

_objectPosition_objectSizeHalf và giữ trước một nửa vị trí và kích thước của nút và sử dụng nó sau để tính toán.

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

Phương thức, được gọi sau khi đối tượng được khởi tạo, nhận được Start một nửa vị trí và kích thước của nút trên canvas. Kích thước cũng tính đến quy mô. Start Nó được xử lý bằng một phương pháp, nhưng nếu cuối cùng nó có thể được sử dụng chính xác trong tính toán, thời gian mua lại có thể ở bất cứ đâu.

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

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

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

OnInitializePotentialDrag được gọi khi bạn muốn bắt đầu kéo sau khi chạm. Trong khi kéo, OnDrag phương thức được gọi. Một ngưỡng được đặt để ngăn chặn phán đoán kéo ở một mức độ nào đó để ngón tay không di chuyển một chút và phán đoán kéo không xảy ra do thao tác chạm đơn giản.

Lần này, vì nó là đầu vào giả định rằng bạn muốn thực hiện thao tác tinh chỉnh, hãy tắt cài đặt ngưỡng này và ngay lập tức đưa ra phán đoán kéo. eventData.useDragThreshold false Bạn có thể ghi đè ngưỡng bằng cách đặt thành .

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

Dưới đây là các sự kiện được gọi khi chạm, kéo và thả tương ứng. OnPointerDown OnDrag Vì và , mỗi thực hiện Operate xử lý đầu vào giống nhau, vì vậy chúng ta tạo một phương thức riêng biệt và gọi nó. OnPointerUp Bây giờ, chuyển nó đến phương thức để SendValueToControl Vector2.zero thông báo cho điều khiển rằng nó đã ngừng gõ.

/// <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 Phương pháp này là hoạt động đầu vào D-pad cốt lõi.

Trước hết, vị trí cảm ứng có thể thu được bằng , nhưng vì tọa độ này là tọa độ của eventData.position màn hình trò chơi, RectTransformUtility.ScreenPointToLocalPointInRectangle Phương pháp chuyển đổi sang tọa độ canvas. Các giá trị được truyền là "canvas", "touch position", "camera" và " RectTransformreceive variable".

Sau khi nhận được "vị trí chạm trên khung vẽ", hãy chuyển đổi nó thành vị trí của nút làm gốc. Chỉ cần trừ đi vị trí () của_objectPosition đối tượng.

Tiếp theo, vì vị trí chạm vẫn là một số trên canvas, hãy chuyển đổi giá trị này thành tỷ lệ 0 ~ 1. Nếu bạn chia cho một nửa kích thước của nút, vị trí chạm trong phạm vi của nút sẽ là 0 ~ 1. Nếu bạn kéo bằng cảm ứng, giá trị sẽ là 1 hoặc nhiều hơn vì nó chấp nhận các thao tác ngay cả bên ngoài nút. Vector2.ClampMagnitude Làm điều này trong phương pháp để nó không vượt quá 1.

Cuối cùng, chuyển giá trị SendValueToControl đầu vào được chuyển đổi thành 0 ~ 1 vào phương thức. Điều khiển trên màn hình thực hiện phần còn lại cho bạn.

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

Đính kèm tập lệnh bạn đã tạo vào nút. Hành động này được thiết lập để được coi là hoạt động của thanh bên trái của Gamepad.

Hãy thử di chuyển trò chơi và chạm vào các nút. Tôi nghĩ bạn có thể thấy rằng giá trị thu được thay đổi tùy thuộc vào vị trí chạm. Ngoài ra, bạn có thể thấy rằng ngay cả khi bạn kéo trong khi chạm, giá trị thay đổi tùy thuộc vào vị trí kéo.