Tùy chỉnh Điều khiển trên màn hình để triển khai D-pad
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:
- Sử dụng bản đồ hành động để gán các nút cho hành vi trò chơi
- Sử dụng các điều khiển đầu vào được tối ưu hóa cho cảm ứng với Điều khiển trên màn hình
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.
- Sử dụng bản đồ hành động để gán các nút cho hành vi trò chơi
- Sử dụng các điều khiển đầu vào được tối ưu hóa cho cảm ứng với Điều khiển trên màn hình
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:
controlPathInternal
OnScreenControl
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à " RectTransform
receive 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.