Unity 笔记二(有关数字孪生-智慧城市)
·
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 |
| 事件驱动 | 需要每帧轮询 | 支持事件回调(performed、canceled) |
| 代码耦合 | 输入逻辑与游戏逻辑混在一起 | 输入逻辑与游戏逻辑解耦 |
| 性能 | 每帧轮询所有轴 | 事件驱动,性能更好 |
总结
这个脚本的核心逻辑可以概括为:
- 输入读取:通过新输入系统的事件回调,将玩家的操作(WASD、鼠标移动)实时存储到 moveInput 和 lookInput 变量中。
- 移动处理:每帧根据 moveInput 和摄像机当前朝向,计算移动方向,然后应用位移。
- 视角处理:每帧根据 lookInput 累加 yRotate 和 xRotate 角度,然后直接设置摄像机的旋转。
- 鼠标管理:在 OnEnable 中锁定鼠标,在 OnDisable 中解锁,并在运行时通过 Esc 键和鼠标点击切换锁定状态。
更多推荐

所有评论(0)