5. 回合制战斗
2026/4/14大约 11 分钟
5. 伏魔记——回合制战斗
5.1 什么是回合制战斗?
想象两个人下棋——你走一步,对方走一步,轮流进行,不能同时动手。回合制战斗就是这样的:我方角色和敌方角色轮流行动,谁速度快谁先动手,一个回合结束后轮到下一个。
经典的回合制RPG(比如《仙剑奇侠传》《勇者斗恶龙》)都采用这种模式。它的好处是:
- 玩家有充足时间思考策略
- 不需要快速反应,操作门槛低
- 可以设计丰富的技能和战术组合
5.2 战斗流程总览
┌───────────────┐
│ 进入战斗场景 │ ← 播放遇敌动画
└───────┬───────┘
↓
┌───────────────┐
│ 生成战斗单位 │ ← 我方队伍 + 敌方阵容
└───────┬───────┘
↓
┌───────────────┐
│ 排列行动顺序 │ ← 根据速度值排序
└───────┬───────┘
↓
┌───────────────┐ ┌───────────────┐
│ 当前单位行动 │ ←→ │ 等待下一个单位 │
└───────┬───────┘ └───────────────┘
↓
┌───────────────┐
│ 选择行动 │ ← 攻击/技能/物品/防御
└───────┬───────┘
↓
┌───────────────┐
│ 执行行动 │ ← 计算伤害,播放动画
└───────┬───────┘
↓
┌──────────────────────────┐
│ 检查胜负 │
│ 敌方全倒 → 战斗胜利 │
│ 我方全倒 → 战斗失败 │
│ 都还有人 → 下一个单位行动 │
└──────────────────────────┘5.3 战斗单位
每个参与战斗的角色(无论是玩家还是敌人)都是一个战斗单位(BattleUnit)。
C
using Godot;
using System.Collections.Generic;
/// <summary>
/// 战斗单位——战斗场景中的角色实体
/// </summary>
public partial class BattleUnit : Node2D
{
// ===== 角色数据 =====
public CharacterStats Stats { get; private set; }
public string UnitName { get; private set; }
public bool IsPlayer { get; private set; } // 是否是玩家方
public bool IsDefending { get; private set; } // 本回合是否选择了防御
// ===== UI引用 =====
private TextureRect _sprite;
private ProgressBar _hpBar;
private ProgressBar _mpBar;
private Label _nameLabel;
private Label _hpLabel;
// ===== 信号 =====
[Signal] public delegate void UnitDiedEventHandler(BattleUnit unit);
[Signal] public delegate void ActionCompletedEventHandler();
public override void _Ready()
{
_sprite = GetNode<TextureRect>("Sprite");
_hpBar = GetNode<ProgressBar>("HPBar");
_mpBar = GetNode<ProgressBar>("MPBar");
_nameLabel = GetNode<Label>("NameLabel");
_hpLabel = GetNode<Label>("HPLabel");
}
/// <summary>
/// 初始化战斗单位
/// </summary>
public void Initialize(CharacterStats stats, bool isPlayer)
{
Stats = stats;
UnitName = stats.Name;
IsPlayer = isPlayer;
IsDefending = false;
_nameLabel.Text = UnitName;
UpdateUI();
}
/// <summary>
/// 更新血条和状态显示
/// </summary>
public void UpdateUI()
{
float hpPercent = (float)Stats.CurrentHp / Stats.MaxHp * 100;
float mpPercent = (float)Stats.CurrentMp / Stats.MaxMp * 100;
_hpBar.Value = hpPercent;
_mpBar.Value = mpPercent;
_hpLabel.Text = $"{Stats.CurrentHp}/{Stats.MaxHp}";
}
/// <summary>
/// 受到伤害
/// </summary>
public void TakeDamage(int damage)
{
var newStats = Stats.TakeDamage(damage);
Stats = newStats;
UpdateUI();
// 播放受伤动画
_play_hurt_animation();
// 检查是否死亡
if (!Stats.IsAlive)
{
_play_death_animation();
EmitSignal(SignalName.UnitDied, this);
}
}
/// <summary>
/// 恢复HP
/// </summary>
public void Heal(int amount)
{
var newStats = Stats.Heal(amount);
Stats = newStats;
UpdateUI();
_play_heal_animation();
}
/// <summary>
/// 开始新的回合(重置防御状态)
/// </summary>
public void StartNewTurn()
{
IsDefending = false;
}
// ===== 动画 =====
private void _play_hurt_animation()
{
// 闪烁效果
var tween = CreateTween();
tween.TweenProperty(_sprite, "modulate:a", 0.3f, 0.1f);
tween.TweenProperty(_sprite, "modulate:a", 1.0f, 0.1f);
tween.TweenProperty(_sprite, "modulate:a", 0.3f, 0.1f);
tween.TweenProperty(_sprite, "modulate:a", 1.0f, 0.1f);
}
private void _play_death_animation()
{
// 淡出效果
var tween = CreateTween();
tween.TweenProperty(this, "modulate:a", 0.0f, 0.5f);
}
private void _play_heal_animation()
{
// 绿色闪烁
var tween = CreateTween();
tween.TweenProperty(_sprite, "modulate",
new Color(0.5f, 1.0f, 0.5f), 0.2f);
tween.TweenProperty(_sprite, "modulate",
Colors.White, 0.3f);
}
}GDScript
extends Node2D
## 战斗单位——战斗场景中的角色实体
# ===== 角色数据 =====
var stats: CharacterStats
var unit_name: String
var is_player: bool ## 是否是玩家方
var is_defending: bool = false ## 本回合是否选择了防御
# ===== UI引用 =====
@onready var _sprite: TextureRect = $Sprite
@onready var _hp_bar: ProgressBar = $HPBar
@onready var _mp_bar: ProgressBar = $MPBar
@onready var _name_label: Label = $NameLabel
@onready var _hp_label: Label = $HPLabel
# ===== 信号 =====
signal unit_died(unit: BattleUnit)
signal action_completed
## 初始化战斗单位
func initialize(char_stats: CharacterStats, player: bool) -> void:
stats = char_stats
unit_name = char_stats.name
is_player = player
is_defending = false
_name_label.text = unit_name
update_ui()
## 更新血条和状态显示
func update_ui() -> void:
var hp_percent: float = float(stats.current_hp) / stats.max_hp * 100
var mp_percent: float = float(stats.current_mp) / stats.max_mp * 100
_hp_bar.value = hp_percent
_mp_bar.value = mp_percent
_hp_label.text = "%d/%d" % [stats.current_hp, stats.max_hp]
## 受到伤害
func take_damage(damage: int) -> void:
stats = stats.take_damage(damage)
update_ui()
# 播放受伤动画
_play_hurt_animation()
# 检查是否死亡
if not stats.is_alive():
_play_death_animation()
unit_died.emit(self)
## 恢复HP
func heal(amount: int) -> void:
stats = stats.heal(amount)
update_ui()
_play_heal_animation()
## 开始新的回合(重置防御状态)
func start_new_turn() -> void:
is_defending = false
# ===== 动画 =====
func _play_hurt_animation() -> void:
var tween = create_tween()
tween.tween_property(_sprite, "modulate:a", 0.3, 0.1)
tween.tween_property(_sprite, "modulate:a", 1.0, 0.1)
tween.tween_property(_sprite, "modulate:a", 0.3, 0.1)
tween.tween_property(_sprite, "modulate:a", 1.0, 0.1)
func _play_death_animation() -> void:
var tween = create_tween()
tween.tween_property(self, "modulate:a", 0.0, 0.5)
func _play_heal_animation() -> void:
var tween = create_tween()
tween.tween_property(_sprite, "modulate", Color(0.5, 1.0, 0.5), 0.2)
tween.tween_property(_sprite, "modulate", Color.WHITE, 0.3)5.4 战斗管理器
BattleManager 是战斗的"裁判",它控制整个战斗流程:谁先行动、什么时候轮到谁、战斗什么时候结束。
C
using Godot;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// 战斗管理器——回合制战斗的"裁判"
/// </summary>
public partial class BattleManager : Node
{
// 所有参战单位
private List<BattleUnit> _allUnits = new();
private List<BattleUnit> _playerUnits = new();
private List<BattleUnit> _enemyUnits = new();
// 行动队列(按速度排序)
private Queue<BattleUnit> _actionQueue = new();
// 当前行动单位
private BattleUnit _currentUnit;
// 战斗状态
private bool _battleActive = false;
private bool _waitingForPlayerInput = false;
// UI引用
private Control _actionMenu;
private Control _targetSelection;
private Control _battleResultPanel;
// 信号
[Signal] public delegate void BattleWonEventHandler();
[Signal] public delegate void BattleLostEventHandler();
public override void _Ready()
{
_actionMenu = GetNode<Control>("%ActionMenu");
_targetSelection = GetNode<Control>("%TargetSelection");
_battleResultPanel = GetNode<Control>("%BattleResultPanel");
_actionMenu.Visible = false;
_targetSelection.Visible = false;
_battleResultPanel.Visible = false;
}
/// <summary>
/// 初始化战斗——创建我方和敌方的战斗单位
/// </summary>
public void StartBattle(
List<CharacterStats> partyStats,
List<CharacterStats> enemyStats)
{
// 创建我方战斗单位
_playerUnits.Clear();
foreach (var stats in partyStats)
{
var unit = CreateBattleUnit(stats, true);
_playerUnits.Add(unit);
_allUnits.Add(unit);
}
// 创建敌方战斗单位
_enemyUnits.Clear();
foreach (var stats in enemyStats)
{
var unit = CreateBattleUnit(stats, false);
_enemyUnits.Add(unit);
_allUnits.Add(unit);
}
// 按速度排序,建立行动队列
BuildActionQueue();
_battleActive = true;
GameManager.Instance.GameState = RpgGameState.Battle;
// 开始第一个回合
NextTurn();
}
/// <summary>
/// 根据速度值排序建立行动队列
/// </summary>
private void BuildActionQueue()
{
var sorted = _allUnits
.Where(u => u.Stats.IsAlive)
.OrderByDescending(u => u.Stats.Speed)
.ToList();
_actionQueue = new Queue<BattleUnit>(sorted);
}
/// <summary>
/// 进入下一个单位的回合
/// </summary>
private void NextTurn()
{
// 跳过已死亡的单位
while (_actionQueue.Count > 0
&& !_actionQueue.Peek().Stats.IsAlive)
{
_actionQueue.Dequeue();
}
// 行动队列空了,重新排序开始新一轮
if (_actionQueue.Count == 0)
{
BuildActionQueue();
}
if (_actionQueue.Count == 0)
{
// 所有人都死了(理论上不会发生)
return;
}
_currentUnit = _actionQueue.Dequeue();
_currentUnit.StartNewTurn();
if (_currentUnit.IsPlayer)
{
// 玩家回合:显示行动菜单
ShowActionMenu();
}
else
{
// 敌人回合:由AI决定行动
EnemyAiTurn(_currentUnit);
}
}
/// <summary>
/// 显示行动菜单(攻击/技能/物品/防御)
/// </summary>
private void ShowActionMenu()
{
_waitingForPlayerInput = true;
_actionMenu.Visible = true;
}
/// <summary>
/// 玩家选择"攻击"
/// </summary>
public void OnAttackSelected()
{
_actionMenu.Visible = false;
// 让玩家选择攻击目标
ShowTargetSelection(
_enemyUnits.Where(u => u.Stats.IsAlive).ToList(),
ExecuteAttack);
}
/// <summary>
/// 玩家选择"防御"
/// </summary>
public void OnDefendSelected()
{
_actionMenu.Visible = false;
_currentUnit.IsDefending = true;
// 显示"防御"提示
GD.Print($"{_currentUnit.UnitName} 选择了防御!");
// 防御完成后进入下一个回合
var timer = GetTree().CreateTimer(0.5);
timer.Timeout += NextTurn;
}
/// <summary>
/// 执行攻击
/// </summary>
private void ExecuteAttack(BattleUnit target)
{
_waitingForPlayerInput = false;
int damage = BattleCalculator.CalculateDamage(
_currentUnit.Stats, target.Stats);
// 如果目标在防御,伤害减半
if (target.IsDefending)
{
damage = Mathf.Max((int)(damage * RpgConstants.DefendDamageReduction), 1);
}
target.TakeDamage(damage);
GD.Print($"{_currentUnit.UnitName} 攻击 {target.UnitName},造成 {damage} 点伤害");
// 延迟后检查战斗状态
var timer = GetTree().CreateTimer(0.8);
timer.Timeout += () => CheckBattleState();
}
/// <summary>
/// 敌人AI回合
/// </summary>
private void EnemyAiTurn(BattleUnit enemy)
{
// 简单AI:随机攻击一个存活的玩家单位
var alivePlayers = _playerUnits
.Where(u => u.Stats.IsAlive)
.ToList();
if (alivePlayers.Count == 0) return;
int targetIndex = GD.Randi() % alivePlayers.Count;
var target = alivePlayers[targetIndex];
int damage = BattleCalculator.CalculateDamage(
enemy.Stats, target.Stats);
if (target.IsDefending)
{
damage = Mathf.Max((int)(damage * RpgConstants.DefendDamageReduction), 1);
}
target.TakeDamage(damage);
GD.Print($"{enemy.UnitName} 攻击 {target.UnitName},造成 {damage} 点伤害");
var timer = GetTree().CreateTimer(0.8);
timer.Timeout += () => CheckBattleState();
}
/// <summary>
/// 检查战斗是否结束
/// </summary>
private void CheckBattleState()
{
bool enemiesAlive = _enemyUnits.Any(u => u.Stats.IsAlive);
bool playersAlive = _playerUnits.Any(u => u.Stats.IsAlive);
if (!enemiesAlive)
{
// 战斗胜利
BattleVictory();
}
else if (!playersAlive)
{
// 战斗失败
BattleDefeat();
}
else
{
// 战斗继续
NextTurn();
}
}
/// <summary>
/// 战斗胜利处理
/// </summary>
private void BattleVictory()
{
_battleActive = false;
GD.Print("战斗胜利!");
// 计算经验值和金币奖励
int totalExp = _enemyUnits.Sum(u => u.Stats.Level * 10 + 5);
int totalGold = _enemyUnits.Sum(u => u.Stats.Level * 3 + 2);
GD.Print($"获得经验: {totalExp},获得金币: {totalGold}");
// 分配经验给存活的角色
foreach (var unit in _playerUnits.Where(u => u.Stats.IsAlive))
{
var (newStats, levels) = LevelSystem.AddExperience(
unit.Stats, totalExp);
unit.Stats = newStats;
if (levels > 0)
{
GD.Print($"{unit.UnitName} 升了 {levels} 级!");
}
}
// 发送胜利信号
EmitSignal(SignalName.BattleWon);
}
/// <summary>
/// 战斗失败处理
/// </summary>
private void BattleDefeat()
{
_battleActive = false;
GD.Print("战斗失败...");
EmitSignal(SignalName.BattleLost);
}
/// <summary>
/// 显示目标选择界面
/// </summary>
private void ShowTargetSelection(
List<BattleUnit> targets,
System.Action<BattleUnit> onSelected)
{
_targetSelection.Visible = true;
// 为每个目标创建选择按钮
foreach (var target in targets)
{
var button = new Button { Text = target.UnitName };
button.Pressed += () =>
{
_targetSelection.Visible = false;
onSelected(target);
};
_targetSelection.GetNode<VBoxContainer>("ButtonList")
.AddChild(button);
}
}
private BattleUnit CreateBattleUnit(
CharacterStats stats, bool isPlayer)
{
var scene = GD.Load<PackedScene>(
"res://scenes/battle/battle_unit.tscn");
var unit = scene.Instantiate<BattleUnit>();
unit.Initialize(stats, isPlayer);
return unit;
}
}GDScript
extends Node
## 战斗管理器——回合制战斗的"裁判"
# 所有参战单位
var _all_units: Array[BattleUnit] = []
var _player_units: Array[BattleUnit] = []
var _enemy_units: Array[BattleUnit] = []
# 行动队列
var _action_queue: Array[BattleUnit] = []
var _queue_index: int = 0
# 当前行动单位
var _current_unit: BattleUnit = null
# 战斗状态
var _battle_active: bool = false
var _waiting_for_player_input: bool = false
# UI引用
@onready var _action_menu: Control = %ActionMenu
@onready var _target_selection: Control = %TargetSelection
@onready var _battle_result_panel: Control = %BattleResultPanel
# 信号
signal battle_won
signal battle_lost
func _ready() -> void:
_action_menu.visible = false
_target_selection.visible = false
_battle_result_panel.visible = false
## 初始化战斗
func start_battle(party_stats: Array, enemy_stats: Array) -> void:
# 创建我方战斗单位
_player_units.clear()
for stats in party_stats:
var unit = _create_battle_unit(stats, true)
_player_units.append(unit)
_all_units.append(unit)
# 创建敌方战斗单位
_enemy_units.clear()
for stats in enemy_stats:
var unit = _create_battle_unit(stats, false)
_enemy_units.append(unit)
_all_units.append(unit)
# 按速度排序,建立行动队列
_build_action_queue()
_battle_active = true
GameManager.game_state = RpgGameState.BATTLE
# 开始第一个回合
_next_turn()
## 根据速度值排序建立行动队列
func _build_action_queue() -> void:
_action_queue.clear()
_queue_index = 0
var alive_units = _all_units.filter(
func(u): return u.stats.is_alive())
# 按速度降序排序
alive_units.sort_custom(
func(a, b): return a.stats.speed > b.stats.speed)
_action_queue = alive_units
## 进入下一个单位的回合
func _next_turn() -> void:
# 跳过已死亡的单位
while _queue_index < _action_queue.size():
if _action_queue[_queue_index].stats.is_alive():
break
_queue_index += 1
# 行动队列用完了,重新排序
if _queue_index >= _action_queue.size():
_build_action_queue()
if _queue_index >= _action_queue.size():
return
_current_unit = _action_queue[_queue_index]
_queue_index += 1
_current_unit.start_new_turn()
if _current_unit.is_player:
_show_action_menu()
else:
_enemy_ai_turn(_current_unit)
## 显示行动菜单
func _show_action_menu() -> void:
_waiting_for_player_input = true
_action_menu.visible = true
## 玩家选择"攻击"
func on_attack_selected() -> void:
_action_menu.visible = false
var alive_enemies = _enemy_units.filter(
func(u): return u.stats.is_alive())
_show_target_selection(alive_enemies, _execute_attack)
## 玩家选择"防御"
func on_defend_selected() -> void:
_action_menu.visible = false
_current_unit.is_defending = true
print(_current_unit.unit_name, " 选择了防御!")
await get_tree().create_timer(0.5).timeout
_next_turn()
## 执行攻击
func _execute_attack(target: BattleUnit) -> void:
_waiting_for_player_input = false
var damage: int = BattleCalculator.calculate_damage(
_current_unit.stats, target.stats)
# 如果目标在防御,伤害减半
if target.is_defending:
damage = maxf(int(damage * RpgConstants.DEFEND_DAMAGE_REDUCTION), 1)
target.take_damage(damage)
print(_current_unit.unit_name, " 攻击 ", target.unit_name,
",造成 ", damage, " 点伤害")
await get_tree().create_timer(0.8).timeout
_check_battle_state()
## 敌人AI回合
func _enemy_ai_turn(enemy: BattleUnit) -> void:
# 简单AI:随机攻击一个存活的玩家单位
var alive_players = _player_units.filter(
func(u): return u.stats.is_alive())
if alive_players.size() == 0:
return
var target_index: int = randi() % alive_players.size()
var target: BattleUnit = alive_players[target_index]
var damage: int = BattleCalculator.calculate_damage(
enemy.stats, target.stats)
if target.is_defending:
damage = maxf(int(damage * RpgConstants.DEFEND_DAMAGE_REDUCTION), 1)
target.take_damage(damage)
print(enemy.unit_name, " 攻击 ", target.unit_name,
",造成 ", damage, " 点伤害")
await get_tree().create_timer(0.8).timeout
_check_battle_state()
## 检查战斗是否结束
func _check_battle_state() -> void:
var enemies_alive = _enemy_units.any(
func(u): return u.stats.is_alive())
var players_alive = _player_units.any(
func(u): return u.stats.is_alive())
if not enemies_alive:
_battle_victory()
elif not players_alive:
_battle_defeat()
else:
_next_turn()
## 战斗胜利
func _battle_victory() -> void:
_battle_active = false
print("战斗胜利!")
# 计算经验值和金币奖励
var total_exp: int = 0
var total_gold: int = 0
for unit in _enemy_units:
total_exp += unit.stats.level * 10 + 5
total_gold += unit.stats.level * 3 + 2
print("获得经验: ", total_exp, ",获得金币: ", total_gold)
# 分配经验
for unit in _player_units:
if unit.stats.is_alive():
var result = LevelSystem.add_experience(unit.stats, total_exp)
unit.stats = result[0]
var levels = result[1]
if levels > 0:
print(unit.unit_name, " 升了 ", levels, " 级!")
battle_won.emit()
## 战斗失败
func _battle_defeat() -> void:
_battle_active = false
print("战斗失败...")
battle_lost.emit()
## 显示目标选择界面
func _show_target_selection(targets: Array, on_selected: Callable) -> void:
_target_selection.visible = true
var button_list = _target_selection.get_node("ButtonList")
# 清空旧按钮
for child in button_list.get_children():
child.queue_free()
for target in targets:
var button = Button.new()
button.text = target.unit_name
button.pressed.connect(func():
_target_selection.visible = false
on_selected.call(target)
)
button_list.add_child(button)
func _create_battle_unit(stats: CharacterStats, player: bool) -> BattleUnit:
var scene = load("res://scenes/battle/battle_unit.tscn")
var unit = scene.instantiate()
unit.initialize(stats, player)
return unit5.5 技能系统
技能是战斗中的"大招"——比普通攻击更强,但消耗MP。
| 技能属性 | 说明 |
|---|---|
| 技能名称 | 显示在技能菜单中的名字 |
| MP消耗 | 释放技能需要的法力值 |
| 技能威力 | 威力越高伤害越大 |
| 技能倍率 | 伤害 = (威力 x 攻击力 - 防御力) x 倍率 |
| 目标类型 | 单体 / 全体 / 自身 |
C
/// <summary>
/// 技能数据
/// </summary>
public class SkillData
{
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int MpCost { get; set; }
public int Power { get; set; }
public float Multiplier { get; set; }
public enum TargetType { SingleEnemy, AllEnemies, Self, SingleAlly }
public TargetType Target { get; set; }
}
/// <summary>
/// 预定义技能列表
/// </summary>
public static class SkillDatabase
{
public static readonly SkillData[] AllSkills = new[]
{
new SkillData
{
Id = "fireball",
Name = "火球术",
Description = "向敌人投掷一颗火球",
MpCost = 8,
Power = 20,
Multiplier = 1.5f,
Target = SkillData.TargetType.SingleEnemy
},
new SkillData
{
Id = "heal",
Name = "治愈术",
Description = "恢复一个队友的HP",
MpCost = 6,
Power = 40,
Multiplier = 1.0f,
Target = SkillData.TargetType.SingleAlly
},
new SkillData
{
Id = "thunder_storm",
Name = "雷霆万钧",
Description = "召唤雷电攻击所有敌人",
MpCost = 15,
Power = 25,
Multiplier = 1.3f,
Target = SkillData.TargetType.AllEnemies
},
new SkillData
{
Id = "iron_wall",
Name = "铁壁",
Description = "大幅提升自身防御力",
MpCost = 5,
Power = 10,
Multiplier = 1.0f,
Target = SkillData.TargetType.Self
}
};
public static SkillData FindById(string id)
{
foreach (var skill in AllSkills)
{
if (skill.Id == id) return skill;
}
return null;
}
}GDScript
## 技能数据
class_name SkillData
enum TargetType { SINGLE_ENEMY, ALL_ENEMIES, SELF, SINGLE_ALLY }
var id: String
var name: String
var description: String
var mp_cost: int
var power: int
var multiplier: float
var target: TargetType
## 预定义技能列表
class_name SkillDatabase
const ALL_SKILLS: Array[Dictionary] = [
{
"id": "fireball",
"name": "火球术",
"description": "向敌人投掷一颗火球",
"mp_cost": 8,
"power": 20,
"multiplier": 1.5,
"target": TargetType.SINGLE_ENEMY
},
{
"id": "heal",
"name": "治愈术",
"description": "恢复一个队友的HP",
"mp_cost": 6,
"power": 40,
"multiplier": 1.0,
"target": TargetType.SINGLE_ALLY
},
{
"id": "thunder_storm",
"name": "雷霆万钧",
"description": "召唤雷电攻击所有敌人",
"mp_cost": 15,
"power": 25,
"multiplier": 1.3,
"target": TargetType.ALL_ENEMIES
},
{
"id": "iron_wall",
"name": "铁壁",
"description": "大幅提升自身防御力",
"mp_cost": 5,
"power": 10,
"multiplier": 1.0,
"target": TargetType.SELF
}
]
static func find_by_id(skill_id: String) -> Dictionary:
for skill in ALL_SKILLS:
if skill.id == skill_id:
return skill
return {}5.6 战斗UI布局
┌──────────────────────────────────────────────────┐
│ [妖怪A] HP:████░░ 30/50 [妖怪B] HP:██████ 60/60│
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ (敌人精灵/动画区域) │ │
│ └────────────────────────────────────────────┘ │
│ │
│ ───────────── 行动顺序条 ────────────────────── │
│ 玩家 > 敌A > 敌B > 玩家2 │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ (玩家精灵/动画区域) │ │
│ └────────────────────────────────────────────┘ │
│ │
│ [主角] HP:████████ 80/80 [同伴] HP:████░░ 40/60│
│ │
│ ┌──────┬──────┬──────┬──────┐ │
│ │ 攻击 │ 技能 │ 物品 │ 防御 │ │
│ └──────┴──────┴──────┴──────┘ │
└──────────────────────────────────────────────────┘5.7 本章小结
| 知识点 | 说明 |
|---|---|
| 战斗单位 | BattleUnit封装角色属性、动画和UI显示 |
| 战斗管理器 | 控制行动队列、回合切换、胜负判定 |
| 伤害计算 | 攻击力-防御力,加随机波动,防御减半 |
| 技能系统 | MP消耗、技能威力、倍率、目标类型 |
| 敌人AI | 简单AI:随机攻击存活玩家单位 |
| 战斗结算 | 胜利获得经验和金币,失败返回城镇 |
下一章我们将实现物品与背包系统,让玩家能管理装备和使用道具。
