自定义屏幕控件以实现方向键

更新页 :
页面创建日期 :

验证环境

窗户
  • 视窗 11
统一编辑器
  • 2020.3.25f1
输入系统包
  • 1.2.0

此提示的先决条件

作为此提示描述的前提,已预先进行了以下设置。

您还应该熟悉以下提示:

屏幕控制杆是标准的方向操作

屏幕摇杆作为虚拟摇杆实现,复制类似于物理控制器上的摇杆的操作。 例如,如果要将其向右移动,可以先触摸屏幕触摸摇杆,然后将其向右滑动以将摇杆击倒。

很容易想象成一个摇杆操作,但相反,当你想立即向右移动它时,(1)触摸,(2)向右滑动, 由于需要两步操作,因此响应不可避免地会有点延迟。 当您需要连续快速改变方向时尤其如此。

在触摸时确定方向的方向键(非标准)

快速方向输入的最佳输入方法是可以通过放置方向键或箭头键之类的东西来确定触摸时的方向。 它不是由Unity制作的,但是在我的游戏Little Saber中,我放置了箭头键,以便您可以快速向触摸的方向移动。 当然,您也可以在触摸时向上或向左滑动以切换动作。

但是,屏幕控制标准不容易放置和实施,因为只有屏幕摇杆和屏幕按钮。

您也可以如下图所示,通过排列四个按钮来创建伪方向键,但这很不方便,因为您无法对角线输入。 如果放置8个按钮,则可以进行对角线操作,但是仍然无法进行诸如“ ←↙↓”之类的流向操作。

使用屏幕控制自定义创建方向键

如您所见,屏幕控制仅标配摇杆和按钮,但您可以使用自己的脚本自定义缺少的功能。 所以在这里我想创建一个带有屏幕控制自定义的方向键。

行动地图

我们将使用操作映射,但我们将按原样使用在前面的提示中创建的操作映射,因此省略了该过程。

您还编写了一些代码。

定位对象

放置一个文本区域以显示您的输入和一个替换方向键的按钮。 在这种情况下,按钮是排列的,但您可以将它们替换为更易于理解的图像。

用于显示输入的脚本

由于屏幕控制取代了物理控制器交互的触摸, 创建一个脚本,在操作映射工作时以文本形式显示输入。

内容和以前一样,所以我省略了解释。

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 该方法是核心方向键输入操作。

首先,触摸位置可以用 获得,但由于这个坐标是游戏画面的 eventData.position 坐标, RectTransformUtility.ScreenPointToLocalPointInRectangle 转换为画布坐标的方法。 要传递的值是“画布”、“触摸位置”、“ RectTransform相机”和“接收变量”。

获取“画布上的触摸位置”后,将其转换为按钮的位置作为原点。 只需减去对象的位置_objectPosition ()。

接下来,由于触摸位置仍然是画布上的数字,因此将此值转换为 0~1 的比率。 如果除以按钮大小的一半,则按钮范围内的触摸位置将为0~1。 如果使用触摸拖动,则该值将为 1 或更大,因为它即使在按钮外部也能接受操作。 Vector2.ClampMagnitude 在方法中执行此操作,使其不超过 1。

最后,将转换为 0~1 的输入值 SendValueToControl 传递给方法。 屏幕控件为您完成其余工作。

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

将创建的脚本附加到按钮。 此操作设置为被视为游戏手柄左摇杆的操作。

尝试移动游戏并触摸按钮。 我想您可以看到获得的值会根据触摸位置而变化。 此外,您可以看到,即使您在触摸时拖动,该值也会根据拖动位置而变化。