U
章 03镜头与操作6 课 — 点击打开
05 / 06 · 10 分钟
镜头与操作

触屏操作与摇杆

到目前为止的项目

Assets/

Scenes/

Arena.unity

TouchCanvas

Scripts/

InputRouter.csVirtualJoystick.csSafeAreaFitter.cs

Settings/

PlayerControls.inputactions

你已经有的这一课新增

因为上一课已经把所有东西都接到 InputRouter 上了,这一课你一个 gameplay 脚本都不用改。Knight 现在就接受任何能填 Move 这个 action 的输入。

触屏控制不是另一套游戏,它只是一层 UI,产出的就是你键盘已经在产出的那个 Vector2。

动态图解

拇指拖动摇杆帽,摇杆帽变成一个方向,这个方向喂给同一个 Move action。

最快的路子:On-Screen Controls

  1. 1

    创建一个 UI Canvas:GameObject▸UI▸Canvas,命名为 TouchCanvas,并把 Render Mode 设为 Screen Space Overlay。

  2. 2

    Canvas Scaler → Scale With Screen Size,参考分辨率 1080×1920,Match 0.5。少了这步,有些手机上的 UI 会小得看不见。

  3. 3

    加一个 Image 当作摇杆底座,再在它下面加一个子 Image 当作摇杆帽。

  4. 4

    在摇杆帽上挂 On-Screen Stick 组件,把 Control Path 指向 Move 的绑定。

  5. 5

    用同样的方式给你的动作按钮加上 On-Screen Button。这样 Input System 看到的就是一个虚拟手柄。

自己写一个虚拟摇杆

自己写的摇杆能给你跟随手指的原点、死区和自定义手感。大概 40 行。

VirtualJoystick.cs — put on the stick base Image
using UnityEngine;
using UnityEngine.EventSystems;

public class VirtualJoystick : MonoBehaviour,
    IPointerDownHandler, IDragHandler, IPointerUpHandler
{
    [SerializeField] private RectTransform knob;
    [SerializeField] private float radius = 90f;
    [SerializeField] private float deadZone = 0.15f;

    private RectTransform baseRect;
    public Vector2 Value { get; private set; }

    void Awake() => baseRect = GetComponent<RectTransform>();

    public void OnPointerDown(PointerEventData e) => OnDrag(e);

    public void OnDrag(PointerEventData e)
    {
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            baseRect, e.position, e.pressEventCamera, out Vector2 local);

        Vector2 clamped = Vector2.ClampMagnitude(local, radius);
        knob.anchoredPosition = clamped;

        Vector2 raw = clamped / radius;                 // -1 .. 1
        Value = raw.magnitude < deadZone ? Vector2.zero : raw;
    }

    public void OnPointerUp(PointerEventData e)
    {
        knob.anchoredPosition = Vector2.zero;
        Value = Vector2.zero;                           // always reset
    }
}
  • ScreenPointToLocalPointInRectangle 会把手指位置换算成摇杆内部的坐标,不受屏幕分辨率影响。

  • ClampMagnitude 让摇杆帽待在圆里而不是方框里 —— 方形摇杆一上手就知道不对劲。

  • 把死区设成 0.15,拇指搭在屏幕上时角色就不会自己飘走。

点击、拖动,右半屏交给相机

C# 脚本
void Update()
{
    // right half of the screen rotates the camera
    foreach (Touch t in Input.touches)
    {
        if (t.position.x < Screen.width * 0.5f) continue;   // left half is the stick
        if (t.phase != TouchPhase.Moved) continue;

        yaw   += t.deltaPosition.x * touchSensitivity;
        pitch -= t.deltaPosition.y * touchSensitivity;
    }
}

移动 UI 里不能商量的几条规则

  • 点击区域至少 iOS 44 pt 或 Android 48 dp —— 大约 7 到 9 mm。再小玩家就会不停按空。

  • 底部两个角落别放小按钮 —— 那里既是拇指停放的地方,也是系统手势经过的地方。

  • UI 要锚定到角落而不是中间,并且遵守 Screen.safeArea 来避让刘海。

  • 出包之前先在 Simulator 视图里(Game 视图的下拉菜单 → Simulator)用真机配置测一遍。

SafeAreaFitter.cs — put on a full-screen panel
using UnityEngine;

[RequireComponent(typeof(RectTransform))]
public class SafeAreaFitter : MonoBehaviour
{
    void Start()
    {
        RectTransform rt = GetComponent<RectTransform>();
        Rect safe = Screen.safeArea;

        Vector2 min = safe.position;
        Vector2 max = safe.position + safe.size;
        min.x /= Screen.width;  min.y /= Screen.height;
        max.x /= Screen.width;  max.y /= Screen.height;

        rt.anchorMin = min;
        rt.anchorMax = max;
        rt.offsetMin = rt.offsetMax = Vector2.zero;
    }
}