优化,增加设计文档

This commit is contained in:
2026-06-29 02:48:13 +08:00
parent fb2dbfca99
commit 32484e8cb8
5 changed files with 2868 additions and 24 deletions
@@ -0,0 +1,377 @@
# Enemy双重寻敌机制实现计划
## 需求总结
1. **规则一:视觉检测与追击**
- 怪物通过射线检测看到玩家(360°全向视野)
- 看到玩家后追击
- 玩家脱离视线3秒后停止追击
2. **规则二:铃铛响应**
- 玩家摇铃铛时,铃铛声波范围内的怪物获取铃铛位置
- 怪物追击到铃铛位置
- 追击过程中如果看到玩家,切换为追击玩家(执行规则一)
- 到达铃铛位置后,如果没有看到玩家,在周围巡逻一段时间
## 状态机设计
```
Idle(待机)
├─ 射线检测到玩家 → ChasingPlayer
└─ 收到铃铛信号 → ChasingBell
ChasingPlayer(追击玩家)
├─ 持续看到玩家 → 继续追击
├─ 玩家脱离视线 → 开始计时(3秒)
│ └─ 3秒内重新看到 → 继续追击
│ └─ 3秒后仍未看到 → Idle
└─ 收到铃铛信号 → ChasingBell(打断当前状态)
ChasingBell(追击铃铛)
├─ 追击过程中射线检测到玩家 → ChasingPlayer
├─ 到达铃铛位置 → PatrollingBell
└─ 超时(10秒未到达)→ Idle
PatrollingBell(铃铛位置巡逻)
├─ 巡逻过程中射线检测到玩家 → ChasingPlayer
└─ 巡逻时间结束(5秒)→ Idle
```
## 实现步骤
### 步骤1:修改EnemyAI.cs - 添加新状态和参数
**文件**: `Assets/enemy/EnemyAI.cs`
**新增状态**:
```csharp
private enum State
{
Idle,
ChasingPlayer, // 追击玩家
ChasingBell, // 追击铃铛
PatrollingBell // 铃铛位置巡逻
}
```
**新增参数**:
```csharp
[Header("视觉检测")]
[Tooltip("射线检测层(哪些物体会阻挡视线)")]
[SerializeField] private LayerMask obstacleLayers;
[Tooltip("射线起点偏移(避免从地面发射)")]
[SerializeField] private float rayOffsetY = 0.5f;
[Header("脱离计时")]
[Tooltip("玩家脱离视线后,继续追击的时间(秒)")]
[SerializeField] private float loseSightTime = 3f;
[Header("铃铛追击")]
[Tooltip("到达铃铛位置后的容差距离")]
[SerializeField] private float bellArriveDistance = 1f;
[Tooltip("追击铃铛的最大时间(超时则放弃)")]
[SerializeField] private float bellChaseTimeout = 10f;
[Header("铃铛巡逻")]
[Tooltip("到达铃铛位置后的巡逻时间")]
[SerializeField] private float bellPatrolTime = 5f;
[Tooltip("巡逻半径")]
[SerializeField] private float bellPatrolRadius = 3f;
```
**新增变量**:
```csharp
private float _loseSightTimer; // 脱离视线计时器
private Vector3 _bellTargetPosition; // 铃铛目标位置
private float _bellChaseTimer; // 铃铛追击计时器
private Vector3 _patrolCenter; // 巡逻中心点
private float _patrolTimer; // 巡逻计时器
private Vector3 _patrolTarget; // 当前巡逻目标点
```
### 步骤2:实现射线检测视线
**新增方法**:
```csharp
/// <summary>
/// 检测是否能看到玩家(射线检测)
/// </summary>
private bool CanSeePlayer()
{
if (playerTarget == null) return false;
// 射线起点(稍微抬高,避免从地面发射)
Vector3 rayStart = transform.position + Vector3.up * rayOffsetY;
Vector3 rayEnd = playerTarget.position + Vector3.up * rayOffsetY;
Vector3 dir = rayEnd - rayStart;
float dist = dir.magnitude;
// 发射射线,只检测障碍物层
if (Physics.Raycast(rayStart, dir.normalized, out RaycastHit hit, dist, obstacleLayers))
{
// 如果击中的是玩家,说明可以看到
return hit.transform == playerTarget;
}
return false;
}
```
### 步骤3:修改状态机逻辑
**Idle状态**:
```csharp
private void UpdateIdle()
{
// 检测是否看到玩家
if (CanSeePlayer())
{
_state = State.ChasingPlayer;
_loseSightTimer = 0f;
return;
}
}
```
**ChasingPlayer状态**:
```csharp
private void UpdateChasingPlayer()
{
// 检测是否看到玩家
if (CanSeePlayer())
{
_loseSightTimer = 0f; // 重置计时器
// 追击玩家
float dist = XZDistance(transform.position, playerTarget.position);
if (dist > stopDistance)
{
MoveToward(playerTarget.position, chaseSpeed);
}
}
else
{
// 玩家脱离视线,开始计时
_loseSightTimer += Time.deltaTime;
// 继续朝玩家最后已知位置移动
if (playerTarget != null)
{
float dist = XZDistance(transform.position, playerTarget.position);
if (dist > stopDistance)
{
MoveToward(playerTarget.position, chaseSpeed);
}
}
// 超过3秒,停止追击
if (_loseSightTimer >= loseSightTime)
{
_state = State.Idle;
}
}
}
```
**ChasingBell状态**:
```csharp
private void UpdateChasingBell()
{
// 追击过程中检测是否看到玩家
if (CanSeePlayer())
{
_state = State.ChasingPlayer;
_loseSightTimer = 0f;
return;
}
// 追击计时
_bellChaseTimer += Time.deltaTime;
if (_bellChaseTimer >= bellChaseTimeout)
{
_state = State.Idle;
return;
}
// 检测是否到达铃铛位置
float dist = XZDistance(transform.position, _bellTargetPosition);
if (dist <= bellArriveDistance)
{
// 到达铃铛位置,开始巡逻
_state = State.PatrollingBell;
_patrolCenter = _bellTargetPosition;
_patrolTimer = 0f;
PickNewPatrolPoint();
return;
}
// 朝铃铛位置移动
MoveToward(_bellTargetPosition, chaseSpeed);
}
```
**PatrollingBell状态**:
```csharp
private void UpdatePatrollingBell()
{
// 巡逻过程中检测是否看到玩家
if (CanSeePlayer())
{
_state = State.ChasingPlayer;
_loseSightTimer = 0f;
return;
}
// 巡逻计时
_patrolTimer += Time.deltaTime;
if (_patrolTimer >= bellPatrolTime)
{
_state = State.Idle;
return;
}
// 朝巡逻点移动
float dist = XZDistance(transform.position, _patrolTarget);
if (dist <= 0.5f)
{
// 到达巡逻点,选择新的巡逻点
PickNewPatrolPoint();
}
else
{
MoveToward(_patrolTarget, chaseSpeed * 0.7f); // 巡逻时速度稍慢
}
}
/// <summary>
/// 选择新的巡逻点
/// </summary>
private void PickNewPatrolPoint()
{
// 在巡逻中心周围随机选择一个点
Vector2 randomDir = Random.insideUnitCircle * bellPatrolRadius;
_patrolTarget = _patrolCenter + new Vector3(randomDir.x, 0f, randomDir.y);
}
```
### 步骤4:修改铃铛响应逻辑
**OnBell方法**:
```csharp
private void OnBell(Vector3 bellPos)
{
if (playerTarget == null) return;
// 只有在聆听范围内才响应
if (XZDistance(transform.position, bellPos) > listenRange) return;
// 设置铃铛目标位置
_bellTargetPosition = bellPos;
_bellChaseTimer = 0f;
// 切换到追击铃铛状态(可以打断任何状态)
_state = State.ChasingBell;
}
```
### 步骤5:更新状态机调用
**Update方法**:
```csharp
private void Update()
{
switch (_state)
{
case State.Idle: UpdateIdle(); break;
case State.ChasingPlayer: UpdateChasingPlayer(); break;
case State.ChasingBell: UpdateChasingBell(); break;
case State.PatrollingBell: UpdatePatrollingBell(); break;
}
}
```
### 步骤6:更新编辑器可视化
**OnDrawGizmos方法**:
```csharp
private void OnDrawGizmosSelected()
{
// 根据状态显示不同颜色
Color stateColor = Color.white;
switch (_state)
{
case State.Idle: stateColor = Color.gray; break;
case State.ChasingPlayer: stateColor = Color.red; break;
case State.ChasingBell: stateColor = Color.yellow; break;
case State.PatrollingBell: stateColor = Color.cyan; break;
}
// 绘制当前状态指示
Gizmos.color = stateColor;
Gizmos.DrawWireSphere(transform.position, 0.5f);
// 绘制铃铛目标(如果在追击铃铛)
if (_state == State.ChasingBell || _state == State.PatrollingBell)
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(_bellTargetPosition, bellArriveDistance);
// 绘制到铃铛的连线
Gizmos.DrawLine(transform.position, _bellTargetPosition);
}
// 绘制巡逻范围(如果在巡逻)
if (_state == State.PatrollingBell)
{
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(_patrolCenter, bellPatrolRadius);
// 绘制当前巡逻目标
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(_patrolTarget, 0.3f);
}
}
```
## 配置参数建议
| 参数 | 建议值 | 说明 |
|-----|--------|------|
| `obstacleLayers` | Obstacle | 阻挡视线的物体层 |
| `rayOffsetY` | 0.5 | 射线起点高度 |
| `loseSightTime` | 3.0 | 脱离视线后继续追击时间 |
| `bellArriveDistance` | 1.0 | 到达铃铛位置的容差 |
| `bellChaseTimeout` | 10.0 | 追击铃铛最大时间 |
| `bellPatrolTime` | 5.0 | 铃铛位置巡逻时间 |
| `bellPatrolRadius` | 3.0 | 巡逻半径 |
## 测试要点
1. **视觉检测测试**
- 在Enemy和玩家之间放置障碍物,验证射线被阻挡
- 移除障碍物,验证能检测到玩家
2. **脱离视线测试**
- 玩家跑出Enemy视线,验证3秒后停止追击
- 3秒内重新出现,验证继续追击
3. **铃铛响应测试**
- 玩家摇铃铛,验证Enemy朝铃铛位置移动
- 铃铛追击过程中看到玩家,验证切换为追击玩家
4. **巡逻测试**
- 到达铃铛位置后,验证在周围巡逻
- 巡逻过程中看到玩家,验证切换为追击玩家
## 文件清单
- 修改: `Assets/enemy/EnemyAI.cs`
## 预计工作量
- 实现时间: 2-3小时
- 调试时间: 1-2小时
- 总计: 3-5小时
@@ -0,0 +1,476 @@
# 亡灵灯笼 - 游戏设计文档
## 🎮 游戏概述
**游戏名称**: 亡灵灯笼 (Soul Lantern)
**游戏类型**: 2.5D 冒险解谜 + 生存收集
**核心玩法**: 探索黑暗迷宫 → 收集亡灵 → 躲避怪物和陷阱 → 解救亡灵
**视觉风格**: 2.5D等距视角,黑暗氛围,灯笼光照效果
**目标平台**: PC (可扩展到移动端)
---
## 🎯 核心游戏循环
```
1. 探索阶段
- 玩家在黑暗迷宫中探索
- 灯笼提供有限视野
- 铃铛声会吸引怪物
2. 收集阶段
- 找到被困的亡灵
- 使用技能将亡灵收入灯笼
- 灯笼亮度和铃铛范围增加
3. 危险阶段
- 怪物被铃声吸引,开始追击
- 触发陷阱和机关
- 需要躲避或战斗
4. 逃脱阶段
- 找到出口或传送门
- 切换到下一张地图
- 保存进度
```
---
## 🕹️ 玩家系统
### 1. 灯笼系统 (Lantern System)
**核心道具**: 魔法灯笼
| 属性 | 初始值 | 升级方式 |
|-----|--------|---------|
| **照亮范围** | 5m | 收集亡灵后增加 |
| **铃铛范围** | 10m | 收集亡灵后增加 |
| **亡灵容量** | 3个 | 升级灯笼 |
| **特殊技能CD** | 10秒 | 升级技能 |
**功能**:
-**照明**: 提供周围视野(已实现LightMaskSystem
-**铃铛**: 按E释放声波(已实现EchoSystem
-**收集**: 靠近亡灵时自动收入灯笼
-**技能**: 按Q释放"灵魂冲击"(范围眩晕)
**视觉效果**:
```
灯笼亮度 = 基础亮度 + (亡灵数量 × 单个亡灵亮度)
视野半径 = 5m + (亡灵数量 × 2m)
铃铛半径 = 10m + (亡灵数量 × 3m)
```
### 2. 玩家属性
| 属性 | 说明 |
|-----|------|
| **生命值** | 3颗心(被怪物碰到扣1颗) |
| **移动速度** | 5单位/秒 |
| **闪避能力** | 空格键翻滚(短暂无敌) |
| **技能CD** | 10秒(可升级减少) |
### 3. 技能系统
#### 技能1: 灵魂冲击 (Soul Shock)
- **按键**: Q
- **CD**: 10秒
- **效果**: 以玩家为中心,释放灵魂波动
- 范围内怪物眩晕3秒
- 可以打断怪物攻击
- 可以触发陷阱(提前引爆)
- **范围**: 8m(随亡灵数量增加)
#### 技能2: 灵魂护盾 (Soul Shield) - 解锁
- **解锁条件**: 收集10个亡灵
- **按键**: 右键
- **CD**: 15秒
- **效果**: 生成护盾,抵挡1次伤害
#### 技能3: 灵魂传送 (Soul Teleport) - 解锁
- **解锁条件**: 收集20个亡灵
- **按键**: 双击空格
- **CD**: 20秒
- **效果**: 短距离传送(10m内)
---
## 👹 敌人系统
### 1. 怪物类型
基于已实现的 `EnemyAI.cs`,扩展以下怪物类型:
#### 游荡者 (Wanderer)
- **行为**: 在固定路径巡逻
- **视野**: 15m(已实现maxSightDistance
- **速度**: 3m/s
- **特点**: 被铃声吸引(已实现BellResponding
- **弱点**: 移动缓慢,容易躲避
#### 守卫者 (Guardian)
- **行为**: 站岗不动,看到玩家追击
- **视野**: 20m
- **速度**: 4m/s
- **特点**: 攻击范围广,伤害高
- **弱点**: 只能直线攻击,可以绕柱躲避
#### 猎手 (Hunter)
- **行为**: 快速追击,会预判玩家位置
- **视野**: 25m
- **速度**: 6m/s
- **特点**: 会呼叫其他怪物
- **弱点**: 血量低,可以被灵魂冲击击杀
#### Boss: 暗影领主 (Shadow Lord)
- **行为**: 多阶段战斗
- **视野**: 全图
- **特殊能力**:
- 阶段1: 召唤小怪
- 阶段2: 黑暗领域(缩小玩家视野)
- 阶段3: 灵魂锁链(减速玩家)
### 2. 怪物AI增强
基于现有 `EnemyAI.cs`,添加以下功能:
```csharp
// 新增状态
private enum State
{
Idle,
ChasingPlayer,
ChasingBell,
PatrollingBell,
Stunned, // 眩晕(灵魂冲击)
Searching, // 搜索玩家(丢失视线后)
Returning // 返回巡逻点
}
// 新增参数
[Header("搜索行为")]
[SerializeField] private float searchDuration = 5f; // 搜索时间
[SerializeField] private float returnDistance = 10f; // 返回距离
[Header("攻击行为")]
[SerializeField] private float attackRange = 1.5f; // 攻击范围
[SerializeField] private float attackCooldown = 2f; // 攻击CD
[SerializeField] private int damage = 1; // 伤害值
```
**新增行为**:
-**搜索**: 丢失玩家视线后,在附近搜索5秒
-**返回**: 搜索失败后,返回巡逻点
-**攻击**: 靠近玩家时发动攻击
-**眩晕**: 被灵魂冲击击中后眩晕3秒
---
## 🗺️ 关卡设计
### 1. 地图系统
**地图结构**: 多个迷宫地图,通过传送门切换
#### 地图1: 废弃墓穴 (新手教程)
- **大小**: 50x50m
- **特点**: 简单迷宫,少量怪物
- **亡灵数量**: 5个
- **陷阱**: 地刺(固定位置)
- **目标**: 教学基本操作
#### 地图2: 幽暗森林
- **大小**: 80x80m
- **特点**: 复杂迷宫,视野受限
- **亡灵数量**: 10个
- **陷阱**: 地刺 + 飞箭
- **新元素**: 可破坏的墙壁
#### 地图3: 地下神殿
- **大小**: 100x100m
- **特点**: 多层结构,机关谜题
- **亡灵数量**: 15个
- **陷阱**: 所有陷阱 + 移动平台
- **新元素**: 机关门(需要激活)
#### 地图4: 暗影领域 (Boss战)
- **大小**: 60x60m
- **特点**: 开阔场地,Boss战
- **亡灵数量**: 0(专注Boss
- **陷阱**: 无
- **目标**: 击败暗影领主
### 2. 陷阱系统
#### 地刺 (Ground Spikes)
```csharp
public class GroundSpikes : MonoBehaviour
{
[SerializeField] private float damage = 1f;
[SerializeField] private float triggerRange = 1f;
[SerializeField] private float cooldown = 3f;
// 触发方式: 玩家踩上
// 效果: 扣1颗心,短暂无敌
}
```
#### 飞箭 (Flying Arrow)
```csharp
public class FlyingArrow : MonoBehaviour
{
[SerializeField] private float speed = 10f;
[SerializeField] private float damage = 1f;
[SerializeField] private float lifetime = 5f;
// 触发方式: 玩家进入触发区域
// 效果: 从固定方向发射箭矢
}
```
#### 落石 (Falling Rock)
```csharp
public class FallingRock : MonoBehaviour
{
[SerializeField] private float warningTime = 1f;
[SerializeField] private float damage = 1f;
[SerializeField] private float radius = 2f;
// 触发方式: 玩家进入区域
// 效果: 警告1秒后落石,范围伤害
}
```
### 3. 亡灵系统
#### 亡灵类型
**普通亡灵**:
- **外观**: 半透明人形
- **行为**: 被困在笼子/结界中
- **收集**: 靠近后自动收入灯笼
- **效果**: 灯笼亮度+1,铃铛范围+2m
**特殊亡灵**:
- **战士亡灵**: 提供战斗技能
- **法师亡灵**: 提供魔法护盾
- **盗贼亡灵**: 提供传送技能
#### 亡灵数据结构
```csharp
[System.Serializable]
public class SoulData
{
public string soulName; // 亡灵名称
public SoulType soulType; // 类型: 普通/战士/法师/盗贼
public int level; // 等级
public float brightnessBonus; // 亮度加成
public float rangeBonus; // 范围加成
public string specialAbility; // 特殊能力
}
```
---
## 🎨 UI系统
### 1. HUD (抬头显示)
```
┌─────────────────────────────────────┐
│ ❤❤❤ 🕐 00:12:34 │
│ │
│ [游戏画面] │
│ │
│ 🔦 灯笼: ████░░ 4/6 亡灵 │
│ ⚡ 技能: 就绪 (10s CD) │
│ 🗺️ 地图: 废弃墓穴 │
└─────────────────────────────────────┘
```
### 2. 灯笼UI
- **图标**: 灯笼图标
- **进度条**: 显示当前亡灵数量/容量
- **亮度指示**: 动态光圈大小
### 3. 技能UI
- **图标**: 技能图标
- **CD指示**: 灰色覆盖层 + 倒计时数字
- **快捷键**: Q / 右键 / 双击空格
### 4. 地图UI
- **小地图**: 右上角显示已探索区域
- **任务标记**: 显示亡灵位置和出口
---
## 💾 存档系统
### 1. 存档数据
```csharp
[System.Serializable]
public class SaveData
{
public int currentMapIndex; // 当前地图索引
public Vector3 playerPosition; // 玩家位置
public int playerHealth; // 玩家生命
public List<SoulData> collectedSouls; // 收集的亡灵
public float lanternBrightness; // 灯笼亮度
public float bellRange; // 铃铛范围
public float skillCooldown; // 技能CD
public Dictionary<string, bool> mapProgress; // 地图进度
}
```
### 2. 存档点
- **存档水晶**: 在地图中分布
- **自动存档**: 到达新地图时
- **手动存档**: 在存档水晶处按F
---
## 🎵 音效系统
### 1. 环境音效
| 场景 | 音效 |
|-----|------|
| 墓穴 | 风声、滴水声、远处哀嚎 |
| 森林 | 树叶沙沙声、猫头鹰叫 |
| 神殿 | 回声、机关运转声 |
| Boss战 | 紧张BGM、Boss咆哮 |
### 2. 交互音效
| 行为 | 音效 |
|-----|------|
| 铃铛 | 清脆铃声(已实现) |
| 收集亡灵 | 灵魂吸收音 |
| 技能释放 | 灵魂爆发音 |
| 受伤 | 痛苦呻吟 |
| 陷阱触发 | 机械触发音 |
---
## 📊 游戏平衡
### 1. 难度曲线
```
地图1 (废弃墓穴):
- 怪物数量: 3
- 陷阱数量: 5
- 亡灵数量: 5
- 难度: ★☆☆☆☆
地图2 (幽暗森林):
- 怪物数量: 6
- 陷阱数量: 10
- 亡灵数量: 10
- 难度: ★★☆☆☆
地图3 (地下神殿):
- 怪物数量: 10
- 陷阱数量: 15
- 亡灵数量: 15
- 难度: ★★★☆☆
地图4 (暗影领域):
- 怪物数量: 1 (Boss)
- 陷阱数量: 0
- 亡灵数量: 0
- 难度: ★★★★★
```
### 2. 资源平衡
**灯笼容量**:
- 初始: 3个亡灵
- 地图1后: 6个亡灵
- 地图2后: 9个亡灵
- 地图3后: 12个亡灵
**技能CD**:
- 初始: 10秒
- 每收集5个亡灵: -1秒
- 最低: 5秒
---
## 🚀 开发优先级
### Phase 1: 核心玩法 (2周)
- ✅ 灯笼系统(照明+铃铛)- 已实现
- ✅ Enemy AI(视觉+铃声)- 已实现
- ⏳ 玩家移动和碰撞
- ⏳ 亡灵收集逻辑
### Phase 2: 战斗系统 (1周)
- ⏳ 灵魂冲击技能
- ⏳ 怪物攻击逻辑
- ⏳ 玩家受伤和无敌帧
### Phase 3: 关卡设计 (2周)
- ⏳ 地图1: 废弃墓穴
- ⏳ 陷阱系统(地刺、飞箭)
- ⏳ 亡灵分布
### Phase 4: UI和存档 (1周)
- ⏳ HUD系统
- ⏳ 存档功能
- ⏳ 地图切换
### Phase 5: 扩展内容 (2周)
- ⏳ 地图2-4
- ⏳ 更多怪物类型
- ⏳ Boss战
---
## 🎯 下一步行动
基于现有代码,建议按以下顺序开发:
1. **玩家控制器增强**
- 添加生命值系统
- 添加技能系统(灵魂冲击)
- 添加灯笼升级逻辑
2. **亡灵系统**
- 创建亡灵预制体
- 实现收集逻辑
- 实现灯笼亮度动态调整
3. **陷阱系统**
- 创建地刺预制体
- 创建飞箭预制体
- 实现陷阱触发逻辑
4. **地图系统**
- 创建地图1(废弃墓穴)
- 实现地图切换逻辑
- 添加传送门
5. **UI系统**
- 创建HUD
- 添加灯笼和技能UI
- 添加小地图
---
这个设计充分利用了你已经实现的功能:
- ✅ 黑暗遮罩系统 (LightMaskSystem)
- ✅ 回声系统 (EchoSystem)
- ✅ Enemy AI (EnemyAI.cs)
- ✅ Y-Sort排序
- ✅ 地面裁剪Shader
- ✅ 2.5D摄像机系统
需要我帮你实现哪个部分?