# Unity RPG 学习笔记 > 📅 创建于 2026-05-23 > 🎯 按时间顺序记录学习过程 --- ## 2026-05-23 · 第一次更新 **文件:** `Assets/Player.cs` ```csharp using UnityEngine; public class Player : MonoBehaviour { public float xInput; private void Update() // 旧的移动方式 { xInput = Input.GetAxisRaw("Horizontal"); } } ``` ### 知识点 #### 1. `MonoBehaviour` 继承 - Unity 中挂载到 GameObject 的脚本必须继承 `MonoBehaviour`。 - 继承后可使用生命周期方法(`Update`、`Start`、`Awake` 等)。 #### 2. `public` 字段暴露到 Inspector - `public float xInput;` 会在 Inspector 面板中可见、可编辑。 - 公开字段会被序列化,保存到场景/预制体数据中。 #### 3. `Update()` 生命周期 - 每帧调用一次,帧率越高调用越频繁。 - 适合处理**输入检测**、**持续移动**等实时逻辑。 #### 4. `Input.GetAxisRaw("Horizontal")` vs `GetAxis("Horizontal")` | 方法 | 返回值变化 | 手感 | |---|---|---| | `GetAxis` | 0→1 平滑过渡(加减速曲线) | 柔和,有惯性 | | `GetAxisRaw` | 瞬间跳变 -1/0/1 | 灵敏,即时响应 | - `"Horizontal"` 默认绑定:A/D、←/→、手柄左摇杆 X 轴。 #### 5. `Input` 类常用方法 - `GetAxis()` / `GetAxisRaw()` — 轴向输入 - `GetKey()` / `GetKeyDown()` / `GetKeyUp()` — 按键检测 - `GetMouseButton()` — 鼠标检测 --- ## 2026-05-23 · 第二次更新 **文件:** `Assets/Player.cs` ```csharp using UnityEngine; public class Player : MonoBehaviour { public Rigidbody2D rb; public float moveSpeed = 3.5f; public float xInput; private void Update() // 旧的移动方式 { xInput = Input.GetAxisRaw("Horizontal"); rb.linearVelocity = new Vector2 (xInput * moveSpeed, rb.linearVelocity.y); } } ``` ### 新增知识点 #### 1. `Rigidbody2D` 组件 - Unity 2D 物理引擎的核心组件,赋予物体物理属性(质量、重力、速度等)。 - 通过 `public Rigidbody2D rb;` 声明后,需在 Inspector 中拖入对应的 Rigidbody2D 组件进行绑定。 - 凡是需要物理模拟(重力、碰撞、速度)的 2D 物体都应挂载此组件。 #### 2. `linearVelocity` 属性(物理移动) - `Rigidbody2D.linearVelocity` 表示刚体的**线性速度**(Vector2 类型)。 - 直接赋值可以瞬间改变物体速度,实现物理驱动的移动。 - 这是旧版 `velocity` 属性的新命名(Unity 2023+ 推荐使用 `linearVelocity`)。 - 相比直接改 `Transform.position`,物理移动能正确参与碰撞检测。 #### 3. `Vector2` 构造函数 - `new Vector2(x, y)` 创建一个二维向量。 - `x` 控制水平移动:`xInput * moveSpeed`,按方向键时值为 ±3.5f,松开时为 0。 - `y` 保持当前垂直速度:`rb.linearVelocity.y`,避免干扰重力/跳跃。 - ⚠️ 注意:代码中 `new Vector2 (...)` 括号前多了一个空格,虽不影响编译但建议去掉。 #### 4. 物理移动 vs Transform 移动(对比) | 方式 | 原理 | 碰撞检测 | 适用场景 | |---|---|---|---| | `rb.linearVelocity` | 修改刚体速度 | ✅ 物理正确 | 平台跳跃、推箱子等 | | `transform.Translate` | 直接改位置 | ❌ 可能穿模 | 纯位移、无物理需求 | #### 5. 移动逻辑解析 ``` 按下 A / ← → xInput = -1 → velocity.x = -3.5 → 向左移动 松开按键 → xInput = 0 → velocity.x = 0 → 停止 按下 D / → → xInput = 1 → velocity.x = 3.5 → 向右移动 ``` --- ## 2026-05-24 · 第三次更新 **文件:** `Assets/Player.cs` ```csharp using UnityEngine; public class Player : MonoBehaviour { private Rigidbody2D rb; [SerializeField] private float moveSpeed = 3.5f; private float xInput; private void Awake() { rb = GetComponent(); } private void Update() // 旧的移动方式 { xInput = Input.GetAxisRaw("Horizontal"); rb.linearVelocity = new Vector2 (xInput * moveSpeed, rb.linearVelocity.y); } } ``` ### 本次改动 · 新增知识点 #### 1. `private` 字段与封装 - 之前字段全用 `public`,现在改为 `private`。 - **原则:** 不需要外部访问的字段应该设为私有,遵循封装原则,防止意外修改。 - `xInput` 改为 `private` 很合理——它只是内部计算用的临时值,Inspector 中没必要看到。 #### 2. `[SerializeField]` 特性 - 问题:字段设为 `private` 后,Inspector 面板中就看不到了。 - 解决:加 `[SerializeField]` 可以让**私有字段也显示在 Inspector 中**。 - 这样就做到了「Inspector 可调,代码不可外部访问」的最佳实践。 - `moveSpeed` 用了这个特性——可以在编辑器里调速度,但其他脚本不能随便改。 #### 3. `Awake()` 生命周期方法 - `Awake()` 在脚本实例加载时调用,**比 `Start()` 更早**,且只调用一次。 - Unity 生命周期顺序:`Awake()` → `OnEnable()` → `Start()` → `Update()` → ... - 适合做**组件引用的初始化**(获取自己身上的组件、初始化变量等)。 - 与 `Start()` 的区别:`Awake` 在所有脚本的 `Start` 之前执行完,适合组件间的依赖初始化。 #### 4. `GetComponent()` 方法 - 从当前 GameObject 上获取指定类型的组件引用。 - `rb = GetComponent();` — 自动找到同物体上的 Rigidbody2D 组件并赋值。 - 这是比手动在 Inspector 中拖拽更优雅的方式——**减少人为忘记绑定的风险**。 - 前提:该组件必须挂载在同一个 GameObject 上。 #### 5. 对比:手动拖拽 vs GetComponent | 方式 | 字段类型 | 优点 | 缺点 | |---|---|---|---| | Inspector 拖拽 | `public` | 可以绑定其他物体上的组件 | 容易忘记拖,运行时 NullReference | | `GetComponent` | `private` | 不会忘,代码自动获取 | 只能获取同一物体上的组件 | > 💡 当前代码中 `rb` 用的是 `GetComponent`,因为 Rigidbody2D 肯定在 Player 自己身上,很合适。 ### 使用建议 - 自己身上的组件 → `GetComponent` + `Awake` - 需要引用其他物体的组件 → `public`(或 `[SerializeField] private`)+ Inspector 拖拽 --- ## 2026-05-24 · 第四次更新 **文件:** `Assets/Player.cs` ```csharp using UnityEngine; public class Player : MonoBehaviour { private Rigidbody2D rb; [SerializeField] private float moveSpeed = 3.5f; private float xInput; private void Awake() { rb = GetComponent(); } private void Update() // 旧的移动方式 { xInput = Input.GetAxisRaw("Horizontal"); rb.linearVelocity = new Vector2 (xInput * moveSpeed, rb.linearVelocity.y); // 按K if (Input.GetKey(KeyCode.K)) { Debug.Log("holding K"); } // 按下K if (Input.GetKeyDown(KeyCode.K)) { Debug.Log("pressed K"); } } } ``` ### 本次改动 · 新增知识点 #### 1. 按键检测三剑客:`GetKey` / `GetKeyDown` / `GetKeyUp` | 方法 | 触发时机 | 返回值 | 典型用途 | |---|---|---|---| | `Input.GetKey(KeyCode)` | 按键**按住**期间 | 每帧 `true` | 持续开火、加速跑 | | `Input.GetKeyDown(KeyCode)` | 按键**按下**的那一帧 | 只 `true` 一次 | 跳跃、菜单确认 | | `Input.GetKeyUp(KeyCode)` | 按键**松开**的那一帧 | 只 `true` 一次 | 蓄力释放、弹起 | > ⚡ 记忆口诀:`GetKey` = 按住不放,`GetKeyDown` = 点一下,`GetKeyUp` = 松开那一刻。 #### 2. `KeyCode` 枚举 - 用 `KeyCode.K` 代替字符串 `"k"`,避免拼写错误,IDE 有自动补全。 - 常用按键:`KeyCode.Space`(空格)、`KeyCode.Escape`、`KeyCode.W/A/S/D`、`KeyCode.Mouse0`(鼠标左键)。 #### 3. `Debug.Log()` 调试输出 - 向 Unity 控制台(Console)输出消息,调试必备。 - `Debug.Log("xxx")` — 普通信息 - `Debug.LogWarning("xxx")` — 警告(黄色) - `Debug.LogError("xxx")` — 错误(红色,会暂停编辑器播放) #### 4. 代码验证思路 当前代码跑起来后的效果: - 按住 K → 控制台疯狂刷 "holding K"(每帧一条) - 敲一下 K → 只出现一条 "pressed K" - 通过这个对比就能直观理解 `GetKey` 和 `GetKeyDown` 的区别 ✅ --- ## 2026-05-24 · 第五次更新 **文件:** `Assets/Player.cs` ```csharp using UnityEngine; public class Player : MonoBehaviour { private Rigidbody2D rb; [SerializeField] private float moveSpeed = 3.5f; [SerializeField] private float jumpForce = 8; private float xInput; private void Awake() { rb = GetComponent(); } private void Update() // 旧的移动方式 { xInput = Input.GetAxisRaw("Horizontal"); rb.linearVelocity = new Vector2 (xInput * moveSpeed, rb.linearVelocity.y); if (Input.GetKeyDown(KeyCode.K)) { rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce); } } } ``` ### 本次改动 · 新增知识点 #### 1. 跳跃实现原理 - 跳跃 = 给刚体的 **Y 轴速度**一个瞬间向上的值。 - `rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);` - X 保持不变:角色在空中仍能左右移动 - Y 覆盖为 `jumpForce`(正数 = 向上) - 之后重力会自然把 Y 速度拉回负值,形成"上升→下落"的弧线。 #### 2. 为什么跳跃用 `GetKeyDown` 而不是 `GetKey` - `GetKeyDown` — 只在按下那一帧触发 → 按一次跳一次 ✅ - `GetKey` — 按住期间每帧都触发 → 角色会一直往上飞 ❌ - 跳跃是一次性动作,必须用 `GetKeyDown`。 #### 3. `jumpForce` 参数调优 - `jumpForce = 8` 是目前的值,可以根据手感调整: - 太小 → 跳不起来 - 太大 → 飞出屏幕 - 配合 Rigidbody2D 的 **Gravity Scale**(重力缩放)一起调,效果更好。 - Gravity Scale 越大 → 下落越快 → 跳跃手感更"重" - Gravity Scale 越小 → 轻飘飘(适合太空/月球感) #### 4. 当前完整移动逻辑图 ``` ┌─────────────────────────────────────────┐ │ Update() │ ├─────────────────────────────────────────┤ │ xInput = GetAxisRaw("Horizontal") │ │ ↓ │ │ velocity.x = xInput × moveSpeed │ ← 左右移动(每帧) │ velocity.y = 保持原来的 y │ │ ↓ │ │ if GetKeyDown(K) │ │ velocity.y = jumpForce │ ← 跳跃(按下瞬间) └─────────────────────────────────────────┘ ``` > ⚠️ 当前跳跃没有接地检测,角色可以在空中无限跳跃。后续可以加 `isGrounded` 判断来限制。 --- ## 2026-05-24 · 第六次更新 **文件:** `Assets/Player.cs` ```csharp using UnityEngine; public class Player : MonoBehaviour { private Animator anim; private Rigidbody2D rb; [SerializeField] private float moveSpeed = 3.5f; [SerializeField] private float jumpForce = 8; private float xInput; private void Awake() { rb = GetComponent(); anim = GetComponentInChildren(); } private void Update() { HandleInput(); HandleMovement(); HandleAnimations(); } private void HandleInput() { xInput = Input.GetAxisRaw("Horizontal"); if (Input.GetKeyDown(KeyCode.K)) { jump(); } } private void HandleMovement() { rb.linearVelocity = new Vector2(xInput * moveSpeed, rb.linearVelocity.y); } private void HandleAnimations() { bool isMoving = rb.linearVelocity.x != 0; anim.SetBool("isMoving", isMoving); } private void jump() { rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce); } } ``` ### 本次改动 · 新增知识点 #### 1. 代码重构:拆分 Update() - 之前 `Update()` 里什么都混在一起,现在拆成三个职责明确的方法: | 方法 | 职责 | |---|---| | `HandleInput()` | 读取玩家输入,触发相应动作 | | `HandleMovement()` | 根据输入计算并应用速度 | | `HandleAnimations()` | 根据状态更新动画参数 | - 好处:易读、易改、易扩展。比如以后加攻击,加一个 `HandleCombat()` 即可。 #### 2. `Animator` 组件与动画控制 - `Animator` 是 Unity 的动画状态机组件,控制角色动画的播放。 - `GetComponentInChildren()` — 从**子物体**上获取 Animator。 - 因为 Animator 通常挂在角色模型的子物体上,而不是 Player 根物体。 - 控制动画的方式:通过 `SetBool` / `SetFloat` / `SetTrigger` 等方法修改 Animator 参数。 #### 3. `anim.SetBool("isMoving", value)` - 在 Animator 窗口中预先创建 `isMoving` 参数(Bool 类型)。 - 代码中设置这个参数 → Animator 根据状态切换动画(Idle ↔ Run)。 - `isMoving = rb.linearVelocity.x != 0`:水平速度不为 0 就是移动中。 ``` isMoving = false → Idle 动画 isMoving = true → Run 动画 ``` #### 4. Animator 参数类型对比 | 类型 | 方法 | 用途 | |---|---|---| | Bool | `SetBool("name", true/false)` | 二元状态:跑/不跑、地面/空中 | | Float | `SetFloat("name", 值)` | 连续值:速度、方向 | | Trigger | `SetTrigger("name")` | 一次性触发:攻击、受伤、死亡 | | Int | `SetInteger("name", 值)` | 整数状态:武器序号、连击段数 | #### 5. `GetComponent` vs `GetComponentInChildren` | 方法 | 搜索范围 | 示例 | |---|---|---| | `GetComponent()` | 当前物体 | 获取 Player 上的 Rigidbody2D | | `GetComponentInChildren()` | 当前物体 + 所有子物体 | 获取模型子物体上的 Animator | | `GetComponentInParent()` | 当前物体 + 所有父物体 | 少用,偶尔反向查找 | #### 6. 当前架构图 ``` Player (MonoBehaviour) ├── Awake() │ ├── rb = GetComponent() │ └── anim = GetComponentInChildren() │ └── Update() ├── HandleInput() → 读取按键,调用 jump() ├── HandleMovement() → 设置 velocity └── HandleAnimations() → 设置 isMoving 驱动动画 ``` --- ## 2026-05-24 · 第七次更新 **文件:** `Assets/Player.cs` ```csharp using UnityEngine; public class Player : MonoBehaviour { private Animator anim; private Rigidbody2D rb; [SerializeField] private float moveSpeed = 3.5f; [SerializeField] private float jumpForce = 8; private float xInput; [SerializeField] private bool facingRight = true; // ... Awake、Update、HandleInput、HandleMovement、HandleAnimations、jump 同上 ... [ContextMenu("Flip")] private void Flip() { transform.Rotate(0, 180, 0); facingRight = !facingRight; } } ``` ### 本次改动 · 新增知识点 #### 1. `[ContextMenu]` 特性 —— 测试利器 - 给方法加上 `[ContextMenu("显示名称")]` 后,可以在 Inspector 中右键组件直接调用。 - **非常适合在编辑器中手动测试某个功能**,不需要写额外的测试代码。 ``` 用法: 1. 选中挂载 Player 脚本的 GameObject 2. 在 Inspector 中找到 Player 组件,右键 3. 菜单中出现 "Flip" → 点击即可执行 Flip() 方法 ``` #### 2. `transform.Rotate(x, y, z)` —— 旋转物体 - `transform.Rotate(0, 180, 0)` → 绕 Y 轴旋转 180°,即**水平翻转**。 - 三个参数分别对应 X / Y / Z 轴旋转(欧拉角,单位:度)。 - 绕 Y 轴旋转 180° 是 2D 游戏中最常用的翻转方式(让角色面朝另一个方向)。 | 旋转 | 效果 | |---|---| | `Rotate(0, 0, 0)` | 面朝右 | | `Rotate(0, 180, 0)` | 面朝左 | #### 3. `facingRight` 方向追踪 - `private bool facingRight = true;` 记录当前朝向。 - `[SerializeField]` 让它在 Inspector 中可见,方便调试时观察。 - `facingRight = !facingRight;` 每次翻转时取反,保持状态同步。 - 后续可根据 `facingRight` 决定子弹发射方向、攻击判定范围等。 #### 4. `ContextMenu` vs 其他调用方式 | 方式 | 何时用 | |---|---| | `[ContextMenu]` | 编辑器里手动测试某个功能 | | `Update()` 中调用 | 运行时每帧自动执行 | | `Button`(UI)| 玩家通过游戏界面触发 | > 💡 `[ContextMenu]` 即使方法是 `private` 也能调用,非常适合调试。 --- ## 2026-05-25 · 第八次更新 **文件:** `Assets/Player.cs` ```csharp using UnityEngine; public class Player : MonoBehaviour { private Animator anim; private Rigidbody2D rb; [SerializeField] private float moveSpeed = 4.58f; [SerializeField] private float jumpForce = 8; private float xInput; [SerializeField] private bool facingRight = true; // ... Awake 同上 ... private void Update() { HandleInput(); HandleMovement(); HandleAnimations(); HandleFlip(); // ← 新增 } // ... HandleInput、HandleMovement、HandleAnimations、jump 同上 ... private void HandleFlip() { if(rb.linearVelocity.x > 0 && facingRight == false) { Flip(); } else if (rb.linearVelocity.x < 0 && facingRight == true) { Flip(); } } private void Flip() { transform.Rotate(0, 180, 0); facingRight = !facingRight; } } ``` ### 本次改动 · 新增知识点 #### 1. `HandleFlip()` —— 自动转向逻辑 - 之前 `Flip()` 只能通过 `[ContextMenu]` 手动调用,现在改成**运行时自动翻转**。 - 核心思路:当角色**移动方向**与**当前朝向**不一致时,执行翻转。 #### 2. 翻转判断逻辑拆解 ``` 向右移动 (velocity.x > 0) 且 面朝左 (facingRight == false) → 翻转 向左移动 (velocity.x < 0) 且 面朝右 (facingRight == true) → 翻转 其他情况(静止、方向已一致) → 不翻转 ``` | 速度方向 | facingRight | 需要翻转?| |---|---|---| | 向右 (>0) | true(面朝右)| ❌ 方向一致 | | 向右 (>0) | false(面朝左)| ✅ 翻过来 | | 向左 (<0) | true(面朝右)| ✅ 翻过来 | | 向左 (<0) | false(面朝左)| ❌ 方向一致 | | 静止 (=0) | 任意 | ❌ 不动不翻 | #### 3. 为什么用 `facingRight` 而不是直接每帧翻转 - 如果每帧都根据速度方向直接翻转(`if velocity.x > 0 → 右,else → 左`),会导致: - 静止时角色朝向不确定(velocity.x = 0 时往哪边?) - 可能每帧都在调用 `Rotate`,浪费性能 - 用 `facingRight` 做**脏标记(dirty flag)**:只在需要翻转时才翻,静止时保持上次朝向。 #### 4. `[ContextMenu]` 的移除 - 上一次 `Flip()` 上挂了 `[ContextMenu("Flip")]` 用于手动测试。 - 现在翻转逻辑已集成到 `Update` 中自动运行,不再需要手动触发,所以去掉了。 - 这也是开发中的常见节奏:**先手动测试 → 确认 OK → 集成到自动化流程**。 #### 5. 当前完整流程图 ``` Update() ├── HandleInput() → 读取 A/D/K 输入 ├── HandleMovement() → xInput → velocity ├── HandleAnimations() → velocity.x != 0 → 跑/停 动画 └── HandleFlip() → 方向不一致时 Flip() └── Flip() ├── Rotate(0, 180, 0) └── facingRight = !facingRight ``` --- ## 2026-05-25 · 第九次更新 **文件:** `Assets/Player.cs` ```csharp using Unity.VisualScripting; using UnityEngine; public class Player : MonoBehaviour { private Animator anim; private Rigidbody2D rb; [Header("Movement details")] [SerializeField] private float moveSpeed = 4.58f; [SerializeField] private float jumpForce = 12; private bool facingRight = true; private float xInput; [Header("Collision details")] [SerializeField] private float groundCheckDistance; [SerializeField] private LayerMask whatIsGround; private bool isGrounded; // ... Awake 同上 ... private void Update() { HandleCollision(); // ← 新增,放在最前面 HandleInput(); HandleMovement(); HandleAnimations(); HandleFlip(); } // ... HandleInput、HandleMovement、HandleAnimations、HandleFlip、Flip 同上 ... private void jump() { if(isGrounded) // ← 加了接地判断 rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce); } private void HandleCollision() { isGrounded = Physics2D.Raycast(transform.position, Vector2.down, groundCheckDistance, whatIsGround); } private void OnDrawGizmos() { Gizmos.DrawLine(transform.position, transform.position + new Vector3(0, -groundCheckDistance)); } } ``` ### 本次改动 · 新增知识点 #### 1. 地面检测 (`isGrounded`) 解决无限跳跃 - 之前跳跃没有限制,空中也能一直跳。 - 现在 `jump()` 加了 `if(isGrounded)` 判断,只有站在地面上才能跳。 #### 2. `Physics2D.Raycast()` —— 2D 射线检测 - 从指定位置向指定方向发射一条**不可见的射线**,检测是否碰到碰撞体。 - 参数:`Physics2D.Raycast(起点, 方向, 距离, 层级掩码)` - 当前用法: ``` 起点:transform.position(角色脚底位置) 方向:Vector2.down(向下) 距离:groundCheckDistance 过滤:whatIsGround(只检测地面层) ``` ``` 角色 │ ─────┼───── transform.position │ ↓ 射线 (Vector2.down, 长度 = groundCheckDistance) ═════╪═════ 地面碰撞体 → 射线命中 → isGrounded = true ······│······ 没碰到地面 → isGrounded = false(在空中) ``` #### 3. `[Header("...")]` —— Inspector 分组标签 - Unity 会把 Inspector 字段按 `[Header]` 分组,加上加粗标题。 - 让 Inspector 面板更有条理: ``` ┌─ Player (Script) ──────────────┐ │ Script Player │ │ │ │ ── Movement details ── │ ← [Header("Movement details")] │ Move Speed 4.58 │ │ Jump Force 12 │ │ │ │ ── Collision details ── │ ← [Header("Collision details")] │ Ground Check Distance 1.5 │ │ What Is Ground Ground ▾ │ └────────────────────────────────┘ ``` #### 4. `LayerMask` —— 层级过滤 - `LayerMask whatIsGround` 在 Inspector 中显示为下拉菜单,可以选择哪些 Layer 算作"地面"。 - 射线只会检测被选中的层,忽略其他层(玩家自身、敌人等)。 - **必须设置:** 给地面物体分配一个 Layer(如"Ground"),然后在 Player 的 `whatIsGround` 中勾选它。 #### 5. `OnDrawGizmos()` —— Scene 视图可视化调试 - `Gizmos.DrawLine(起点, 终点)` 在 Scene 视图中画线,方便调试射线位置。 - 只在编辑器 Scene 视图可见,游戏运行时不会显示。 - 当前画了一条向下的红线,直观看到 `groundCheckDistance` 的长度是否合适。 #### 6. ⚠️ 注意事项 - `using Unity.VisualScripting;` 是多余的引用,当前代码并未使用 Visual Scripting 功能。可以安全删除,不影响编译但保持代码整洁。 - `jump()` 中 `if(isGrounded)` 后面没有花括号 —— 只有一行代码时可以省略 `{}`,但建议养成始终加括号的习惯,避免后续扩展时出错。 #### 7. `Vector2.down` 快捷向量 | 写法 | 等价于 | 含义 | |---|---|---| | `Vector2.up` | `(0, 1)` | 上 | | `Vector2.down` | `(0, -1)` | 下 | | `Vector2.left` | `(-1, 0)` | 左 | | `Vector2.right` | `(1, 0)` | 右 | | `Vector2.zero` | `(0, 0)` | 零向量 | | `Vector2.one` | `(1, 1)` | 单位向量 | #### 8. 当前架构图(更新版) ``` Player (MonoBehaviour) ├── Awake() │ ├── rb = GetComponent() │ └── anim = GetComponentInChildren() │ ├── Update() │ ├── HandleCollision() → Raycast 向下 → isGrounded │ ├── HandleInput() → 读取按键 │ ├── HandleMovement() → 设置 velocity │ ├── HandleAnimations() → 驱动动画 │ └── HandleFlip() → 自动转向 │ └── OnDrawGizmos() → Scene 视图画射线(仅编辑器) ```