1、遇见的一个问题:物体(或UI)左右倒向面向摄像机,以及解决方案

        首先我们需要明白 LookAt 默认让物体的 Z 轴(前向轴)指向目标

        所以如上图所示,问题最常会出现的原因就是z轴(前向轴,在图中为蓝色)与自己所设想的方向相反,解决的方法就是将物体朝向与摄像机朝向一致。

 void Update()
 {
     transform.LookAt(MainCamera.transform.position);
     transform.forward=MainCamera.transform.forward;
 }

2、了解Cursor

        Cursor 在 Unity 中是一个静态类,它的作用就是控制鼠标指针(光标)的显示、锁定状态和外观

成员 类型 作用
Cursor.visible bool 控制鼠标指针是否可见
Cursor.lockState CursorLockMode 控制鼠标指针的锁定状态
Cursor.SetCursor() 静态方法 设置自定义鼠标指针外观
功能 代码 说明
隐藏鼠标 Cursor.visible = false; 隐藏指针
锁定鼠标 Cursor.lockState = CursorLockMode.Locked; 锁定到屏幕中心,用于 FPS
限制鼠标 Cursor.lockState = CursorLockMode.Confined; 限制在窗口内,用于 RTS
自定义外观 Cursor.SetCursor(tex, hotspot, mode); 替换鼠标图片

3、新输入系统实现摄像机移动和旋转

第一部分:逐行详细解释 CameraController.cs 脚本

这个脚本的核心是:用新输入系统读取操作,然后控制摄像机移动和旋转。

1. 头部声明与变量定义
using UnityEngine;
using UnityEngine.InputSystem;   // 引入新输入系统的命名空间
  • UnityEngine.InputSystem 包含了所有新输入系统的类,比如 InputAction、InputActionMap、PlayerInput 等。没有这行,就无法使用 CameraControls 这个自动生成的类。
public class CameraController : MonoBehaviour
{
    [Header("移动设置")]
    public float moveSpeed = 10f;
    
    [Header("旋转设置")]
    public float lookSpeed = 2f;
    public float minRotation = -90f;
    public float maxRotation = 90f;
  • [Header("移动设置")]:这是一个属性标签,它不会影响代码逻辑,只会在 Unity 的 Inspector 面板中生成一个分组标题,让参数更清晰。
  • public float moveSpeed = 10f:移动速度,单位是“单位/秒”。在3D场景中,1个单位通常等于1米。
  • public float lookSpeed = 2f:鼠标灵敏度。值越大,鼠标移动一点点,视角就转得越快。
  • minRotation 和 maxRotation:俯仰角限制。-90° 表示可以完全看向正下方,90° 表示可以完全看向正上方。通常限制在 -85° ~ 85° 可以避免万向节死锁。
    private CameraControl controls;
    private Vector2 moveInput;
    private Vector2 lookInput;
    private float yRotate = 0f;
    private float xRotate = 0f;
  • private CameraControl controls:这是之前通过 Input Actions 资源自动生成的 C# 类的实例。它包含了在可视化编辑器中定义的所有 Action Map 和 Action。
  • Vector2 moveInput 和 lookInput:用于存储当前帧的输入值。Vector2 包含 x 和 y 两个分量。对于移动,x 是左右(A/D),y 是前后(W/S)。对于视角,x 是鼠标水平移动,y 是鼠标垂直移动。
  • yRotate 和 xRotate:分别存储水平旋转角度(绕 Y 轴)和垂直旋转角度(绕 X 轴)。它们是累加变量,每一帧都会根据鼠标输入增加或减少。
2. 生命周期函数
    void Awake()
    {
        controls = new CameraControl();
    }
  • Awake():在脚本实例被创建时调用,比 Start() 更早。在这里实例化 controls,确保它在任何输入事件注册之前就已经存在。
    void OnEnable()
    {
        controls.Camera.Enable();
        
        controls.Camera.Move.performed += ctx => moveInput = ctx.ReadValue<Vector2>();
        controls.Camera.Move.canceled += ctx => moveInput = Vector2.zero;
        controls.Camera.Look.performed += ctx => lookInput = ctx.ReadValue<Vector2>();
        controls.Camera.Look.canceled += ctx => lookInput = Vector2.zero;
        
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }
  • OnEnable():当脚本或游戏对象被激活时调用。这里是启用输入和注册事件的最佳时机。
  • controls.Camera.Enable():启用名为 Camera 的 Action Map。只有启用了,它下面的所有 Action(Move、Look)才会开始监听输入。
  • Lambda 表达式事件注册:

        performed:当输入值发生变化时触发。例如,你按下 W 键,moveInput 的 y 分量会变成 1。

        canceled:当输入被取消时触发。例如,你松开 W 键,moveInput 被重置为 (0, 0)。

        ctx.ReadValue<Vector2>():从输入上下文中读取当前的值,并转换为 Vector2 类型。

  • Cursor.lockState = CursorLockMode.Locked:将鼠标指针锁定到屏幕中心并隐藏。这是 FPS 游戏的标配,防止鼠标移出窗口。
    void OnDisable()
    {
        controls.Camera.Disable();
        
        Cursor.lockState = CursorLockMode.None;
        Cursor.visible = true;
    }
  • OnDisable():当脚本或游戏对象被禁用时调用。必须在这里禁用输入并解锁鼠标,否则切换到其他场景时,鼠标可能还是锁定的,导致无法操作 UI。
    void Update()
    {
        if (Keyboard.current.escapeKey.wasPressedThisFrame)
        {
            Cursor.lockState = CursorLockMode.None;
            Cursor.visible = true;
        }//按下Esc键解锁鼠标
        
        if (Mouse.current.leftButton.wasPressedThisFrame && Cursor.lockState == CursorLockMode.None)
        {
            Cursor.lockState = CursorLockMode.Locked;
            Cursor.visible = false;
        }
        
        Move();
        Look();
    }
  • Keyboard.current.escapeKey.wasPressedThisFrame:新输入系统的写法,检测这一帧是否按下了 Esc 键。注意,这里没有使用事件,而是直接轮询,因为解锁鼠标是一个需要实时响应的操作。
  • Mouse.current.leftButton.wasPressedThisFrame:检测这一帧是否点击了鼠标左键。配合解锁状态,实现“点击重新锁定”的功能。
3. 核心逻辑函数
    private void Move()
    {
        Vector3 forward = transform.forward;
        Vector3 right = transform.right;
        
        //水平面移动
        //forward.y = 0f;
        //right.y = 0f;
        //forward.Normalize();
        //right.Normalize();
        
        Vector3 moveDirection = (forward * moveInput.y + right * moveInput.x).normalized;
        
        transform.position += moveDirection * moveSpeed * Time.deltaTime;
    }
  • transform.forward 和 transform.right:获取摄像机当前的前方向和右方向。这是 3D 移动的关键:让移动方向始终相对于摄像机的朝向。
  • (forward * moveInput.y + right * moveInput.x):合成移动向量。moveInput.y 控制前后,moveInput.x 控制左右。
  • .normalized:归一化,防止对角线移动速度更快(对角线移动时,两个轴都有输入,向量长度约为 1.414,归一化后变成 1)。
  • transform.position += moveDirection * moveSpeed * Time.deltaTime:应用位移。Time.deltaTime 是上一帧的耗时,乘以它可以让移动速度与帧率无关。
    private void Look()
    {
        yRotate += lookInput.x * lookSpeed;
        xRotate -= lookInput.y * lookSpeed;
        
        xRotate = Mathf.Clamp(xRotate, minRotation, maxRotation);
        
        transform.rotation = Quaternion.Euler(xRotate, yRotate, 0f);
    }
  • yRotate += lookInput.x * lookSpeed:水平旋转。lookInput.x 是鼠标水平移动的增量,乘以灵敏度后累加到 yaw 上。
  • xRotate -= lookInput.y * lookSpeed:垂直旋转。注意是减号,因为鼠标向上移动时,lookInput.y 是正值,但应当是摄像机向上看(xRotate 减小),所以用减号。
  • Mathf.Clap(xRotate, minRotation, maxRotation):将 xRotate 限制在 -90° 到 90° 之间,防止摄像机翻转。
  • Quaternion.Euler(xRotate, yRotate, 0f):用欧拉角创建一个四元数。xRotate 绕 X 轴旋转,yRotate 绕 Y 轴旋转,0 表示不绕 Z 轴旋转(没有滚转)。
  • transform.rotation = ...:直接赋值,覆盖之前的旋转。这是绝对旋转,而不是增量旋转,可以避免浮点误差累积。
第二部分:新输入系统的核心概念
概念 说明 类比
Input Action Asset 一个资源文件(.inputactions),可视化地定义所有输入 相当于一个遥控器的布局图
Action Map 一组相关的 Action 的集合,可以整体启用/禁用 遥控器上的一个模式(如“游戏模式”、“菜单模式”)
Action 一个具体的输入行为,如“移动”、“跳跃” 遥控器上的一个按钮
Binding 将 Action 与具体的物理按键/手柄轴关联起来 按钮上的标签(如“W键”、“左摇杆”)
Processor 对原始输入值进行预处理,如反转、缩放、归一化 信号放大器或滤波器
Interactions 定义输入如何触发,如“按下”、“长按”、“双击” 按钮的触发方式
新输入系统 vs 旧输入系统:
特性 旧系统 (Input Manager) 新系统 (Input System)
配置方式 硬编码字符串(如 "Horizontal" 可视化编辑 + 自动生成 C# 类
跨平台 需要手动适配 自动适配键盘、鼠标、手柄、触屏、VR
事件驱动 需要每帧轮询 支持事件回调(performedcanceled
代码耦合 输入逻辑与游戏逻辑混在一起 输入逻辑与游戏逻辑解耦
性能 每帧轮询所有轴 事件驱动,性能更好
总结

        这个脚本的核心逻辑可以概括为:

  • 输入读取:通过新输入系统的事件回调,将玩家的操作(WASD、鼠标移动)实时存储到 moveInput 和 lookInput 变量中。
  • 移动处理:每帧根据 moveInput 和摄像机当前朝向,计算移动方向,然后应用位移。
  • 视角处理:每帧根据 lookInput 累加 yRotate 和 xRotate 角度,然后直接设置摄像机的旋转。
  • 鼠标管理:在 OnEnable 中锁定鼠标,在 OnDisable 中解锁,并在运行时通过 Esc 键和鼠标点击切换锁定状态。
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐