前言:
前面第四篇已经详细介绍了Ai逻辑引擎的方法,下面介绍下Ai引擎怎样设计的,方便后续做逻辑时可以更好理解游戏自动走.
代码如下:
TypeScript
import { Unit, Faction, Position } from '../common/GameModels';
import { GameEngine } from '../engine/GameEngine';
import { GamePhase } from '../common/GameConstants';
// AI引擎 - 控制敌方单位行动
export class AIEngine {
private gameEngine: GameEngine;
constructor(gameEngine: GameEngine) {
this.gameEngine = gameEngine;
}
// 执行AI回合
executeTurn(): Promise<void> {
return new Promise((resolve: () => void): void => {
const enemyUnits: Unit[] = this.gameEngine.getGameState().units
.filter((u: Unit): boolean => u.faction === Faction.ENEMY && u.isAlive && !u.hasActed);
if (enemyUnits.length === 0) {
resolve();
return;
}
// 逐个执行敌方单位行动
this.executeUnitActions(enemyUnits, 0, resolve);
});
}
// 逐个执行单位行动(带延迟以便观察)
private executeUnitActions(units: Unit[], index: number, resolve: () => void): void {
if (index >= units.length) {
resolve();
return;
}
const unit = units[index];
if (!unit.isAlive || unit.hasActed) {
this.executeUnitActions(units, index + 1, resolve);
return;
}
// 执行当前单位的行动
this.executeUnitAction(unit);
// 延迟后执行下一个单位
setTimeout((): void => {
this.executeUnitActions(units, index + 1, resolve);
}, 800);
}
// 执行单个单位的行动
private executeUnitAction(unit: Unit): void {
// 1. 尝试攻击(优先攻击)
const attackTarget = this.findBestAttackTarget(unit);
if (attackTarget) {
this.gameEngine.attackUnit(unit, attackTarget);
return;
}
// 2. 如果没有攻击目标,则移动
const moveTarget = this.findBestMoveTarget(unit);
if (moveTarget) {
this.gameEngine.moveUnit(unit, moveTarget);
// 移动后尝试攻击
const newAttackTarget = this.findBestAttackTarget(unit);
if (newAttackTarget) {
this.gameEngine.attackUnit(unit, newAttackTarget);
} else {
unit.hasActed = true;
}
} else {
// 无法移动,标记为已行动
unit.hasActed = true;
}
}
// 寻找最佳攻击目标
private findBestAttackTarget(unit: Unit): Position | null {
const attackablePositions: Position[] = this.gameEngine.getAttackablePositions(unit);
if (attackablePositions.length === 0) return null;
// 评估每个攻击目标的价值
let bestTarget: Position | null = null;
let bestValue = -1;
for (let i = 0; i < attackablePositions.length; i++) {
const pos = attackablePositions[i];
const targetUnit = this.gameEngine.getUnitAt(pos);
if (!targetUnit) continue;
// 计算目标价值(优先攻击血量低、攻击力高的单位)
const value = this.evaluateTargetValue(unit, targetUnit);
if (value > bestValue) {
bestValue = value;
bestTarget = pos;
}
}
return bestTarget;
}
// 评估攻击目标的价值
private evaluateTargetValue(attacker: Unit, target: Unit): number {
let value = 0;
// 1. 优先攻击将军(高价值目标)
if (target.type === 4) { // GENERAL
value += 100;
}
// 2. 优先攻击血量低的单位(可以击杀)
const hpPercent = target.hp / target.maxHp;
if (hpPercent < 0.3) {
value += 50; // 可能击杀
}
// 3. 考虑攻击力威胁
value += target.attack * 0.5;
// 4. 远程单位优先攻击近战单位
if (attacker.type === 1 || attacker.type === 3) { // ARCHER or MAGE
if (target.type === 0 || target.type === 2) { // WARRIOR or CAVALRY
value += 20;
}
}
// 5. 扣除距离惩罚(越近越好)
const distance = Math.abs(attacker.position.x - target.position.x) +
Math.abs(attacker.position.y - target.position.y);
value -= distance * 2;
return value;
}
// 寻找最佳移动目标
private findBestMoveTarget(unit: Unit): Position | null {
const movablePositions: Position[] = this.gameEngine.getMovablePositions(unit);
if (movablePositions.length === 0) return null;
// 找到最近的敌方单位
const enemyUnits: Unit[] = this.gameEngine.getGameState().units
.filter((u: Unit): boolean => u.faction === Faction.PLAYER && u.isAlive);
if (enemyUnits.length === 0) return null;
// 评估每个可移动位置的价值
let bestPosition: Position | null = null;
let bestValue = -1000;
for (let i = 0; i < movablePositions.length; i++) {
const pos = movablePositions[i];
const value = this.evaluateMovePosition(unit, pos, enemyUnits);
if (value > bestValue) {
bestValue = value;
bestPosition = pos;
}
}
return bestPosition;
}
// 评估移动位置的价值
private evaluateMovePosition(unit: Unit, pos: Position, enemyUnits: Unit[]): number {
let value = 0;
// 1. 接近敌方单位(移动范围允许的情况下)
let minDistanceToEnemy = 100;
for (let i = 0; i < enemyUnits.length; i++) {
const enemy = enemyUnits[i];
const distance = Math.abs(pos.x - enemy.position.x) +
Math.abs(pos.y - enemy.position.y);
minDistanceToEnemy = Math.min(minDistanceToEnemy, distance);
}
// 对于近战单位,想要接近敌人
if (unit.attackRange === 1) {
value += (10 - minDistanceToEnemy) * 10; // 越近越好
}
// 对于远程单位,保持距离但进入攻击范围
if (unit.attackRange > 1) {
if (minDistanceToEnemy <= unit.attackRange && minDistanceToEnemy > 1) {
value += 50; // 理想攻击位置
} else if (minDistanceToEnemy > unit.attackRange) {
value += (unit.attackRange - minDistanceToEnemy + 10) * 5; // 需要接近
}
}
// 2. 优先选择有利地形
const terrain = this.gameEngine.getTerrainAt(pos);
if (terrain === 1) { // FOREST
value += 10; // 防御加成
} else if (terrain === 2) { // MOUNTAIN
value += 15; // 更多防御加成
} else if (terrain === 3) { // WATER
value -= 100; // 避免水域
}
// 3. 避免过于冒险(低血量单位应该更保守)
const hpPercent = unit.hp / unit.maxHp;
if (hpPercent < 0.3) {
// 低血量,寻找防御位置
if (terrain === 1 || terrain === 2) {
value += 30;
}
// 远离敌方
value += minDistanceToEnemy * 3;
}
return value;
}
}
一、游戏 AI 的核心问题
游戏 AI 需要回答三个问题:
-
**做什么**:攻击?移动?等待?
-
**攻击谁**:选哪个目标最有价值?
-
**去哪里**:移动到哪个格子最有利?
下面AIEngine.ets` 用 **贪心策略(Greedy Algorithm)** 解答了这三个问题。
二、AI 决策流程全景
TypeScript
AI 回合开始
↓
获取所有存活且未行动的敌方单位
↓
逐个单位决策(间隔800ms)
↓
┌─ 当前单位
├─ 1. 能直接攻击?
│ 是 → 选最高价值目标 → 攻击 → 结束行动
│ 否 ↓
├─ 2. 能移动?
│ 是 → 选最佳移动位置 → 移动
│ ↓
│ 移动后能攻击?
│ 是 → 攻击 → 结束行动
│ 否 → 标记已行动
│ 否 → 标记已行动
AI 回合结束
三、异步回合执行:Promise + 递归
TypeScript
// 对外接口:执行整个 AI 回合
executeTurn(): Promise<void> {
return new Promise((resolve) => {
const enemyUnits = this.gameEngine.getGameState().units
.filter(u => u.faction === Faction.ENEMY && u.isAlive && !u.hasActed);
if (enemyUnits.length === 0) {
resolve(); // 没有可行动单位,立即结束
return;
}
this.executeUnitActions(enemyUnits, 0, resolve);
});
}
// 逐个单位行动,每次间隔800ms
private executeUnitActions(units: Unit[], index: number, resolve: () => void): void {
if (index >= units.length) {
resolve(); // 所有单位行动完毕,回调结束
return;
}
const unit = units[index];
if (!unit.isAlive || unit.hasActed) {
// 跳过死亡或已行动的单位
this.executeUnitActions(units, index + 1, resolve);
return;
}
this.executeUnitAction(unit); // 执行行动
// 800ms 后处理下一个单位
setTimeout(() => {
this.executeUnitActions(units, index + 1, resolve);
}, 800);
}
为什么用递归而不是循环?
```
// ❌ 错误:同步循环,所有行动瞬间完成
for (const unit of units) {
this.executeUnitAction(unit);
}
```
同步循环会导致所有 AI 行动在一帧内完成,玩家看不到 AI 的决策过程。
```
// ✅ 正确:递归 + setTimeout,实现可观察的延迟序列
this.executeUnitActions(units, index + 1, resolve); // 在 setTimeout 回调中调用
```
`setTimeout + 递归` 是实现「带延迟的序列执行」的经典模式,避免了 `async/await` 在某些 HarmonyOS 版本中的兼容性问题。
Promise 封装的意义
`executeTurn()` 返回 `Promise<void>`,使得调用方可以用 `.then()` 在 AI 回合**真正结束后**执行后续操作:
```
// GamePage.ets
this.aiEngine.executeTurn().then(() => {
// AI 回合结束后切回玩家回合
this.gameEngine.endTurn();
this.gameState = this.gameEngine.getGameState();
this.isAITurn = false;
this.gameMessage = '我方回合 - 请选择单位';
this.drawGameMap();
this.checkGameEnd();
});
```
如果不用 Promise,就无法知道 AI 回合何时真正结束(因为有异步延迟)。
四、单个单位行动决策
```
private executeUnitAction(unit: Unit): void {
// 优先级1:能直接攻击
const attackTarget = this.findBestAttackTarget(unit);
if (attackTarget) {
this.gameEngine.attackUnit(unit, attackTarget);
return; // 攻击后结束(hasActed 由 attackUnit 设为 true)
}
// 优先级2:移动 + 尝试攻击
const moveTarget = this.findBestMoveTarget(unit);
if (moveTarget) {
this.gameEngine.moveUnit(unit, moveTarget);
const newAttackTarget = this.findBestAttackTarget(unit); // 移动后重新检查
if (newAttackTarget) {
this.gameEngine.attackUnit(unit, newAttackTarget);
} else {
unit.hasActed = true; // 无法攻击,单纯移动后结束
}
} else {
unit.hasActed = true; // 无法移动,跳过
}
}
```
优先级设计的合理性:
-
能直接攻击时绝不浪费机会(攻击 > 移动)
-
不能攻击时,移动靠近敌人,移动后尝试攻击
-
无法移动时(被困)也会标记已行动,防止死循环
五、目标评估系统
```
private evaluateTargetValue(attacker: Unit, target: Unit): number {
let value = 0;
// 规则1:优先攻击将军(最高价值目标)
if (target.type === 4) { // UnitType.GENERAL
value += 100;
}
// 规则2:优先攻击血量低的单位(可能击杀)
const hpPercent = target.hp / target.maxHp;
if (hpPercent < 0.3) {
value += 50; // 快击杀了!
}
// 规则3:攻击威胁高的单位(攻击力×0.5)
value += target.attack * 0.5;
// 规则4:远程克近战(弓箭手/法师优先打战士/骑兵)
if (attacker.type === 1 || attacker.type === 3) { // ARCHER or MAGE
if (target.type === 0 || target.type === 2) { // WARRIOR or CAVALRY
value += 20;
}
}
// 规则5:距离惩罚(越近越好------减少需要追的步骤)
const distance = Math.abs(attacker.position.x - target.position.x) +
Math.abs(attacker.position.y - target.position.y);
value -= distance * 2;
return value;
}
```
评估函数的权重分析
| 规则 | 加分 | 设计意图 |
|------|------|---------|
| 攻击将军 | +100 | 极高优先级,将军是决胜关键 |
| 低血量目标 | +50 | 补刀意识,减少敌方战斗力 |
| 高攻击力目标 | +0~17.5 | 消灭威胁(法师35×0.5=17.5)|
| 远程克近战 | +20 | 体现兵种相克关系 |
| 距离惩罚 | -2/格 | 优先打近处目标 |
权重的优先级保证:
将军加分 100 远大于其他加分之和(最多约 87.5),确保只要将军在攻击范围内,AI 一定会优先攻击将军。这让游戏 AI 有了明确的策略目标感。
六、移动位置评估系统
```
private evaluateMovePosition(unit: Unit, pos: Position, enemyUnits: Unit\[\]): number {
let value = 0;
// 计算到最近玩家单位的距离
let minDistanceToEnemy = 100;
for (const enemy of enemyUnits) {
const distance = Math.abs(pos.x - enemy.position.x) +
Math.abs(pos.y - enemy.position.y);
minDistanceToEnemy = Math.min(minDistanceToEnemy, distance);
}
// 近战单位(攻击范围1):越近越好
if (unit.attackRange === 1) {
value += (10 - minDistanceToEnemy) * 10;
}
// 远程单位:进入攻击范围但保持距离
if (unit.attackRange > 1) {
if (minDistanceToEnemy <= unit.attackRange && minDistanceToEnemy > 1) {
value += 50; // 理想:在攻击范围内且保持距离
} else if (minDistanceToEnemy > unit.attackRange) {
value += (unit.attackRange - minDistanceToEnemy + 10) * 5; // 需要继续靠近
}
}
// 有利地形加分
const terrain = this.gameEngine.getTerrainAt(pos);
if (terrain === 1) value += 10; // 森林 +10
else if (terrain === 2) value += 15; // 山地 +15
else if (terrain === 3) value -= 100; // 水域 -100(强力阻止)
// 低血量单位更保守(优先站地形 + 远离敌人)
const hpPercent = unit.hp / unit.maxHp;
if (hpPercent < 0.3) {
if (terrain === 1 || terrain === 2) value += 30; // 更愿意守地形
value += minDistanceToEnemy * 3; // 远离敌人
}
return value;
}
```
近战 vs 远程的移动逻辑
```
// 近战单位(战士、骑兵、将军)
// 距离越近越好:距离1格加90分,距离5格加50分,距离10格加0分
value += (10 - minDistanceToEnemy) * 10;
// 远程单位(弓箭手、法师)
// 理想位置:在攻击范围内(distance ≤ attackRange)但不紧贴(distance > 1)
if (minDistanceToEnemy <= unit.attackRange && minDistanceToEnemy > 1) {
value += 50; // 完美射击位置
}
```
远程单位的「保持距离」逻辑体现了战术思考:弓箭手不应该冲到前线被近战单位反杀。
低血量退缩逻辑
```
if (hpPercent < 0.3) {
if (terrain === 1 || terrain === 2) value += 30; // 更喜欢地形庇护
value += minDistanceToEnemy * 3; // 越远越好
}
```
血量低于 30% 时,AI 会转入「自保模式」:优先寻找防御地形,并试图远离敌人。这让游戏 AI 有了「知道逃跑」的基本自我保护意识。
七、AI 设计模式:策略模式的体现
`evaluateTargetValue` 和 `evaluateMovePosition` 本质上是**评估函数(Evaluation Function)**,这是 AI 游戏设计中的核心概念:
```
评估函数 = Σ(各项因素 × 权重)
```
通过调整权重,可以控制 AI 的「性格」:
-
增加攻击性权重 → AI 更激进
-
增加防守地形权重 → AI 更保守
-
增加将军权重 → AI 更专注斩首
这种设计非常利于调参和扩展,是战棋游戏 AI 的常见架构。
八、AI 的局限性与改进方向
当前 AI 是**单步贪心策略**,只考虑「当前最优行动」,不做长远规划。
**改进方向:**
-
**Minimax 搜索**:向前看 N 步,选择最小化对方最大优势的方案
-
**蒙特卡洛树搜索(MCTS)**:随机模拟多次战斗,统计获胜路径
-
**A* 寻路**:计算到达目标位置的最优路径,而不只是「移动到最近的可达格」
-
**群体协作**:多个 AI 单位协同,包围敌人而不是单独行动
-
**规则引擎**:设置更复杂的战术规则(如保护将军、集火策略)