7. Boss战
2026/4/14大约 7 分钟
7. 雷霆战机——Boss战
7.1 Boss战设计原则
Boss是每个关卡的高潮,一个好的Boss应该让玩家感到"好难但好爽"。设计Boss时需要遵循几个原则:
| 原则 | 说明 |
|---|---|
| 阶段递进 | 血量降低后攻击变强,让战斗有节奏感 |
| 弹幕公平 | 再密集的弹幕也要有安全区域 |
| 弱点暴露 | 某些时段不攻击或露出弱点,给玩家攻击窗口 |
| 视觉反馈 | Boss受伤、切换阶段时有明显提示 |
| 奖励丰厚 | 击败Boss获得大量分数和道具 |
7.2 Boss血条系统
Boss需要一个醒目的血条,显示在屏幕顶部。
C
using Godot;
/// <summary>
/// Boss血条UI
/// </summary>
public partial class BossHealthBar : Control
{
private ProgressBar _healthBar;
private Label _bossNameLabel;
private Label _hpLabel;
private AnimationPlayer _animPlayer;
private int _maxHP = 100;
private int _currentHP = 100;
public override void _Ready()
{
_healthBar = GetNode<ProgressBar>("HealthBar");
_bossNameLabel = GetNode<Label>("BossName");
_hpLabel = GetNode<Label>("HPLabel");
_animPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
Visible = false;
}
/// <summary>
/// 初始化Boss血条
/// </summary>
public void Initialize(string bossName, int maxHP)
{
_bossNameLabel.Text = bossName;
_maxHP = maxHP;
_currentHP = maxHP;
_healthBar.MaxValue = maxHP;
_healthBar.Value = maxHP;
_hpLabel.Text = $"{maxHP} / {maxHP}";
// 入场动画
Visible = true;
_animPlayer.Play("show");
}
/// <summary>
/// 更新血条
/// </summary>
public void UpdateHP(int currentHP)
{
_currentHP = currentHP;
_healthBar.Value = currentHP;
_hpLabel.Text = $"{currentHP} / {_maxHP}";
// 血量低于30%时血条变红
float ratio = (float)currentHP / _maxHP;
if (ratio < 0.3f)
{
_healthBar.SelfModulate = Colors.Red;
}
else if (ratio < 0.6f)
{
_healthBar.SelfModulate = Colors.Yellow;
}
}
/// <summary>
/// 隐藏血条
/// </summary>
public void HideBar()
{
_animPlayer.Play("hide");
var timer = GetTree().CreateTimer(0.5f);
timer.Timeout += () => Visible = false;
}
}GDScript
extends Control
## Boss血条UI
@onready var _health_bar = $HealthBar
@onready var _boss_name_label = $BossName
@onready var _hp_label = $HPLabel
@onready var _anim_player = $AnimationPlayer
var _max_hp: int = 100
var _current_hp: int = 100
func _ready() -> void:
visible = false
## 初始化Boss血条
func initialize(boss_name: String, max_hp: int) -> void:
_boss_name_label.text = boss_name
_max_hp = max_hp
_current_hp = max_hp
_health_bar.max_value = max_hp
_health_bar.value = max_hp
_hp_label.text = "%d / %d" % [max_hp, max_hp]
visible = true
_anim_player.play("show")
## 更新血条
func update_hp(current_hp: int) -> void:
_current_hp = current_hp
_health_bar.value = current_hp
_hp_label.text = "%d / %d" % [_current_hp, _max_hp]
var ratio = float(current_hp) / float(_max_hp)
if ratio < 0.3:
_health_bar.self_modulate = Color.RED
elif ratio < 0.6:
_health_bar.self_modulate = Color.YELLOW
## 隐藏血条
func hide_bar() -> void:
_anim_player.play("hide")
var timer = get_tree().create_timer(0.5)
await timer.timeout
visible = false7.3 Boss核心逻辑
Boss继承自Enemy基类,但增加了多阶段攻击和更复杂的行为。
C
using Godot;
using System.Collections.Generic;
/// <summary>
/// Boss——多阶段Boss
/// </summary>
public partial class Boss : Enemy
{
// ===== Boss参数 =====
[Export] public string BossName { get; set; } = "Boss";
[Export] public int BossMaxHP { get; set; } = 200;
// ===== 阶段系统 =====
private int _currentPhase = 1;
private int _maxPhases = 3;
private float[] _phaseThresholds = { 1.0f, 0.6f, 0.3f };
// ===== 攻击计时器 =====
private float _attackTimer = 0f;
private float _attackInterval = 1.5f;
private int _attackPatternIndex = 0;
// ===== 移动 =====
private float _moveTimer = 0f;
private Vector2 _targetPosition;
// ===== 状态 =====
private bool _isEntering = true;
private float _enterTimer = 0f;
private bool _isVulnerable = true;
// ===== 引用 =====
private BossHealthBar _healthBar;
private Node2D _bulletContainer;
private Area2D _player;
public override void _Ready()
{
MaxHP = BossMaxHP;
ScoreValue = 5000;
DropChance = 1.0f; // Boss必掉道具
MoveSpeed = 60f;
base._Ready();
_bulletContainer = GetParent()
?.GetParent()
?.GetNode<Node2D>("GameArea/EnemyBullets");
_player = GetNodeOrNull<Area2D>(
"/root/Main/GameArea/Player");
_healthBar = GetNodeOrNull<BossHealthBar>(
"/root/Main/HUD/BossHealthBar");
// 初始位置:屏幕上方中间
Position = new Vector2(
GameConfig.ScreenWidth / 2f, -80f);
_targetPosition = new Vector2(
GameConfig.ScreenWidth / 2f, 80f);
}
public override void _Process(double delta)
{
if (_isEntering)
{
HandleEntry((float)delta);
return;
}
if (!_alive) return;
HandleMovement((float)delta);
HandleAttack((float)delta);
}
/// <summary>
/// Boss入场动画
/// </summary>
private void HandleEntry(float delta)
{
_enterTimer += delta;
// 缓慢移动到目标位置
Position = Position.Lerp(
_targetPosition, 2f * delta);
if (_enterTimer >= GameConfig.BossEntryDuration)
{
_isEntering = false;
_healthBar?.Initialize(BossName, BossMaxHP);
}
}
/// <summary>
/// Boss移动——左右缓慢移动
/// </summary>
private void HandleMovement(float delta)
{
_moveTimer += delta;
float margin = 60f;
float minX = margin;
float maxX = GameConfig.ScreenWidth - margin;
// 阶段越高移动越快
float speed = 40f + _currentPhase * 20f;
Position += new Vector2(
Mathf.Sin(_moveTimer * speed * 0.02f) * speed,
0
) * delta;
// 限制范围
Position = new Vector2(
Mathf.Clamp(Position.X, minX, maxX),
Position.Y);
}
/// <summary>
/// Boss攻击——根据阶段选择不同的弹幕模式
/// </summary>
private void HandleAttack(float delta)
{
_attackTimer += delta;
float interval = Mathf.Max(0.3f,
_attackInterval - _currentPhase * 0.3f);
if (_attackTimer >= interval)
{
_attackTimer = 0f;
ExecuteAttackPattern();
}
}
/// <summary>
/// 执行攻击模式
/// </summary>
private void ExecuteAttackPattern()
{
if (_player == null) return;
switch (_currentPhase)
{
case 1:
Phase1Attack();
break;
case 2:
Phase2Attack();
break;
case 3:
Phase3Attack();
break;
}
_attackPatternIndex++;
}
/// <summary>
/// 阶段1攻击——扇形散射
/// </summary>
private void Phase1Attack()
{
int bulletCount = 5;
float spreadAngle = Mathf.Pi / 3f; // 60度扇形
for (int i = 0; i < bulletCount; i++)
{
float angle = -spreadAngle / 2f
+ spreadAngle * i / (bulletCount - 1)
+ Mathf.Pi / 2f; // 向下
var dir = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
FireBullet(dir, GameConfig.EnemyBulletSpeed,
EnemyBullet.BulletType.Straight);
}
}
/// <summary>
/// 阶段2攻击——圆形弹幕+散射
/// </summary>
private void Phase2Attack()
{
if (_attackPatternIndex % 2 == 0)
{
// 圆形弹幕
int bulletCount = 12;
for (int i = 0; i < bulletCount; i++)
{
float angle = (Mathf.Tau * i) / bulletCount
+ _attackPatternIndex * 0.3f;
var bullet = GD.Load<PackedScene>(
"res://scenes/bullets/enemy_bullet.tscn")
.Instantiate<EnemyBullet>();
bullet.InitCircular(GlobalPosition, angle,
GameConfig.EnemyBulletSpeed * 0.8f);
_bulletContainer.AddChild(bullet);
}
}
else
{
// 瞄准弹
FireBullet(Vector2.Down, GameConfig.EnemyBulletSpeed * 1.2f,
EnemyBullet.BulletType.Aimed);
}
}
/// <summary>
/// 阶段3攻击——疯狂模式
/// </summary>
private void Phase3Attack()
{
// 旋转弹幕
int bulletCount = 16;
float offset = _attackPatternIndex * 0.2f;
for (int i = 0; i < bulletCount; i++)
{
float angle = (Mathf.Tau * i) / bulletCount + offset;
var bullet = GD.Load<PackedScene>(
"res://scenes/bullets/enemy_bullet.tscn")
.Instantiate<EnemyBullet>();
bullet.InitCircular(GlobalPosition, angle,
GameConfig.EnemyBulletSpeed * 0.7f);
_bulletContainer.AddChild(bullet);
}
// 追踪弹(每隔3次攻击发射一颗)
if (_attackPatternIndex % 3 == 0)
{
var homingBullet = GD.Load<PackedScene>(
"res://scenes/bullets/enemy_bullet.tscn")
.Instantiate<EnemyBullet>();
homingBullet.InitHoming(GlobalPosition, _player,
GameConfig.EnemyBulletSpeed * 0.5f, 1.0f);
_bulletContainer.AddChild(homingBullet);
}
}
/// <summary>
/// 重写受伤处理——加入阶段切换逻辑
/// </summary>
public override void TakeDamage(int damage)
{
if (!_isVulnerable) return;
base.TakeDamage(damage);
// 更新血条
_healthBar?.UpdateHP(_currentHP);
// 检查是否进入下一阶段
float ratio = (float)_currentHP / BossMaxHP;
int newPhase = 1;
for (int i = _phaseThresholds.Length - 1; i >= 0; i--)
{
if (ratio <= _phaseThresholds[i])
{
newPhase = i + 1;
break;
}
}
if (newPhase > _currentPhase)
{
_currentPhase = newPhase;
OnPhaseChange();
}
}
/// <summary>
/// 阶段切换
/// </summary>
private void OnPhaseChange()
{
GD.Print($"Boss进入阶段 {_currentPhase}");
// 短暂无敌+特效
_isVulnerable = false;
var tween = CreateTween();
// 闪烁效果
for (int i = 0; i < 6; i++)
{
tween.TweenProperty(this, "modulate",
new Color(1, 1, 1, 0.3f), 0.1f);
tween.TweenProperty(this, "modulate",
Colors.White, 0.1f);
}
tween.TweenCallback(Callable.From(() =>
{
_isVulnerable = true;
}));
}
/// <summary>
/// 重写死亡处理
/// </summary>
protected override void Die()
{
_healthBar?.HideBar();
_gameManager?.AddScore(ScoreValue);
// 大爆炸特效
for (int i = 0; i < 5; i++)
{
var timer = GetTree().CreateTimer(i * 0.15f);
timer.Timeout += () =>
{
SpawnExplosion();
Position += new Vector2(
_random.Next(-30, 30),
_random.Next(-30, 30));
};
}
// 屏幕震动
ScreenShake.Shake(
GameConfig.ScreenShakeOnBossDeath, 1.0f);
// 掉落多个道具
var powerUpMgr = GetNodeOrNull<PowerUpManager>(
"/root/Main/PowerUpManager");
if (powerUpMgr != null)
{
powerUpMgr.ForceDrop(GlobalPosition,
PowerUp.PowerUpType.Power);
powerUpMgr.ForceDrop(
GlobalPosition + new Vector2(-30, 0),
PowerUp.PowerUpType.Bomb);
powerUpMgr.ForceDrop(
GlobalPosition + new Vector2(30, 0),
PowerUp.PowerUpType.Life);
}
// 延迟后销毁
var destroyTimer = GetTree().CreateTimer(1.0f);
destroyTimer.Timeout += () => QueueFree();
_alive = false;
Visible = false;
}
private Random _random = new();
}GDScript
extends Enemy
## Boss——多阶段Boss
@export var boss_name: String = "Boss"
@export var boss_max_hp: int = 200
# 阶段系统
var _current_phase: int = 1
var _max_phases: int = 3
var _phase_thresholds = [1.0, 0.6, 0.3]
# 攻击计时器
var _attack_timer: float = 0.0
var _attack_interval: float = 1.5
var _attack_pattern_index: int = 0
# 移动
var _move_timer: float = 0.0
var _target_position: Vector2
# 状态
var _is_entering: bool = true
var _enter_timer: float = 0.0
var _is_vulnerable: bool = true
# 引用
var _health_bar: BossHealthBar
var _bullet_container: Node2D
var _player: Area2D
func _ready() -> void:
max_hp = boss_max_hp
score_value = 5000
drop_chance = 1.0
move_speed = 60.0
super._ready()
_bullet_container = get_parent().get_parent().get_node("GameArea/EnemyBullets")
_player = get_node_or_null("/root/Main/GameArea/Player")
_health_bar = get_node_or_null("/root/Main/HUD/BossHealthBar")
position = Vector2(GameConfig.SCREEN_WIDTH / 2.0, -80.0)
_target_position = Vector2(GameConfig.SCREEN_WIDTH / 2.0, 80.0)
func _process(delta: float) -> void:
if _is_entering:
_handle_entry(delta)
return
if not _alive:
return
_handle_movement(delta)
_handle_attack(delta)
## Boss入场
func _handle_entry(delta: float) -> void:
_enter_timer += delta
position = position.lerp(_target_position, 2.0 * delta)
if _enter_timer >= GameConfig.BOSS_ENTRY_DURATION:
_is_entering = false
if _health_bar:
_health_bar.initialize(boss_name, boss_max_hp)
## Boss移动
func _handle_movement(delta: float) -> void:
_move_timer += delta
var speed = 40.0 + _current_phase * 20.0
position += Vector2(sin(_move_timer * speed * 0.02) * speed, 0) * delta
position.x = clampf(position.x, 60.0, GameConfig.SCREEN_WIDTH - 60.0)
## Boss攻击
func _handle_attack(delta: float) -> void:
_attack_timer += delta
var interval = maxf(0.3, _attack_interval - _current_phase * 0.3)
if _attack_timer >= interval:
_attack_timer = 0.0
_execute_attack_pattern()
func _execute_attack_pattern() -> void:
if _player == null:
return
match _current_phase:
1:
_phase1_attack()
2:
_phase2_attack()
3:
_phase3_attack()
_attack_pattern_index += 1
## 阶段1——扇形散射
func _phase1_attack() -> void:
var bullet_count = 5
var spread_angle = PI / 3.0
for i in range(bullet_count):
var angle = -spread_angle / 2.0 + spread_angle * i / (bullet_count - 1) + PI / 2.0
var dir = Vector2(cos(angle), sin(angle))
fire_bullet(dir, GameConfig.ENEMY_BULLET_SPEED, EnemyBullet.BulletType.STRAIGHT)
## 阶段2——圆形+瞄准
func _phase2_attack() -> void:
if _attack_pattern_index % 2 == 0:
var bullet_count = 12
for i in range(bullet_count):
var angle = TAU * i / bullet_count + _attack_pattern_index * 0.3
var bullet = load("res://scenes/bullets/enemy_bullet.tscn").instantiate()
bullet.init_circular(global_position, angle, GameConfig.ENEMY_BULLET_SPEED * 0.8)
_bullet_container.add_child(bullet)
else:
fire_bullet(Vector2.DOWN, GameConfig.ENEMY_BULLET_SPEED * 1.2, EnemyBullet.BulletType.AIMED)
## 阶段3——疯狂模式
func _phase3_attack() -> void:
var bullet_count = 16
var offset = _attack_pattern_index * 0.2
for i in range(bullet_count):
var angle = TAU * i / bullet_count + offset
var bullet = load("res://scenes/bullets/enemy_bullet.tscn").instantiate()
bullet.init_circular(global_position, angle, GameConfig.ENEMY_BULLET_SPEED * 0.7)
_bullet_container.add_child(bullet)
if _attack_pattern_index % 3 == 0:
var homing = load("res://scenes/bullets/enemy_bullet.tscn").instantiate()
homing.init_homing(global_position, _player, GameConfig.ENEMY_BULLET_SPEED * 0.5, 1.0)
_bullet_container.add_child(homing)
## 重写受伤
func take_damage(damage: int) -> void:
if not _is_vulnerable:
return
super.take_damage(damage)
if _health_bar:
_health_bar.update_hp(_current_hp)
var ratio = float(_current_hp) / float(boss_max_hp)
var new_phase = 1
for i in range(_phase_thresholds.size() - 1, -1, -1):
if ratio <= _phase_thresholds[i]:
new_phase = i + 1
break
if new_phase > _current_phase:
_current_phase = new_phase
_on_phase_change()
## 阶段切换
func _on_phase_change() -> void:
print("Boss进入阶段 ", _current_phase)
_is_vulnerable = false
var tween = create_tween()
for i in range(6):
tween.tween_property(self, "modulate", Color(1, 1, 1, 0.3), 0.1)
tween.tween_property(self, "modulate", Color.WHITE, 0.1)
tween.tween_callback(func(): _is_vulnerable = true)7.4 Boss攻击模式总结
| 阶段 | 血量范围 | 攻击模式 | 移动速度 |
|---|---|---|---|
| 1 | 100%-60% | 5发扇形散射 | 慢 |
| 2 | 60%-30% | 圆形弹幕+瞄准弹交替 | 中 |
| 3 | 30%-0% | 16发旋转弹幕+追踪弹 | 快 |
7.5 本章小结
| 组件 | 说明 |
|---|---|
| Boss血条 | 顶部显示,带入场/退场动画 |
| 多阶段系统 | 血量降低自动切换阶段 |
| 弹幕模式 | 散射→圆形→旋转+追踪,递进加强 |
| 阶段切换 | 闪烁特效+短暂无敌 |
| Boss死亡 | 多次爆炸+屏幕震动+大量道具掉落 |
下一章我们将搭建游戏UI界面。
