U
章 03镜头与操作6 课 — 点击打开
06 / 06 · 20 分钟
镜头与操作★ 练习

练习:一根拇指就能玩

到目前为止的项目

Assets/

Scenes/

Arena.unity

Ultimate vcam

Scripts/

CameraDirector.cs

Settings/

UI action map

everything from tab 01–03

你已经有的这一课新增

练习2–3 小时

给 Arena 做一套操控装置:只靠一个 action map 就同时支持键盘、手柄和触屏 —— 再配一个第三人称相机,不穿墙、不发抖、命中时震一下屏幕,放大招时切到好看的机位。

复用的东西

Input Actions assetInput Actions 资源Generated C# class自动生成的 C# 类Cinemachine虚拟相机Impulse source / listener相机震动Camera-relative movement相机相对移动On-screen stick屏幕摇杆Screen.safeArea安全区
动态图解

三种设备,一个 action map,一套相机装置。

第 1 部分 —— 一个 action map 管所有输入

每个 action 都要同时绑定三种设备。如果你发现自己正在写 if (touch) … else if (keyboard) …,就停下来 —— action map 存在的意义本来就是避免这种分支。

PlayerControls.inputactions — the map you must build
Map: Gameplay

  Move     Value / Vector2
     ├─ 2D Vector Composite  W A S D
     ├─ Gamepad              <Gamepad>/leftStick
     └─ On-Screen Stick      (bound from the UI prefab)

  Look     Value / Vector2
     ├─ Pointer delta        <Mouse>/delta
     ├─ Gamepad              <Gamepad>/rightStick
     └─ Touch drag           (right half of the screen)

  Jump     Button
     ├─ <Keyboard>/space     ├─ <Gamepad>/buttonSouth   └─ On-Screen Button

  Skill1 / Skill2 / Skill3   Button
     ├─ Q E R                ├─ West / North / buttonEast   └─ On-Screen Buttons

  Pause    Button
     ├─ <Keyboard>/escape    └─ <Gamepad>/start

Map: UI          ← enabled only while paused
  Navigate · Submit · Cancel
InputRouter.cs — the single place that reads input
using UnityEngine;
using UnityEngine.InputSystem;

public class InputRouter : MonoBehaviour
{
    private PlayerControls controls;

    public Vector2 Move { get; private set; }
    public Vector2 Look { get; private set; }
    public bool JumpQueued { get; private set; }

    [SerializeField] private SkillCaster caster;

    void Awake()
    {
        controls = new PlayerControls();
        var g = controls.Gameplay;

        g.Move.performed += c => Move = c.ReadValue<Vector2>();
        g.Move.canceled  += _ => Move = Vector2.zero;
        g.Look.performed += c => Look = c.ReadValue<Vector2>();
        g.Look.canceled  += _ => Look = Vector2.zero;

        g.Jump.performed   += _ => JumpQueued = true;
        g.Skill1.performed += _ => caster.TryCast(0);
        g.Skill2.performed += _ => caster.TryCast(1);
        g.Skill3.performed += _ => caster.TryCast(2);
        g.Pause.performed  += _ => TogglePause();
    }

    void OnEnable()  => controls.Enable();
    void OnDisable() => controls.Disable();
    void LateUpdate() => JumpQueued = false;

    void TogglePause()
    {
        bool paused = Time.timeScale > 0f;
        Time.timeScale = paused ? 0f : 1f;

        // swap which map is live
        if (paused) { controls.Gameplay.Disable(); controls.UI.Enable(); }
        else        { controls.UI.Disable(); controls.Gameplay.Enable(); }

        Cursor.lockState = paused ? CursorLockMode.None : CursorLockMode.Locked;
        Cursor.visible = paused;
    }
}

第 2 部分 —— 相机装置

  • 一个 Cinemachine 相机,用 Third Person Follow:Follow = Knight,LookAt 指向胸口高度的 CameraTarget。

  • Camera Collision Filter 选你的 Environment 层,遇到墙时相机会自己收进来而不是穿过去。

  • 三个轴的 Damping 都是 0.3 —— 够沉稳,又不至于晃得人头晕。

  • 给大招准备第二个相机,平时 Priority 0,按 R 放招时提到 20,持续 1.5 秒。

  • 两个相机都挂 Impulse Listener,玩家身上挂 Impulse Source,每次命中就触发一次。

CameraDirector.cs
using System.Collections;
using UnityEngine;
using Unity.Cinemachine;

public class CameraDirector : MonoBehaviour
{
    [SerializeField] private CinemachineCamera gameplayCam;
    [SerializeField] private CinemachineCamera ultimateCam;
    [SerializeField] private CinemachineImpulseSource impulse;

    /// call from SkillCaster when a hit lands
    public void Shake(float strength = 0.35f) => impulse.GenerateImpulseWithForce(strength);

    /// call when the ultimate starts
    public void PlayUltimateShot(float seconds = 1.5f) => StartCoroutine(Cut(seconds));

    IEnumerator Cut(float seconds)
    {
        ultimateCam.Priority = 20;
        yield return new WaitForSeconds(seconds);
        ultimateCam.Priority = 0;
    }
}

第 3 部分 —— 触屏层

  • Canvas 设为 Scale With Screen Size,参考分辨率 1080 × 1920,Match 0.5。

  • 左下角:On-Screen Stick 绑到 Move。右下角:三个技能键加一个跳跃键。

  • 根面板上加 SafeAreaFitter,每个控件都锚到某个角,绝对不要锚在中间。

  • 在桌面端整个触屏 canvas 要关掉 —— 在 Awake 里判断 Application.isMobilePlatform。

C# 脚本
void Awake()
{
    bool mobile = Application.isMobilePlatform;

#if UNITY_EDITOR
    mobile = forceTouchInEditor;    // a bool you tick to test on desktop
#endif

    touchCanvas.SetActive(mobile);
}

怎样算做完

  • ✓同一个 build 不加任何代码分支,就能同时响应键盘、插入的手柄和屏幕上的触控。
  • ✓在任何机位下按 W,角色都是朝远离相机的方向走。
  • ✓角色走到墙里时,相机平滑地收进来,而不是让你看见墙的内侧。
  • ✓移动时角色不会再发抖 —— 因为相机逻辑写在 LateUpdate 里。
  • ✓每次命中相机只轻微晃一下,0.25 秒内就停下。
  • ✓放大招时切到第二个相机,结束后自动混合回来。
  • ✓按 Esc 会暂停、释放鼠标,游戏输入也停了 —— 角色不会还在那儿往前走。
  • ✓整个项目里再也找不到 Input.GetAxis 或 Input.GetKey。

需要达到的数字

指标目标值原因
相机 Damping0.2–0.4 s低于 0.2 会太生硬,高于 0.5 玩家会跟不上角色。
震动时长≤ 0.25 s再长就像 bug,在手机上还会让人头晕。
俯仰角限制-25° … 65°超出这个范围相机会翻到头顶上去。
触控区域尺寸≥ 44 pt再小的话,真机上拇指会一直点空。

想再多做点

加分项 1

加一个灵敏度滑条和一个反转 Y 轴开关,并在多次游玩之间保留设置。

加分项 2

识别当前使用的设备,在 Q/E/R 和手柄图标之间切换屏幕上的按键提示。

加分项 3

加一个锁定相机的功能:点鼠标中键把最近的敌人套进取景框(Tab 已经用来切换角色了)。

加分项 4

用 Cinemachine 的扩展,在玩家贴近墙时把相机拉近。