refactor: move all files into Study_One folder

This commit is contained in:
2026-05-27 10:59:19 +08:00
parent 47b0f1d992
commit 54ee2462e0
6 changed files with 0 additions and 0 deletions

12
Study_One/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
# Ignore all files
*
# Unignore directories (needed so git can look inside)
!*/
# Only track .cs and .md files
!*.cs
!*.md
# And the gitignore itself
!.gitignore

42
Study_One/Assets/Enemy.cs Normal file
View File

@@ -0,0 +1,42 @@
using UnityEngine;
public class Enemy : MonoBehaviour
{
private SpriteRenderer sr;
[SerializeField]private float redColorDuration = 1;
public float currentTimeInGame;
public float lastTimeWasDamaged;
private void Awake()
{
sr = GetComponent<SpriteRenderer>();
}
private void Update()
{
ChanceColorIfNeedes();
}
private void ChanceColorIfNeedes()
{
currentTimeInGame = Time.time;
if (currentTimeInGame > lastTimeWasDamaged + redColorDuration)
{
if (sr.color != Color.white)
{
TurnWhite();
}
}
}
public void TakeDamage()
{
sr.color = Color.red;
lastTimeWasDamaged = Time.time;
}
private void TurnWhite()
{
sr.color = Color.white;
}
}

139
Study_One/Assets/Player.cs Normal file
View File

@@ -0,0 +1,139 @@
using UnityEngine;
public class Player : MonoBehaviour
{
private Animator anim;
private Rigidbody2D rb;
[Header("Attack details")]
[SerializeField] private float attackRadius;
[SerializeField] private Transform attackPoint;
[SerializeField] private LayerMask whatIsEnemy;
[Header("Movement details")]
[SerializeField] private float moveSpeed = 8f;
[SerializeField] private float jumpForce = 12;
private bool facingRight = true;
private float xInput;
private bool canMove = true;
private bool canJump = true;
[Header("Collision details")]
[SerializeField] private float groundCheckDistance;
[SerializeField] private LayerMask whatIsGround;
private bool isGrounded;
private void Awake()
{
rb = GetComponent<Rigidbody2D>();
anim = GetComponentInChildren<Animator>();
}
private void Update()
{
HandleCollision();
HandleInput();
HandleMovement();
HandleAnimations();
HandleFlip();
}
public void DamageEnemies()
{
Collider2D[] enemyColliders = Physics2D.OverlapCircleAll(attackPoint.position, attackRadius, whatIsEnemy);
foreach (Collider2D enemy in enemyColliders)
{
enemy.GetComponent<Enemy>().TakeDamage();
}
}
public void EnableMovementAndJump(bool enable)
{
canMove = enable;
canJump = enable;
}
private void HandleInput()
{
xInput = Input.GetAxisRaw("Horizontal");
if (Input.GetKeyDown(KeyCode.K))
{
TryToJump();
}
if (Input.GetKeyDown(KeyCode.J))
{
TryToAttack();
}
}
private void TryToAttack()
{
if(isGrounded && canMove)
{
anim.SetTrigger("attack");
}
}
private void HandleAnimations()
{
anim.SetFloat("xVelocity", rb.linearVelocity.x);
anim.SetFloat("yVelocity", rb.linearVelocity.y);
anim.SetBool("isGrounded", isGrounded);
}
private void HandleMovement()
{
if(canMove)
{
rb.linearVelocity = new Vector2(xInput * moveSpeed, rb.linearVelocity.y);
}
else
{
rb.linearVelocity = new Vector2(0, rb.linearVelocity.y);
}
}
private void TryToJump()
{
if (isGrounded && canJump)
{
rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
}
}
private void HandleCollision()
{
isGrounded = Physics2D.Raycast(transform.position, Vector2.down, groundCheckDistance, whatIsGround);
}
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;
}
private void OnDrawGizmos()
{
Gizmos.DrawLine(transform.position, transform.position + new Vector3(0, -groundCheckDistance));
Gizmos.DrawWireSphere(attackPoint.position, attackRadius);
}
}

View File

@@ -0,0 +1,15 @@
using UnityEngine;
public class PlayAnimationEvents : MonoBehaviour
{
private Player player;
private void Awake()
{
player = GetComponentInParent<Player>();
}
public void DamageEnemies() => player.DamageEnemies();
private void DisableMovementAndJump() => player.EnableMovementAndJump(false);
private void EnableMovementAndJump() => player.EnableMovementAndJump(true);
}

175
Study_One/Q&A.md Normal file
View File

@@ -0,0 +1,175 @@
# Q&A
> 学习过程中遇到的问题记录
---
## Q1为什么保留 `rb.linearVelocity.y` 不会导致 Player 上下移动,而改变 `x` 就会左右移动?
### 关键理解
`rb.linearVelocity` 每一帧都被**完整重新赋值**X 和 Y 的来源不同决定了它们的行为不同。
### X 轴:由输入主动控制
```csharp
rb.linearVelocity = new Vector2(xInput * moveSpeed, rb.linearVelocity.y);
// ^^^^^^^^^^^^^^^^
// 这部分的数值由你决定
```
| 操作 | `xInput` | `velocity.x` | 效果 |
|------|:--------:|:------------:|:----:|
| 按 A / ← | -1 | -3.5 | 向左移动 |
| 松开 | 0 | 0 | 停止 |
| 按 D / → | 1 | 3.5 | 向右移动 |
每次赋值X 都被设为**输入决定的明确值**,所以会立刻产生/停止移动。
### Y 轴:物理系统已经在运行
`rb.linearVelocity.y` 不是空的,它已经是物理引擎当前帧算好的结果:
| 状态 | `velocity.y` 实际值 | 保留后效果 |
|------|:-------------------:|:----------:|
| 站地上 | ≈ 0 | 不动(地面碰撞把 y 锁为 0 |
| 下落中 | < 0重力累加 | 继续下落 |
| 跳起后 | > 0 → 逐渐减小 | 上升 → 减速 → 下落 |
保留 Y 的本质是 **"不去干涉"** 物理系统对垂直运动的控制,而不是"主动让角色上下动"。
### 反例:写死 Y 会怎样?
```csharp
rb.linearVelocity = new Vector2(xInput * moveSpeed, 0); // ❌
```
- 重力被清零 → 角色不会下落,飘在空中
- 跳跃被清零 → 跳不起来
- 地面碰撞结果被覆盖 → 可能会卡进地面
### 一句话总结
> X 轴 velocity 是你主动赋值来**命令移动**Y 轴 velocity 是物理系统**正在运行的结果**,保留它只是不去搞破坏。
---
*2026-05-23*
## Q2跳跃时直接覆盖 Y 为 `jumpForce`,和之前说的"保留 Y"不矛盾吗?为什么不用 `y + jumpForce`
### 问题背景
之前的 Q&A 说"保留 `rb.linearVelocity.y` 不去干涉物理系统",但跳跃代码却直接覆盖:
```csharp
// 平时移动:保留 Y不去干扰物理
rb.linearVelocity = new Vector2(xInput * moveSpeed, rb.linearVelocity.y);
// 跳跃时:覆盖 Y
rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
```
直觉上会觉得应该写成 `rb.linearVelocity.y + jumpForce` 才对。
### 核心解答
**场景不同,原则不同:**
| 场景 | 对 Y 的操作 | 意图 |
|------|:----------:|:----:|
| 左右移动 | 保留 Y | **不管**物理系统的事 |
| 跳跃 | 覆盖 Y = jumpForce | **主动干预**,跳跃本身就是这个干预 |
平时保留 Y 是因为"不需要管 Y",跳跃覆盖 Y 是因为"现在就要动 Y"。
### 为什么用 `= jumpForce` 而不是 `+= jumpForce`
站在平地时 `velocity.y ≈ 0`,两者结果一样。但在以下情况加法会出问题:
| 场景 | velocity.y 当前值 | `= jumpForce` | `+= jumpForce` | 哪个合理 |
|------|:--:|:--:|:--:|:--:|
| 平地站着 | ≈ 0 | = 8 ✅ | = 8 ✅ | 一样 |
| 在上升平台上跳 | > 0如 +3 | = 8 ✅ | = 11 ❌ 异常高 | `=` |
| 下落中误触跳跃 | < 0如 -5 | = 8 ✅ | = 3 ❌ 跳不起来 | `=` |
- **`= jumpForce`** — 每次跳跃高度一致,可预测 ✅
- **`+= jumpForce`** — 跳跃高度受当前 Y 速度影响,结果不稳定 ❌
### 总结
> 保留 Y = **不想管**物理的事
> 覆盖 Y = **正在主动做跳跃这件事**
> 用 `=` 保证每次跳跃可预测,用 `+=` 反而会让跳跃高度飘忽不定
---
## Q3二段跳可以从哪些方面入手设计
### 前提
第九次更新已经实现了接地检测(`isGrounded`),跳跃被限制为"只有在地上才能跳"。在此基础上扩展二段跳,需要考虑以下几个维度:
### 1. 核心机制 —— 如何判断"能不能跳"
**计数器方案(推荐,可扩展性强):**
```
jumpCount = 允许的跳跃次数(例如 2
remainingJumps = 当前剩余次数
想跳时:
1. 检查 remainingJumps > 0
2. 是 → 跳remainingJumps--
3. 否 → 不跳
落地时:
重置 remainingJumps = jumpCount
```
- 优点:改为 3 段跳、4 段跳只需改一个数字,代码不用动
- 问自己:`remainingJumps` 什么时候扣?什么时候重置?
### 2. 重置时机 —— "跳"这个状态何时结束
核心问题:**二段跳的"次数"在什么时候补回来?**
| 方案 | 行为 | 手感 |
|------|------|------|
| 落地重置 | 碰到地面才能再次二段跳 | 标准平台跳跃,最常用 |
| 接触墙壁重置 | 贴墙也能补跳 | 适合有爬墙机制的游戏 |
| 每 X 秒恢复一次 | 空中待一会儿又能跳 | 节奏较怪,很少用 |
> 当前项目有 `isGrounded`,落地重置是最自然的选择。
### 3. 跳跃力设计 —— 二段跳应该和第一段一样高吗?
| 方案 | 效果 | 代表游戏 |
|------|------|---------|
| 相同 jumpForce | 两段跳一样高,手感直接 | 空洞骑士(部分技能)|
| 稍弱(如 jumpForce × 0.8| 第二段更低,更真实 | 蔚蓝 Celeste |
| 固定低值 | 二段跳只用来"续一下" | 大乱斗 |
> 可以先设两个独立参数 `jumpForce` 和 `doubleJumpForce`,分别调。
### 4. 需要考虑的边界问题
| 问题 | 说明 |
|------|------|
| **走边缘掉落** | 从平台边缘走下来(没按跳),空中能二段跳吗?通常可以 |
| **没用第一段跳** | 跳过一次后空中还能跳一次?还是说必须先用一次跳?取决于设计 |
| **Coyote Time** | 离开地面后很短时间(如 0.1s)内按跳还算落地跳吗?和剩余次数如何叠加? |
| **动画反馈** | 二段跳时播放不同的动画(如翻转、翅膀),让玩家感知到"我用了二段跳" |
| **只能一次** | 二段跳用完没落地之前,不能再获得跳跃次数 |
### 5. 建议的切入顺序
1. 先把计数器机制做出来(`jumpCount` / `remainingJumps`
2. `jump()` 中把 `if(isGrounded)` 改为 `if(remainingJumps > 0)`
3. 落地时重置 `remainingJumps = jumpCount`
4.`doubleJumpForce` 找手感
5. 加动画区分一段跳和二段跳
---
*2026-05-25*

1807
Study_One/StudyNotes.md Normal file

File diff suppressed because it is too large Load Diff