5. 自动战斗
2026/4/14大约 6 分钟
5. 咸鱼之王——自动战斗
5.1 什么是自动战斗?
想象你派了一队士兵去打仗。你不需要告诉他们"你先砍他,你再射他"——他们会自己判断该攻击谁、什么时候放技能。你只需要看着就行了,偶尔给他们升升级。
自动战斗就是放置游戏的核心战斗方式——玩家不需要操作,英雄们自己打。你唯一需要做的就是确保英雄够强。
自动战斗流程
┌──────────────────────────────────────────────────┐
│ │
│ 英雄自动寻敌 → 自动攻击 → 敌人受伤 → 敌人死亡 │
│ ↑ │ │
│ └──────────────────────────────────────┘ │
│ │
│ 敌人全部死亡 → 关卡通关 → 获得奖励 → 下一关 │
│ │
└──────────────────────────────────────────────────┘5.2 自动战斗管理器
C
using Godot;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// 自动战斗管理器——英雄自动打怪
/// </summary>
public partial class AutoBattleManager : Node
{
// 战斗状态
private bool _isBattleActive = false;
private StageData _currentStageData;
private int _currentEnemyHp;
private int _currentEnemyMaxHp;
// 攻击计时器
private Dictionary<string, float> _attackTimers = new();
// 信号
[Signal] public delegate void BattleWonEventHandler(int stageId, int goldReward);
[Signal] public delegate void BattleLostEventHandler(int stageId);
[Signal] public delegate void EnemyHpChangedEventHandler(int currentHp, int maxHp);
[Signal] public delegate void DamageDealtEventHandler(string heroName, int damage);
public bool IsBattleActive => _isBattleActive;
/// <summary>
/// 开始自动战斗
/// </summary>
public void StartBattle(int stageId)
{
_currentStageData = StageData.Generate(stageId);
_currentEnemyHp = _currentStageData.EnemyHp;
_currentEnemyMaxHp = _currentStageData.EnemyHp;
_isBattleActive = true;
// 初始化所有英雄的攻击计时器
_attackTimers.Clear();
var party = GetNode<HeroManager>("../HeroManager").Party;
foreach (var hero in party)
{
_attackTimers[hero.Id] = 0f;
}
GD.Print($"开始战斗: {_currentStageData.Name}");
GD.Print($"敌人HP: {_currentEnemyHp}");
}
public override void _Process(double delta)
{
if (!_isBattleActive) return;
var heroManager = GetNode<HeroManager>("../HeroManager");
var party = heroManager.Party.ToList();
if (party.Count == 0)
{
BattleLost();
return;
}
// 每个英雄独立计算攻击
foreach (var hero in party)
{
if (!_attackTimers.ContainsKey(hero.Id)) continue;
_attackTimers[hero.Id] += (float)delta;
// 攻击间隔到了就攻击
float interval = 1f / hero.AttackSpeed;
if (_attackTimers[hero.Id] >= interval)
{
_attackTimers[hero.Id] -= interval;
// 造成伤害(加一点随机波动)
int damage = CalculateDamage(hero.Attack);
_currentEnemyHp -= damage;
EmitSignal(SignalName.DamageDealt, hero.Name, damage);
// 检查敌人是否死亡
if (_currentEnemyHp <= 0)
{
OnEnemyDefeated();
return;
}
}
}
// 更新敌人血条
EmitSignal(SignalName.EnemyHpChanged,
Mathf.Max(_currentEnemyHp, 0), _currentEnemyMaxHp);
}
/// <summary>
/// 计算伤害(加随机波动)
/// </summary>
private int CalculateDamage(int attack)
{
float randomMult = GD.Randf() * 0.2f + 0.9f; // 0.9 ~ 1.1
return Mathf.Max((int)(attack * randomMult), 1);
}
/// <summary>
/// 敌人被击败
/// </summary>
private void OnEnemyDefeated()
{
int stageId = _currentStageData.StageId;
int goldReward = _currentStageData.GoldReward;
GD.Print($"击败 {_currentStageData.Name}!获得 {goldReward} 金币");
// 发放奖励
GameManager.Instance.AddGold(goldReward);
// 更新推图进度
GameManager.Instance.OnStageCleared(stageId);
// 通知UI
EmitSignal(SignalName.BattleWon, stageId, goldReward);
// 短暂延迟后开始下一关
var timer = GetTree().CreateTimer(1.0);
timer.Timeout += () =>
{
StartBattle(GameManager.Instance.CurrentStage);
};
}
/// <summary>
/// 战斗失败(英雄全倒——放置游戏中通常不会发生)
/// </summary>
private void BattleLost()
{
_isBattleActive = false;
GD.Print("战斗失败...");
EmitSignal(SignalName.BattleLost, _currentStageData.StageId);
// 放置游戏中通常不会真正失败
// 只是停在当前关卡,等玩家升级英雄后再来
}
/// <summary>
/// 计算编队能否打过当前关卡(预估)
/// </summary>
public bool CanBeatStage(int stageId)
{
var stage = StageData.Generate(stageId);
var heroManager = GetNode<HeroManager>("../HeroManager");
// 简单预估:编队DPS x 10秒 是否能杀死敌人
float dps = heroManager.GetPartyDps();
float timeToKill = stage.EnemyHp / Mathf.Max(dps, 0.1f);
// 10秒内能杀就算能打
return timeToKill <= 15f;
}
}GDScript
extends Node
## 自动战斗管理器——英雄自动打怪
var _is_battle_active: bool = false
var _current_stage_data: Dictionary = {}
var _current_enemy_hp: int = 0
var _current_enemy_max_hp: int = 0
var _attack_timers: Dictionary = {}
# 信号
signal battle_won(stage_id: int, gold_reward: int)
signal battle_lost(stage_id: int)
signal enemy_hp_changed(current_hp: int, max_hp: int)
signal damage_dealt(hero_name: String, damage: int)
var is_battle_active: bool:
get: return _is_battle_active
## 开始自动战斗
func start_battle(stage_id: int) -> void:
_current_stage_data = StageData.generate(stage_id)
_current_enemy_hp = _current_stage_data.enemy_hp
_current_enemy_max_hp = _current_stage_data.enemy_hp
_is_battle_active = true
# 初始化攻击计时器
_attack_timers.clear()
var hero_manager = get_node("../HeroManager")
for hero in hero_manager._party:
_attack_timers[hero.id] = 0.0
print("开始战斗: ", _current_stage_data.name)
print("敌人HP: ", _current_enemy_hp)
func _process(delta: float) -> void:
if not _is_battle_active:
return
var hero_manager = get_node("../HeroManager")
var party = hero_manager._party.duplicate()
if party.size() == 0:
_battle_lost()
return
# 每个英雄独立计算攻击
for hero in party:
if not _attack_timers.has(hero.id):
continue
_attack_timers[hero.id] += delta
var interval: float = 1.0 / hero.attack_speed
if _attack_timers[hero.id] >= interval:
_attack_timers[hero.id] -= interval
var damage: int = _calculate_damage(hero.attack)
_current_enemy_hp -= damage
damage_dealt.emit(hero.name, damage)
if _current_enemy_hp <= 0:
_on_enemy_defeated()
return
# 更新敌人血条
enemy_hp_changed.emit(maxf(_current_enemy_hp, 0), _current_enemy_max_hp)
## 计算伤害
func _calculate_damage(attack_power: int) -> int:
var random_mult: float = randf() * 0.2 + 0.9
return maxf(int(attack_power * random_mult), 1)
## 敌人被击败
func _on_enemy_defeated() -> void:
var stage_id: int = _current_stage_data.stage_id
var gold_reward: int = _current_stage_data.gold_reward
print("击败 ", _current_stage_data.name, "!获得 ", gold_reward, " 金币")
GameManager._gold += gold_reward
GameManager.on_stage_cleared(stage_id)
battle_won.emit(stage_id, gold_reward)
# 延迟后开始下一关
await get_tree().create_timer(1.0).timeout
start_battle(GameManager.current_stage)
## 战斗失败
func _battle_lost() -> void:
_is_battle_active = false
print("战斗失败...")
battle_lost.emit(_current_stage_data.stage_id)
## 预估能否打过
func can_beat_stage(stage_id: int) -> bool:
var stage = StageData.generate(stage_id)
var hero_manager = get_node("../HeroManager")
var dps: float = hero_manager.get_party_dps()
var time_to_kill: float = float(stage.enemy_hp) / maxf(dps, 0.1)
return time_to_kill <= 15.05.3 技能自动释放
英雄的技能不是随便放的——需要有优先级策略。
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 冷却好了就放 | 技能冷却完毕立即释放 | 普通关卡 |
| Boss关优先放 | Boss关保留技能到关键时刻 | Boss关 |
| 血量低时放 | 英雄血量低于50%时释放技能 | 高难度关卡 |
| 敌人血量高时放 | 敌人血量高于70%时释放AOE技能 | 多怪关卡 |
C
/// <summary>
/// 技能AI——决定何时释放技能
/// </summary>
public static class SkillAI
{
/// <summary>
/// 判断是否应该释放技能
/// </summary>
public static bool ShouldUseSkill(
HeroData hero,
float skillCooldownTimer,
float enemyHpPercent,
bool isBoss)
{
// 技能还在冷却中
if (skillCooldownTimer > 0) return false;
// Boss关:敌人血量高于50%时释放
if (isBoss && enemyHpPercent > 0.5f) return true;
// 普通关:技能好了就放
if (!isBoss) return true;
return false;
}
/// <summary>
/// 计算技能冷却时间(秒)
/// </summary>
public static float GetSkillCooldown(HeroRarity rarity)
{
return rarity switch
{
HeroRarity.Legend => 8f, // 传说:8秒
HeroRarity.Epic => 10f, // 史诗:10秒
HeroRarity.Rare => 12f, // 稀有:12秒
_ => 15f // 普通:15秒
};
}
}GDScript
## 技能AI——决定何时释放技能
class_name SkillAI
## 判断是否应该释放技能
static func should_use_skill(
hero: Dictionary,
skill_cd_timer: float,
enemy_hp_percent: float,
is_boss: bool
) -> bool:
# 技能还在冷却中
if skill_cd_timer > 0:
return false
# Boss关:敌人血量高于50%时释放
if is_boss and enemy_hp_percent > 0.5:
return true
# 普通关:技能好了就放
if not is_boss:
return true
return false
## 计算技能冷却时间
static func get_skill_cooldown(rarity: int) -> float:
match rarity:
HeroRarity.LEGEND:
return 8.0
HeroRarity.EPIC:
return 10.0
HeroRarity.RARE:
return 12.0
_:
return 15.05.4 战斗UI
自动战斗的UI需要显示战斗过程和关键信息:
┌──────────────────────────────────────┐
│ 第3章 第7关 │
│ │
│ ┌────────────────────────┐ │
│ │ 怪物头像/动画 │ │
│ │ 森林小鬼 │ │
│ │ │ │
│ │ HP: ████████░░ 240/400│ │
│ └────────────────────────┘ │
│ │
│ ─────── 我方编队 ──────── │
│ │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │英雄1│ │英雄2│ │英雄3│ │英雄4│ │
│ │LV10│ │LV8 │ │LV12│ │LV5 │ │
│ │████│ │████│ │████│ │░░░░│ │
│ └────┘ └────┘ └────┘ └────┘ │
│ │
│ 伤害飘字: -45 -32 -58 技能!-120 │
│ │
│ 编队DPS: 85.3 预计击杀: 4.7秒 │
└──────────────────────────────────────┘5.5 战斗胜负判定
| 情况 | 判定 | 处理 |
|---|---|---|
| 敌人HP归零 | 通关 | 获得金币奖励,自动进入下一关 |
| 英雄全倒 | 失败 | 停在当前关,提示升级英雄 |
| 30秒内未击杀 | 失败 | 说明DPS不够,需要提升英雄 |
5.6 本章小结
| 知识点 | 说明 |
|---|---|
| 自动战斗 | 英雄独立计时攻击,不需要玩家操作 |
| 伤害计算 | 攻击力 x 随机波动(0.9~1.1) |
| 技能AI | 根据关卡类型和敌人血量决定释放时机 |
| 战斗流程 | 敌人HP归零→获得奖励→自动下一关 |
| 胜负判定 | 通关自动推图,失败停在当前关 |
下一章我们将实现装备与强化系统。
