路线阶段:Unity 入门实战第 13 章。
本章目标:把 Boss 战从“单状态高数值怪”升级为“阶段化机制战”,并保证逻辑与演出可维护。
学习目标
完成本章后,你应该能做到:
- 设计 Boss 阶段模型(阈值、行为包、技能池、表现参数)。
- 用配置驱动阶段切换,避免硬编码 if/else。
- 将阶段切换与技能系统、行为树、音画演出联动。
- 支持阶段重置与回放一致性验证。
背景问题
很多项目里的 Boss 逻辑:
if (hp < maxHp * 0.5f)
{
speed = 2.0f;
damage = 200;
}
问题:
- 阶段行为混在一个脚本里,难维护。
- 新增阶段要改很多旧代码。
- 演出触发点和战斗规则容易错位。
- 回放时很难还原“阶段切换瞬间发生了什么”。
阶段配置模型
[Serializable]
public sealed class BossPhaseConfig
{
public int PhaseId;
public float HpThreshold; // 0~1,血量比例 <= 阈值进入
public string BehaviorTreeKey;
public List<int> EnabledSkillIds;
public float MoveSpeedScale;
public float DamageScale;
public string EnterTimelineKey;
public string EnterBgmLayer;
}
[Serializable]
public sealed class BossConfig
{
public int BossId;
public string Name;
public List<BossPhaseConfig> Phases;
}
运行时数据
public sealed class BossRuntime
{
public readonly Actor Actor;
public readonly BossConfig Config;
public int CurrentPhaseIndex;
public bool IsTransitioning;
public BossRuntime(Actor actor, BossConfig config)
{
Actor = actor;
Config = config;
CurrentPhaseIndex = 0;
IsTransitioning = false;
}
}
阶段控制器
public sealed class BossPhaseController : IUpdatable
{
public int Order { get { return 270; } }
private readonly BossRuntime _boss;
private readonly BossSkillBinder _skillBinder;
private readonly BossBehaviorBinder _behaviorBinder;
private readonly BossPresentationBinder _presentationBinder;
private readonly EventBus _eventBus;
public BossPhaseController(
BossRuntime boss,
BossSkillBinder skillBinder,
BossBehaviorBinder behaviorBinder,
BossPresentationBinder presentationBinder,
EventBus eventBus)
{
_boss = boss;
_skillBinder = skillBinder;
_behaviorBinder = behaviorBinder;
_presentationBinder = presentationBinder;
_eventBus = eventBus;
}
public void Tick(float dt, float unscaledDt)
{
if (_boss.IsTransitioning)
{
return;
}
var hpRate = _boss.Actor.MaxHp <= 0f ? 0f : (_boss.Actor.Hp / _boss.Actor.MaxHp);
var nextIndex = ResolvePhaseIndex(hpRate);
if (nextIndex == _boss.CurrentPhaseIndex)
{
return;
}
SwitchPhase(nextIndex, "hp_threshold");
}
private int ResolvePhaseIndex(float hpRate)
{
var phases = _boss.Config.Phases;
// 默认按阈值从高到低排序
for (var i = phases.Count - 1; i >= 0; i--)
{
if (hpRate <= phases[i].HpThreshold)
{
return i;
}
}
return 0;
}
private void SwitchPhase(int index, string reason)
{
if (index < 0 || index >= _boss.Config.Phases.Count)
{
return;
}
_boss.IsTransitioning = true;
var from = _boss.Config.Phases[_boss.CurrentPhaseIndex];
var to = _boss.Config.Phases[index];
_boss.CurrentPhaseIndex = index;
_skillBinder.ApplyPhase(to);
_behaviorBinder.ApplyPhase(to);
_presentationBinder.ApplyPhase(to);
_eventBus.Publish("BossPhaseChanged", new BossPhasePayload(from.PhaseId, to.PhaseId, reason));
FoundationLog.Info("Boss", "phase_change from=" + from.PhaseId + " to=" + to.PhaseId + " reason=" + reason);
CoroutineRunner.Instance.Delay(0.35f, delegate
{
_boss.IsTransitioning = false;
});
}
}
public struct BossPhasePayload
{
public int From;
public int To;
public string Reason;
public BossPhasePayload(int from, int to, string reason)
{
From = from;
To = to;
Reason = reason;
}
}
技能绑定
public sealed class BossSkillBinder
{
private readonly SkillComponent _skills;
public BossSkillBinder(SkillComponent skills)
{
_skills = skills;
}
public void ApplyPhase(BossPhaseConfig phase)
{
_skills.DisableAll();
for (var i = 0; i < phase.EnabledSkillIds.Count; i++)
{
_skills.EnableSkill(phase.EnabledSkillIds[i]);
}
}
}
行为树绑定
public sealed class BossBehaviorBinder
{
private readonly EnemyAiBrain _brain;
private readonly BehaviorTreeLibrary _library;
public BossBehaviorBinder(EnemyAiBrain brain, BehaviorTreeLibrary library)
{
_brain = brain;
_library = library;
}
public void ApplyPhase(BossPhaseConfig phase)
{
var tree = _library.Build(phase.BehaviorTreeKey);
_brain.SetTree(tree);
}
}
演出绑定
public sealed class BossPresentationBinder
{
private readonly BossTimelinePlayer _timeline;
private readonly BgmLayerController _bgm;
private readonly BossView _view;
public BossPresentationBinder(BossTimelinePlayer timeline, BgmLayerController bgm, BossView view)
{
_timeline = timeline;
_bgm = bgm;
_view = view;
}
public void ApplyPhase(BossPhaseConfig phase)
{
if (!string.IsNullOrEmpty(phase.EnterTimelineKey))
{
_timeline.Play(phase.EnterTimelineKey);
}
if (!string.IsNullOrEmpty(phase.EnterBgmLayer))
{
_bgm.SwitchLayer(phase.EnterBgmLayer);
}
_view.SetEnrageLevel(phase.PhaseId);
}
}
阶段切换期间的战斗规则
建议默认策略:
- 切换期间短暂霸体(0.2~0.5s)。
- 切换期间暂停普通技能释放,防止动作打断。
- 切换结束后重置当前技能冷却下限(避免瞬发连招失控)。
与回放系统联动
阶段切换必须记录:
{
"frame": 3250,
"event": "BossPhaseChanged",
"from": 1,
"to": 2,
"reason": "hp_threshold"
}
这样线上问题可快速确认:是阶段逻辑异常,还是技能/演出同步异常。
验收清单
- Boss 可按血量阈值稳定进入下一阶段。
- 阶段切换后技能组和行为树立即生效。
- 演出触发与战斗规则同步,无“先播后变/先变后播”错位。
- 回放同一战斗数据时阶段切换帧一致。
常见坑
坑 1:阶段阈值无序
如果阈值未排序,可能跳错阶段。加载配置时要做校验。
坑 2:切阶段不冻结输入与行为
会在过场里继续攻击,破坏演出。需要短暂门控。
坑 3:阶段切换多次触发
同一阈值反复进入。需比较当前阶段索引并去重。
本月作业
实现一个三阶段 Boss:
- P1:近战冲锋 + 扇形斩。
- P2:召唤杂兵 + 范围落雷。
- P3:狂暴追击 + 全屏预警技。
并输出每次阶段切换的日志、回放事件与演出截图。
下一章进入 Unity 入门实战 14:伤害数字、命中停顿与镜头震动(战斗手感打磨)。