Files
zeling_v2/Docs/Design/29_DifficultyModesGuide.md
2026-05-08 11:04:00 +08:00

13 KiB
Raw Permalink Blame History

29 · 难度模式指南

命名空间 BaseGames.Core
所属文档集 ← 返回索引 · 总览
依赖 BaseGames.Core.Events · BaseGames.PlayerPlayerStats· BaseGames.EnemiesEnemyStats


目录

  1. 系统总览
  2. 难度等级定义
  3. DifficultyScalerSO — 难度缩放配置
  4. DifficultyManager — 运行时管理
  5. 钢铁之魂模式特殊规则
  6. 敌人 AI 行为差异
  7. 与各系统的集成
  8. SaveData 集成
  9. UI 选择界面
  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 — 难度缩放配置

[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 — 运行时管理

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);

        /// <summary>切换难度SteelSoul 不可降级)</summary>
        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 动态刷新:

// 示例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 右上角显示「◆ 钢铁之魂」专属图标(金色)
  • 开始界面存档槽显示「◆」钢铁之魂标记
  • 游戏中暂停菜单隐藏难度切换选项(一旦选择不可更改)

存档保护

// 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.EnemyReactionTimeScaleEnemyAggressionLevel 通过 BehaviorDesigner 黑板变量 注入敌人 AI

// EnemyBase.Start() 或 OnDifficultyChanged 时注入
public void ApplyDifficultyToBehaviorTree(DifficultyScalerSO scaler)
{
    var tree = GetComponent<BehaviorTree>();
    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

// 受击时乘以难度伤害系数
int scaledDamage = Mathf.RoundToInt(info.Damage * _difficultyScaler.EnemyDamageMultiplier);
TakeDamage(scaledDamage);

EnemyStatsEnemyStatsSO

// EnemyBase.Awake() 中应用难度缩放
int scaledHP     = Mathf.RoundToInt(_statsSO.BaseHP     * _scaler.EnemyHPMultiplier);
int scaledDamage = Mathf.RoundToInt(_statsSO.BaseDamage * _scaler.EnemyDamageMultiplier);
CurrentHP = MaxHP = scaledHP;
_scaledAttackDamage = scaledDamage;

ShopController

public int GetActualPrice(ShopItemSO item)
{
    float scale = _difficultyManager.CurrentScaler.ShopPriceMultiplier;
    return Mathf.RoundToInt(item.basePrice * scale);
}

CollectibleGeo 掉落)

// Collectible.OnPickup() 中
int scaledGeo = Mathf.RoundToInt(_value * _difficultyManager.CurrentScaler.GeoDropMultiplier);
_stats.AddGeo(scaledGeo);

8. SaveData 集成

难度设置保存在 SaveData 中,确保读档后恢复正确难度:

{
  "difficulty": "Normal",
  "isSteelSoul": false
}
// DifficultyManager
public DifficultyLevel GetDifficultyFromSave(SaveData data)
{
    if (data.IsSteelSoul) return DifficultyLevel.SteelSoul;
    return Enum.Parse<DifficultyLevel>(data.Difficulty);
}

9. UI 选择界面

新游戏难度选择界面

玩家在主菜单点击「新游戏」后,进入难度选择界面(单独 Panel

┌─────────────────────────────────────────┐
│           选择难度                       │
│                                         │
│  ○ 协助模式    「从容体验完整故事」      │
│  ● 标准模式    「推荐,经典挑战」(默认)│
│  ○ 穿刺模式    「更高难度,更多奖励」    │
│  ○ 钢铁之魂    「仅一命,死亡即清档」    │
│                                         │
│                    [开始游戏]            │
└─────────────────────────────────────────┘
  • SteelSoul 选项有警示图标和确认二次弹窗
  • 所有选项均有简短描述说明(见上)

进行中难度切换Easy/Normal/Hard

暂停菜单 → 设置 → 游戏 中提供难度下拉框,SteelSoul 进行中隐藏此选项。


10. 编辑器友好设计

DifficultyScalerSO 自定义 Inspector

[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;
    }
}