# 29 · 难度模式指南 > **命名空间** `BaseGames.Core` > **所属文档集** [← 返回索引](./README.md) · [总览](./00_Overview.md) > **依赖** `BaseGames.Core.Events` · `BaseGames.Player`(PlayerStats)· `BaseGames.Enemies`(EnemyStats) --- ## 目录 1. [系统总览](#1-系统总览) 2. [难度等级定义](#2-难度等级定义) 3. [DifficultyScalerSO — 难度缩放配置](#3-difficultyscalerso--难度缩放配置) 4. [DifficultyManager — 运行时管理](#4-difficultymanager--运行时管理) 5. [钢铁之魂模式特殊规则](#5-钢铁之魂模式特殊规则) 6. [敌人 AI 行为差异](#6-敌人-ai-行为差异) 7. [与各系统的集成](#7-与各系统的集成) 8. [SaveData 集成](#8-savedata-集成) 9. [UI 选择界面](#9-ui-选择界面) 10. [编辑器友好设计](#10-编辑器友好设计) --- ## 1. 系统总览 ``` 难度模式职责: ├─ DifficultyLevel 枚举 → Easy / Normal / Hard / SteelSoul ├─ DifficultyScalerSO → 各难度的数值缩放配置 ├─ DifficultyManager → 全局难度状态,应用缩放到各系统 └─ 系统集成 → PlayerStats / EnemyStats / ShopController 读取缩放系数 ``` **零耦合**:各系统**不持有** `DifficultyManager` 引用,而是在初始化时读取 `DifficultyScalerSO` 中对应难度的缩放系数并缓存,或监听 `OnDifficultyChanged` 事件频道动态更新。 --- ## 2. 难度等级定义 | 等级 | 名称 | 目标玩家 | 核心差异 | |------|------|---------|---------| | `Easy` | **协助模式** | 休闲玩家 / 无障碍 | 玩家 HP × 1.5,敌人伤害 × 0.7,死亡无 Geo 损失 | | `Normal` | **标准模式** | 大多数玩家(默认)| 基准数值,死亡掉落 Geo 至遗骸 | | `Hard` | **穿刺模式** | 挑战玩家 | 玩家 HP × 0.75,敌人伤害 × 1.3,敌人 AI 更激进 | | `SteelSoul` | **钢铁之魂** | 极限玩家 | 仅一命,死亡即清档;钢铁之魂专属 UI 标识 | > 难度在**新游戏开始时**选择,游戏进行中可在 Easy / Normal / Hard 之间切换(`SteelSoul` 一旦选择不可降级)。 --- ## 3. DifficultyScalerSO — 难度缩放配置 ```csharp [CreateAssetMenu(menuName = "Core/DifficultyScaler")] public class DifficultyScalerSO : ScriptableObject { [Header("玩家属性缩放")] [Range(0.1f, 3.0f)] public float PlayerMaxHPMultiplier = 1.0f; // 最大 HP 倍率 [Range(0.1f, 3.0f)] public float PlayerDamageMultiplier = 1.0f; // 玩家造成的伤害倍率 [Range(0.0f, 1.0f)] public float InvincibilityFrameScale = 1.0f; // 无敌帧时长倍率 [Header("敌人属性缩放")] [Range(0.1f, 3.0f)] public float EnemyDamageMultiplier = 1.0f; // 敌人造成的伤害倍率 [Range(0.1f, 3.0f)] public float EnemyHPMultiplier = 1.0f; // 敌人 HP 倍率 [Range(0.1f, 3.0f)] public float BossDamageMultiplier = 1.0f; // Boss 伤害单独控制 [Range(0.1f, 3.0f)] public float BossHPMultiplier = 1.0f; [Header("游戏规则")] public bool CanReviveWithGeoLoss = true; // 死亡时 Geo 掉落至遗骸 public bool InstantDeathOnZeroHP = false; // 钢铁之魂:HP 归零直接清档 public bool GeoPenaltyOnDeath = true; // false = Easy 无 Geo 损失 [Header("AI 行为(用于 BehaviorDesigner 黑板变量)")] [Range(0.1f, 2.0f)] public float EnemyReactionTimeScale = 1.0f; // 1.0 = 正常,< 1 = 更快反应 [Range(0f, 1f)] public float EnemyAggressionLevel = 0.5f; // 0 = 最保守,1 = 最激进 [Header("经济")] [Range(0.5f, 2.0f)] public float ShopPriceMultiplier = 1.0f; // 商店价格倍率 [Range(0.5f, 2.0f)] public float GeoDropMultiplier = 1.0f; // 击杀敌人 Geo 掉落倍率 } ``` **资产存放路径**:`Assets/ScriptableObjects/Config/Difficulty/` ### 四种难度预设值 | 参数 | Easy | Normal | Hard | SteelSoul | |------|------|--------|------|-----------| | `PlayerMaxHPMultiplier` | 1.5 | 1.0 | 0.75 | 1.0 | | `PlayerDamageMultiplier` | 1.0 | 1.0 | 1.0 | 1.0 | | `InvincibilityFrameScale` | 1.3 | 1.0 | 0.8 | 1.0 | | `EnemyDamageMultiplier` | 0.7 | 1.0 | 1.3 | 1.5 | | `EnemyHPMultiplier` | 0.9 | 1.0 | 1.15 | 1.2 | | `BossDamageMultiplier` | 0.6 | 1.0 | 1.4 | 1.6 | | `BossHPMultiplier` | 0.8 | 1.0 | 1.2 | 1.3 | | `GeoPenaltyOnDeath` | false | true | true | true | | `InstantDeathOnZeroHP` | false | false | false | **true** | | `EnemyReactionTimeScale` | 1.4 | 1.0 | 0.7 | 0.6 | | `EnemyAggressionLevel` | 0.3 | 0.5 | 0.75 | 0.9 | | `ShopPriceMultiplier` | 0.9 | 1.0 | 1.1 | 1.0 | | `GeoDropMultiplier` | 1.0 | 1.0 | 1.2 | 1.5 | --- ## 4. DifficultyManager — 运行时管理 ```csharp namespace BaseGames.Core { public class DifficultyManager : MonoBehaviour { // ── 配置 ───────────────────────────────────────────────── [SerializeField] DifficultyScalerSO _easySO; [SerializeField] DifficultyScalerSO _normalSO; [SerializeField] DifficultyScalerSO _hardSO; [SerializeField] DifficultyScalerSO _steelSoulSO; [SerializeField] StringEventChannelSO _onDifficultyChanged; // payload = 枚举名 // ── 当前难度 ────────────────────────────────────────────── public DifficultyLevel CurrentLevel { get; private set; } = DifficultyLevel.Normal; public DifficultyScalerSO CurrentScaler { get; private set; } void Awake() => ApplyDifficulty(DifficultyLevel.Normal); /// 切换难度(SteelSoul 不可降级) public bool TrySetDifficulty(DifficultyLevel level) { if (CurrentLevel == DifficultyLevel.SteelSoul && level != DifficultyLevel.SteelSoul) return false; // 钢铁之魂不可降级 ApplyDifficulty(level); return true; } void ApplyDifficulty(DifficultyLevel level) { CurrentLevel = level; CurrentScaler = level switch { DifficultyLevel.Easy => _easySO, DifficultyLevel.Hard => _hardSO, DifficultyLevel.SteelSoul => _steelSoulSO, _ => _normalSO, }; _onDifficultyChanged.Raise(level.ToString()); } } public enum DifficultyLevel { Easy, Normal, Hard, SteelSoul } } ``` ### 系统集成方式 各系统在 `OnEnable` / 初始化时读取 `DifficultyManager.CurrentScaler`,并监听 `OnDifficultyChanged` 动态刷新: ```csharp // 示例:PlayerStats 集成难度缩放 void OnEnable() { _onDifficultyChanged.OnEventRaised += OnDifficultyChanged; ApplyDifficultyScaling(_difficultyManager.CurrentScaler); } void ApplyDifficultyScaling(DifficultyScalerSO scaler) { MaxHP = Mathf.RoundToInt(_statsSO.BaseMaxHP * scaler.PlayerMaxHPMultiplier); // 无敌帧缩放由 BeginInvincibility 方法内部乘以 scaler.InvincibilityFrameScale } ``` --- ## 5. 钢铁之魂模式特殊规则 ### 死亡处理 ``` HP 归零 │ ▼ PlayerStats.TakeDamage() │ ├─ Normal/Hard: 触发 OnPlayerDied → 读档 → 复活 │ └─ SteelSoul: 触发 OnSteelSoulDeath │ ▼ GameManager 监听 OnSteelSoulDeath ├─ 播放专属死亡动画(黑屏 + 钢铁之魂 UI) ├─ DeleteSaveSlot(slotIndex) // 清除存档 └─ LoadScene("Scene_MainMenu") // 返回主菜单 ``` ### 钢铁之魂 UI 标识 - HUD 右上角显示「◆ 钢铁之魂」专属图标(金色) - 开始界面存档槽显示「◆」钢铁之魂标记 - 游戏中暂停菜单**隐藏**难度切换选项(一旦选择不可更改) ### 存档保护 ```csharp // SaveManager void DeleteSaveSlot(int slotIndex) { string path = GetSavePath(slotIndex); if (File.Exists(path)) { // 先备份(可选,用于后期统计或调试) File.Copy(path, path + ".dead", overwrite: true); File.Delete(path); } } ``` --- ## 6. 敌人 AI 行为差异 `DifficultyScalerSO.EnemyReactionTimeScale` 和 `EnemyAggressionLevel` 通过 **BehaviorDesigner 黑板变量** 注入敌人 AI: ```csharp // EnemyBase.Start() 或 OnDifficultyChanged 时注入 public void ApplyDifficultyToBehaviorTree(DifficultyScalerSO scaler) { var tree = GetComponent(); if (tree == null) return; tree.SetVariableValue("ReactionTimeScale", scaler.EnemyReactionTimeScale); tree.SetVariableValue("AggressionLevel", scaler.EnemyAggressionLevel); } ``` ### AI 行为差异示例 | 行为 | Easy | Normal | Hard | |------|------|--------|------| | 攻击前摇时长 | × 1.4(更慢)| × 1.0 | × 0.7(更快)| | Boss 攻击连段数 | 减少 1 段 | 正常 | 增加 1 段 | | 弹射物速度 | × 0.8 | × 1.0 | × 1.2 | | 追击范围 | × 0.8 | × 1.0 | × 1.3 | | Boss 进入第 2 阶段 HP 阈值 | 35% | 50% | 60% | --- ## 7. 与各系统的集成 ### PlayerStats ```csharp // 受击时乘以难度伤害系数 int scaledDamage = Mathf.RoundToInt(info.Damage * _difficultyScaler.EnemyDamageMultiplier); TakeDamage(scaledDamage); ``` ### EnemyStats(EnemyStatsSO) ```csharp // EnemyBase.Awake() 中应用难度缩放 int scaledHP = Mathf.RoundToInt(_statsSO.BaseHP * _scaler.EnemyHPMultiplier); int scaledDamage = Mathf.RoundToInt(_statsSO.BaseDamage * _scaler.EnemyDamageMultiplier); CurrentHP = MaxHP = scaledHP; _scaledAttackDamage = scaledDamage; ``` ### ShopController ```csharp public int GetActualPrice(ShopItemSO item) { float scale = _difficultyManager.CurrentScaler.ShopPriceMultiplier; return Mathf.RoundToInt(item.basePrice * scale); } ``` ### Collectible(Geo 掉落) ```csharp // Collectible.OnPickup() 中 int scaledGeo = Mathf.RoundToInt(_value * _difficultyManager.CurrentScaler.GeoDropMultiplier); _stats.AddGeo(scaledGeo); ``` --- ## 8. SaveData 集成 难度设置保存在 `SaveData` 中,确保读档后恢复正确难度: ```json { "difficulty": "Normal", "isSteelSoul": false } ``` ```csharp // DifficultyManager public DifficultyLevel GetDifficultyFromSave(SaveData data) { if (data.IsSteelSoul) return DifficultyLevel.SteelSoul; return Enum.Parse(data.Difficulty); } ``` --- ## 9. UI 选择界面 ### 新游戏难度选择界面 玩家在主菜单点击「新游戏」后,进入难度选择界面(单独 Panel): ``` ┌─────────────────────────────────────────┐ │ 选择难度 │ │ │ │ ○ 协助模式 「从容体验完整故事」 │ │ ● 标准模式 「推荐,经典挑战」(默认)│ │ ○ 穿刺模式 「更高难度,更多奖励」 │ │ ○ 钢铁之魂 「仅一命,死亡即清档」 │ │ │ │ [开始游戏] │ └─────────────────────────────────────────┘ ``` - `SteelSoul` 选项有警示图标和确认二次弹窗 - 所有选项均有简短描述说明(见上) ### 进行中难度切换(Easy/Normal/Hard) 在 **暂停菜单 → 设置 → 游戏** 中提供难度下拉框,`SteelSoul` 进行中隐藏此选项。 --- ## 10. 编辑器友好设计 ### DifficultyScalerSO 自定义 Inspector ```csharp [CustomEditor(typeof(DifficultyScalerSO))] public class DifficultyScalerSOEditor : Editor { public override VisualElement CreateInspectorGUI() { var root = new VisualElement(); InspectorElement.FillDefaultInspector(root, serializedObject, this); // 数值摘要 var summary = new Foldout { text = "数值摘要" }; var scaler = (DifficultyScalerSO)target; summary.Add(new Label($"玩家最大 HP: × {scaler.PlayerMaxHPMultiplier:F2}")); summary.Add(new Label($"敌人伤害: × {scaler.EnemyDamageMultiplier:F2}")); summary.Add(new Label($"Boss 伤害: × {scaler.BossDamageMultiplier:F2}")); summary.Add(new Label($"敌人 AI 反应: × {scaler.EnemyReactionTimeScale:F2}(数越低越快)")); summary.Add(new Label($"死亡规则: {(scaler.InstantDeathOnZeroHP ? "⚠️ 钢铁之魂:清档" : scaler.GeoPenaltyOnDeath ? "掉落 Geo" : "无惩罚")}")); root.Add(summary); return root; } } ```