Files
rpg_start/StudyNotes.md

786 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<Rigidbody2D>();
}
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<T>()` 方法
- 从当前 GameObject 上获取指定类型的组件引用。
- `rb = GetComponent<Rigidbody2D>();` — 自动找到同物体上的 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<Rigidbody2D>();
}
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<Rigidbody2D>();
}
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<Rigidbody2D>();
anim = GetComponentInChildren<Animator>();
}
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。
- 因为 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<T>()` | 当前物体 | 获取 Player 上的 Rigidbody2D |
| `GetComponentInChildren<T>()` | 当前物体 + 所有子物体 | 获取模型子物体上的 Animator |
| `GetComponentInParent<T>()` | 当前物体 + 所有父物体 | 少用,偶尔反向查找 |
#### 6. 当前架构图
```
Player (MonoBehaviour)
├── Awake()
│ ├── rb = GetComponent<Rigidbody2D>()
│ └── anim = GetComponentInChildren<Animator>()
└── 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<Rigidbody2D>()
│ └── anim = GetComponentInChildren<Animator>()
├── Update()
│ ├── HandleCollision() → Raycast 向下 → isGrounded
│ ├── HandleInput() → 读取按键
│ ├── HandleMovement() → 设置 velocity
│ ├── HandleAnimations() → 驱动动画
│ └── HandleFlip() → 自动转向
└── OnDrawGizmos() → Scene 视图画射线(仅编辑器)
```