【OpenHarmony/HarmonyOS】游戏难度不是简单加血波次成长、敌人数值与奖励曲线设计很多游戏的“简单、普通、困难”只是把敌人生命值乘一个系数但这种做法容易把挑战变成纯粹拖时。迷宫坦克更适合从敌人数量、反应速度、追击倾向、开火频率、地图规模和奖励密度共同构建难度。本篇结合 ArkTS 项目中的真实波次系统说明参数怎样从 ArkUI 难度卡片一路传入 GameEngine 和 EnemyTank并分析当前曲线的优点、突变点与可测试化方向。一、难度参数经过了哪些层项目的难度不是只在页面换一段文案而是沿调用链传递flowchart LRA[ArkUI 难度卡片]--B[startGame]B-- C[GameEngine.initGame]C -- D[保存 difficulty]D -- E[计算敌人数量]D -- F[计算晶石数量]D -- G[EnemyTank.setDifficulty]G -- H[AI 反应/追击/射击参数]E --I[波次胜利后 currentWave1]I-- E层次表达形式备注UI 卡片easy、normal、hard面向玩家的三档名称游戏入口easy、normal、nightmarehard 在动作中映射为 nightmareGameEngine联合类型字段决定数量、奖励并传给 AIEnemyTank同一联合类型决定反应速度和开火参数UI 使用hard内部使用nightmare并非功能错误但命名不一致会增加维护成本。资源键、组件类型和领域类型最好统一否则统计、存档或网络协议中容易出现第四种“hard”。二、入口层难度选择还带有晶石成本难度页的三张卡片分别消耗 1、2、3 枚晶石this.DifficultyCard( $r(app.string.diff_easy), $r(app.string.diff_easy_desc),#4CAF50,easy,1, () this.startGame(pve,easy) );this.DifficultyCard( $r(app.string.diff_hard), $r(app.string.diff_hard_desc),#F44336,hard,3, () this.startGame(pve,nightmare) );点击时先调用UpgradeManager.spendCoins(cost)成功后才进入游戏。这里的成本更像“入场费”不是永久解锁。它会改变难度选择行为新用户余额不足时连简单模式都可能无法开始而晶石又主要来自游戏内。如果产品意图是通过其他模式积累后挑战 PvE这个闭环可以成立如果 PvE 是主入口至少应该让简单模式免费或首次挑战免票。技术文章不能只谈 AI 参数还要看到入口经济对实际难度可达性的影响。三、波次状态如何推进 currentWave从 1 开始。PvE 中敌人数组清空后不立即重建而是进入过渡先等待爆炸/胜利动画通知 UI 显示区域完成遮罩再等待一段时间开始下一波。privatestartWaveTransition(): void {this.isWaveTransitioning true; setTimeout(() {if(this.gameState !playing)return;this.onWaveComplete?.(this.currentWave); setTimeout(() {if(this.gameState playing) {this.startNextWave();this.isWaveTransitioning false; } },2500); },1500); }privatestartNextWave(): void {this.currentWave;this.isRoundOver false;this.resetGameForWave(); }isWaveTransitioning防止敌人为空的多帧期间重复安排计时器。两个延时阶段分别承担世界反馈和 UI 黑屏过渡状态检查则防止玩家已经离开游戏后仍重建下一波。当前计时器句柄没有保存无法在页面销毁时主动取消虽然回调内检查了gameState仍会有闭包存活到超时。更完整的生命周期治理应保存句柄在停止游戏时清理。四、地图规模曲线前期增长后期封顶普通 PvE 的地图倍率公式是constsizeMultiplier 0.6Math.min(2.2, (this.currentWave-1) *0.25);constcols Math.max(20,Math.floor(this.screenWidth* sizeMultiplier / cellSize ) );第 1 波倍率 0.6之后每波增加 0.25增加部分最多为 2.2所以最终倍率封顶 2.8。实际列数和行数还受最小值 20 限制。波次理论倍率特征10.60紧凑开局但可能被最小 20 格覆盖20.85路径开始拉长31.10接近屏幕尺寸51.60探索成本明显增加92.60接近上限10 及以后2.80地图规模封顶这条曲线只受波次影响不受 Easy/Normal/Nightmare 直接影响。因此高难度主要通过敌人和 AI 增压而不是让迷宫更大。地图变大也并不一定等于更难空间更宽可能给玩家更多躲避机会路径更长却增加遭遇和探索成本需要用实际完成时间验证。限时模式固定使用 0.8 倍小地图解谜模式重新生成约 1.0 倍地图。它们不沿用 PvE 波次曲线体现了模式优先于通用难度的规则。五、敌人数量曲线三档差异最直接PvE 的敌人数由难度和波次共同决定let enemyCount 2;if(this.difficulty easy) { enemyCount 1 (this.currentWave -1); }elseif(this.difficulty nightmare) { enemyCount 3 (this.currentWave -1) *2; }else{ enemyCount 2 (this.currentWave -1); } enemyCount Math.min(40, enemyCount);换成更直观的公式Easy第 N 波为N个Normal第 N 波为N 1个Nightmare第 N 波为2N 1个所有难度最终不超过 40 个。波次EasyNormalNightmare11233347556111010112120202140封顶Nightmare 的斜率是另外两档的两倍因此差距会随波次扩大而不是固定多两只敌人。这种设计能让高手模式在后期明显分化但也可能出现性能压力更多 AI 意味着更多 A*、碰撞、子弹和粒子而不仅是战斗更难。spawnEnemies()每波最多尝试 100 次还要求出生点距玩家至少 200 像素。障碍密集时实际生成数可能小于公式值。调试信息显示的是目标enemyCount不是成功spawned因此诊断难度曲线时应同时记录实际数量。六、AI 难度由反应、倾向和射击共同组成 每个敌人创建后都会接收当前难度constenemy newEnemyTank( x, y,enemy_${spawned},B); enemy.setDifficulty(this.difficulty);this.enemies.push(enemy);在 AI 逻辑中难度影响转向/决策间隔和直接追击概率参数EasyNormalNightmare最小反应间隔1.0s0.5s0.2s最大反应间隔2.5s1.5s0.8s直接追击概率0.400.600.85if(this.difficulty easy) {minReaction1.0;maxReaction2.5;directChance0.4; }elseif (this.difficulty nightmare) {minReaction0.2;maxReaction0.8;directChance0.85; }更短的反应间隔意味着 AI 更频繁重新决定方向较高的追击概率意味着它更少随机游走。这类“行为质量”差异比单纯加速更自然简单敌人显得迟钝困难敌人显得果断。不过 A* 路径更新时间在当前代码中统一为 1 秒并没有按难度区分。近距离时三档都可能直接追击移动基础速度也来自同一个Tank。所以 Nightmare 的优势主要是决策与数量不是全属性碾压。七、射击参数如何形成压迫感AI 只有在与目标横向或纵向大致对齐时才考虑开火然后使用难度参数决定间隔与概率参数EasyNormalNightmare开火间隔范围2.04.0s1.02.0s0.51.5s每次判断开火概率0.500.800.95if(alignedX || alignedY) {if(Math.random() fireChance) {this.fireTimer minFire Math.random() * (maxFire - minFire); wantToFire true; } }数量、间隔和概率会相乘Nightmare 不只是单个敌人射得更快敌人数量也更多所以单位时间内潜在弹幕增长远大于线性。平衡时不能独立看一张参数表必须估算“同时可见敌人 × 对齐概率 × 单位时间发射次数”。还有一个细节如果没有对齐且未开火fireTimer仍可能保持小于等于零下一帧继续抽概率。游戏循环频率很高这会让实际触发概率接近必然。更严格的设计可以在一次判断失败后也设置较短的重试间隔避免帧率影响概率。八、奖励曲线高难度晶石更多但并非纯净倍率晶石基础数量随波次增加letbaseCount 5Math.floor(this.currentWave*2);if(this.difficultynightmare) { baseCount Math.floor(baseCount *2.0); }elseif(this.difficultynormal) { baseCount Math.floor(baseCount *1.5); }Easy 没有额外倍率Normal 为 1.5Nightmare 为 2.0。高难度风险更高、奖励更密集形成合理激励。但每次生成最多尝试 50 次实际数量可能被安全格搜索上限截断同时更大的地图也会稀释单位面积密度。维度EasyNormalNightmare初始敌人数少中多敌人增长斜率1/波1/波2/波AI 决策慢、随机多中等快、追击强AI 射击慢、概率低中等快、概率高晶石目标倍率×1.0×1.5×2.0地图倍率同波次一致同波次一致同波次一致只有当实际收集率也随难度保持合理高倍率才算有效奖励。敌人过多可能让玩家无法探索最终每局收益反而下降。因此应记录“生成数、收集数、结算数、局时长”而不仅看配置倍率。九、波次并不是所有模式都共享项目用一个 GameEngine 承载多种模式但波次语义不同PvE敌人清空后进入下一波地图和敌人数增长Time Attack固定小地图、45 秒基础时间、敌人与晶石持续补充不通过清场推进波次Puzzle没有敌人找到正确出口即结束MultiplayercurrentWave更接近回合团队团灭后计分并重置。因此通用字段currentWave在 UI 中有时显示“区域”有时显示“轮”。这是复用引擎的结果。分析或上报时应同时带上gameMode否则“第 3 波完成率”无法比较不同模式。十、难度参数散落带来的维护风险 ⚠️当前参数分布在Index.ets、GameEngine.ets和EnemyTank.ts入场成本在页面敌人数和晶石倍率在引擎反应与射击在 AI。调整一档难度需要跨文件修改容易漏项。项目还存在两种难度模型公共GameConfig.ts使用数字0/1/2实际游戏入口使用字符串联合类型。如果二者同时演进映射可能产生偏差。可以将规则集中为只读配置type Difficulty easy | normal | nightmare; interface DifficultyRule { entryCost:number;enemyBase:number;enemyPerWave:number;crystalMultiplier:number;reactionRange:[number, number];directChance:number;fireRange:[number, number];fireChance:number;} const RULES:RecordDifficulty, DifficultyRule {easy:{entryCost:1,enemyBase:1,enemyPerWave:1,crystalMultiplier:1,reactionRange:[1.0, 2.5],directChance:0.4,fireRange:[2.0, 4.0],fireChance:0.5}, // normal 与 nightmare 按同一结构配置 };这段是演进示例不是当前源码。集中配置的价值不只是少写if而是让 UI 展示、引擎执行、测试预期和数据分析共享同一权威来源。十一、如何用测试守住曲线数值系统最适合做纯函数测试。把敌人数计算提取后可以验证关键波次和封顶functionenemyCountFor(difficulty: Difficulty, wave:number):number{constsafeWave Math.max(1,Math.floor(wave));if(difficulty nightmare) {returnMath.min(40,3 (safeWave -1) *2); }if(difficulty easy) {returnMath.min(40, safeWave); }returnMath.min(40, safeWave 1); }建议覆盖测试断言wave1Easy/Normal/Nightmare 分别为 1/2/3wave0 或小数先规范化不产生负数或分数敌人极高波次始终不超过 40地图倍率从 0.6 增长并封顶 2.8射击范围最小值始终小于最大值概率字段始终位于 01生成拥挤地图实际数不足时可观测不死循环波次过渡中重复判定只安排一次下一波平衡测试不能只验证代码公式还要做真机数据回收。至少关注波次到达率、单波时长、死亡原因、平均同时在屏敌人数、帧时间 P95 和每分钟晶石收益。十二、平衡时应避免的三个误区1. 不要同时把所有参数翻倍敌人数量、射速、移动速度、生命和奖励一起翻倍会让玩家无法判断失败原因也难以回调。每档最好有明确人格Easy 给反应时间Nightmare 强化协同压迫而不是所有数字都更大。2. 不要忽视性能就是玩法上限40 个敌人的 A*、碰撞和子弹可能先触及设备性能而不是玩家能力。帧率下降还会改变输入手感和概率判断造成“高难度靠卡顿变难”。敌人数封顶必须与性能基线一起设计。3. 不要把目标数量当实际体验随机出生有尝试上限迷宫尺寸有最小值屏幕比例会影响行列数。日志和分析要记录最终生成结果不要只记录公式输入。十三、总结 ✨这套波次系统没有通过增加生命值制造拖延而是组合了地图成长、敌人数量、AI 反应、追击概率、射击节奏和晶石奖励。Easy、Normal、Nightmare 从第一波就有差异Nightmare 的数量斜率还会让差距随波次扩大。同时真实代码也提醒我们难度是跨层契约。UI 中的 hard 与引擎中的 nightmare 要统一入场成本会影响可达性随机生成上限会截断理论数量帧级概率可能受刷新率影响大量 AI 还会触及性能边界。把参数集中、公式纯函数化、记录实际生成与玩家行为才能让“难度”从散落常量变成可解释、可测试、可迭代的系统。推荐标签OpenHarmonyHarmonyOSArkTSArkUI游戏开发游戏数值AI状态机性能优化
【OpenHarmony/HarmonyOS】游戏难度不是简单加血:波次成长、敌人数值与奖励曲线设计
【OpenHarmony/HarmonyOS】游戏难度不是简单加血波次成长、敌人数值与奖励曲线设计很多游戏的“简单、普通、困难”只是把敌人生命值乘一个系数但这种做法容易把挑战变成纯粹拖时。迷宫坦克更适合从敌人数量、反应速度、追击倾向、开火频率、地图规模和奖励密度共同构建难度。本篇结合 ArkTS 项目中的真实波次系统说明参数怎样从 ArkUI 难度卡片一路传入 GameEngine 和 EnemyTank并分析当前曲线的优点、突变点与可测试化方向。一、难度参数经过了哪些层项目的难度不是只在页面换一段文案而是沿调用链传递flowchart LRA[ArkUI 难度卡片]--B[startGame]B-- C[GameEngine.initGame]C -- D[保存 difficulty]D -- E[计算敌人数量]D -- F[计算晶石数量]D -- G[EnemyTank.setDifficulty]G -- H[AI 反应/追击/射击参数]E --I[波次胜利后 currentWave1]I-- E层次表达形式备注UI 卡片easy、normal、hard面向玩家的三档名称游戏入口easy、normal、nightmarehard 在动作中映射为 nightmareGameEngine联合类型字段决定数量、奖励并传给 AIEnemyTank同一联合类型决定反应速度和开火参数UI 使用hard内部使用nightmare并非功能错误但命名不一致会增加维护成本。资源键、组件类型和领域类型最好统一否则统计、存档或网络协议中容易出现第四种“hard”。二、入口层难度选择还带有晶石成本难度页的三张卡片分别消耗 1、2、3 枚晶石this.DifficultyCard( $r(app.string.diff_easy), $r(app.string.diff_easy_desc),#4CAF50,easy,1, () this.startGame(pve,easy) );this.DifficultyCard( $r(app.string.diff_hard), $r(app.string.diff_hard_desc),#F44336,hard,3, () this.startGame(pve,nightmare) );点击时先调用UpgradeManager.spendCoins(cost)成功后才进入游戏。这里的成本更像“入场费”不是永久解锁。它会改变难度选择行为新用户余额不足时连简单模式都可能无法开始而晶石又主要来自游戏内。如果产品意图是通过其他模式积累后挑战 PvE这个闭环可以成立如果 PvE 是主入口至少应该让简单模式免费或首次挑战免票。技术文章不能只谈 AI 参数还要看到入口经济对实际难度可达性的影响。三、波次状态如何推进 currentWave从 1 开始。PvE 中敌人数组清空后不立即重建而是进入过渡先等待爆炸/胜利动画通知 UI 显示区域完成遮罩再等待一段时间开始下一波。privatestartWaveTransition(): void {this.isWaveTransitioning true; setTimeout(() {if(this.gameState !playing)return;this.onWaveComplete?.(this.currentWave); setTimeout(() {if(this.gameState playing) {this.startNextWave();this.isWaveTransitioning false; } },2500); },1500); }privatestartNextWave(): void {this.currentWave;this.isRoundOver false;this.resetGameForWave(); }isWaveTransitioning防止敌人为空的多帧期间重复安排计时器。两个延时阶段分别承担世界反馈和 UI 黑屏过渡状态检查则防止玩家已经离开游戏后仍重建下一波。当前计时器句柄没有保存无法在页面销毁时主动取消虽然回调内检查了gameState仍会有闭包存活到超时。更完整的生命周期治理应保存句柄在停止游戏时清理。四、地图规模曲线前期增长后期封顶普通 PvE 的地图倍率公式是constsizeMultiplier 0.6Math.min(2.2, (this.currentWave-1) *0.25);constcols Math.max(20,Math.floor(this.screenWidth* sizeMultiplier / cellSize ) );第 1 波倍率 0.6之后每波增加 0.25增加部分最多为 2.2所以最终倍率封顶 2.8。实际列数和行数还受最小值 20 限制。波次理论倍率特征10.60紧凑开局但可能被最小 20 格覆盖20.85路径开始拉长31.10接近屏幕尺寸51.60探索成本明显增加92.60接近上限10 及以后2.80地图规模封顶这条曲线只受波次影响不受 Easy/Normal/Nightmare 直接影响。因此高难度主要通过敌人和 AI 增压而不是让迷宫更大。地图变大也并不一定等于更难空间更宽可能给玩家更多躲避机会路径更长却增加遭遇和探索成本需要用实际完成时间验证。限时模式固定使用 0.8 倍小地图解谜模式重新生成约 1.0 倍地图。它们不沿用 PvE 波次曲线体现了模式优先于通用难度的规则。五、敌人数量曲线三档差异最直接PvE 的敌人数由难度和波次共同决定let enemyCount 2;if(this.difficulty easy) { enemyCount 1 (this.currentWave -1); }elseif(this.difficulty nightmare) { enemyCount 3 (this.currentWave -1) *2; }else{ enemyCount 2 (this.currentWave -1); } enemyCount Math.min(40, enemyCount);换成更直观的公式Easy第 N 波为N个Normal第 N 波为N 1个Nightmare第 N 波为2N 1个所有难度最终不超过 40 个。波次EasyNormalNightmare11233347556111010112120202140封顶Nightmare 的斜率是另外两档的两倍因此差距会随波次扩大而不是固定多两只敌人。这种设计能让高手模式在后期明显分化但也可能出现性能压力更多 AI 意味着更多 A*、碰撞、子弹和粒子而不仅是战斗更难。spawnEnemies()每波最多尝试 100 次还要求出生点距玩家至少 200 像素。障碍密集时实际生成数可能小于公式值。调试信息显示的是目标enemyCount不是成功spawned因此诊断难度曲线时应同时记录实际数量。六、AI 难度由反应、倾向和射击共同组成 每个敌人创建后都会接收当前难度constenemy newEnemyTank( x, y,enemy_${spawned},B); enemy.setDifficulty(this.difficulty);this.enemies.push(enemy);在 AI 逻辑中难度影响转向/决策间隔和直接追击概率参数EasyNormalNightmare最小反应间隔1.0s0.5s0.2s最大反应间隔2.5s1.5s0.8s直接追击概率0.400.600.85if(this.difficulty easy) {minReaction1.0;maxReaction2.5;directChance0.4; }elseif (this.difficulty nightmare) {minReaction0.2;maxReaction0.8;directChance0.85; }更短的反应间隔意味着 AI 更频繁重新决定方向较高的追击概率意味着它更少随机游走。这类“行为质量”差异比单纯加速更自然简单敌人显得迟钝困难敌人显得果断。不过 A* 路径更新时间在当前代码中统一为 1 秒并没有按难度区分。近距离时三档都可能直接追击移动基础速度也来自同一个Tank。所以 Nightmare 的优势主要是决策与数量不是全属性碾压。七、射击参数如何形成压迫感AI 只有在与目标横向或纵向大致对齐时才考虑开火然后使用难度参数决定间隔与概率参数EasyNormalNightmare开火间隔范围2.04.0s1.02.0s0.51.5s每次判断开火概率0.500.800.95if(alignedX || alignedY) {if(Math.random() fireChance) {this.fireTimer minFire Math.random() * (maxFire - minFire); wantToFire true; } }数量、间隔和概率会相乘Nightmare 不只是单个敌人射得更快敌人数量也更多所以单位时间内潜在弹幕增长远大于线性。平衡时不能独立看一张参数表必须估算“同时可见敌人 × 对齐概率 × 单位时间发射次数”。还有一个细节如果没有对齐且未开火fireTimer仍可能保持小于等于零下一帧继续抽概率。游戏循环频率很高这会让实际触发概率接近必然。更严格的设计可以在一次判断失败后也设置较短的重试间隔避免帧率影响概率。八、奖励曲线高难度晶石更多但并非纯净倍率晶石基础数量随波次增加letbaseCount 5Math.floor(this.currentWave*2);if(this.difficultynightmare) { baseCount Math.floor(baseCount *2.0); }elseif(this.difficultynormal) { baseCount Math.floor(baseCount *1.5); }Easy 没有额外倍率Normal 为 1.5Nightmare 为 2.0。高难度风险更高、奖励更密集形成合理激励。但每次生成最多尝试 50 次实际数量可能被安全格搜索上限截断同时更大的地图也会稀释单位面积密度。维度EasyNormalNightmare初始敌人数少中多敌人增长斜率1/波1/波2/波AI 决策慢、随机多中等快、追击强AI 射击慢、概率低中等快、概率高晶石目标倍率×1.0×1.5×2.0地图倍率同波次一致同波次一致同波次一致只有当实际收集率也随难度保持合理高倍率才算有效奖励。敌人过多可能让玩家无法探索最终每局收益反而下降。因此应记录“生成数、收集数、结算数、局时长”而不仅看配置倍率。九、波次并不是所有模式都共享项目用一个 GameEngine 承载多种模式但波次语义不同PvE敌人清空后进入下一波地图和敌人数增长Time Attack固定小地图、45 秒基础时间、敌人与晶石持续补充不通过清场推进波次Puzzle没有敌人找到正确出口即结束MultiplayercurrentWave更接近回合团队团灭后计分并重置。因此通用字段currentWave在 UI 中有时显示“区域”有时显示“轮”。这是复用引擎的结果。分析或上报时应同时带上gameMode否则“第 3 波完成率”无法比较不同模式。十、难度参数散落带来的维护风险 ⚠️当前参数分布在Index.ets、GameEngine.ets和EnemyTank.ts入场成本在页面敌人数和晶石倍率在引擎反应与射击在 AI。调整一档难度需要跨文件修改容易漏项。项目还存在两种难度模型公共GameConfig.ts使用数字0/1/2实际游戏入口使用字符串联合类型。如果二者同时演进映射可能产生偏差。可以将规则集中为只读配置type Difficulty easy | normal | nightmare; interface DifficultyRule { entryCost:number;enemyBase:number;enemyPerWave:number;crystalMultiplier:number;reactionRange:[number, number];directChance:number;fireRange:[number, number];fireChance:number;} const RULES:RecordDifficulty, DifficultyRule {easy:{entryCost:1,enemyBase:1,enemyPerWave:1,crystalMultiplier:1,reactionRange:[1.0, 2.5],directChance:0.4,fireRange:[2.0, 4.0],fireChance:0.5}, // normal 与 nightmare 按同一结构配置 };这段是演进示例不是当前源码。集中配置的价值不只是少写if而是让 UI 展示、引擎执行、测试预期和数据分析共享同一权威来源。十一、如何用测试守住曲线数值系统最适合做纯函数测试。把敌人数计算提取后可以验证关键波次和封顶functionenemyCountFor(difficulty: Difficulty, wave:number):number{constsafeWave Math.max(1,Math.floor(wave));if(difficulty nightmare) {returnMath.min(40,3 (safeWave -1) *2); }if(difficulty easy) {returnMath.min(40, safeWave); }returnMath.min(40, safeWave 1); }建议覆盖测试断言wave1Easy/Normal/Nightmare 分别为 1/2/3wave0 或小数先规范化不产生负数或分数敌人极高波次始终不超过 40地图倍率从 0.6 增长并封顶 2.8射击范围最小值始终小于最大值概率字段始终位于 01生成拥挤地图实际数不足时可观测不死循环波次过渡中重复判定只安排一次下一波平衡测试不能只验证代码公式还要做真机数据回收。至少关注波次到达率、单波时长、死亡原因、平均同时在屏敌人数、帧时间 P95 和每分钟晶石收益。十二、平衡时应避免的三个误区1. 不要同时把所有参数翻倍敌人数量、射速、移动速度、生命和奖励一起翻倍会让玩家无法判断失败原因也难以回调。每档最好有明确人格Easy 给反应时间Nightmare 强化协同压迫而不是所有数字都更大。2. 不要忽视性能就是玩法上限40 个敌人的 A*、碰撞和子弹可能先触及设备性能而不是玩家能力。帧率下降还会改变输入手感和概率判断造成“高难度靠卡顿变难”。敌人数封顶必须与性能基线一起设计。3. 不要把目标数量当实际体验随机出生有尝试上限迷宫尺寸有最小值屏幕比例会影响行列数。日志和分析要记录最终生成结果不要只记录公式输入。十三、总结 ✨这套波次系统没有通过增加生命值制造拖延而是组合了地图成长、敌人数量、AI 反应、追击概率、射击节奏和晶石奖励。Easy、Normal、Nightmare 从第一波就有差异Nightmare 的数量斜率还会让差距随波次扩大。同时真实代码也提醒我们难度是跨层契约。UI 中的 hard 与引擎中的 nightmare 要统一入场成本会影响可达性随机生成上限会截断理论数量帧级概率可能受刷新率影响大量 AI 还会触及性能边界。把参数集中、公式纯函数化、记录实际生成与玩家行为才能让“难度”从散落常量变成可解释、可测试、可迭代的系统。推荐标签OpenHarmonyHarmonyOSArkTSArkUI游戏开发游戏数值AI状态机性能优化