第5篇 | 基于 HarmonyOS 开发战旗小游戏项目AI 引擎——怎样设计逻辑

前言:

前面第四篇已经详细介绍了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 需要回答三个问题:

  1. **做什么**:攻击?移动?等待?

  2. **攻击谁**:选哪个目标最有价值?

  3. **去哪里**:移动到哪个格子最有利?

下面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; // 无法移动,跳过

}

}

```

优先级设计的合理性:

  1. 能直接攻击时绝不浪费机会(攻击 > 移动)

  2. 不能攻击时,移动靠近敌人,移动后尝试攻击

  3. 无法移动时(被困)也会标记已行动,防止死循环

五、目标评估系统

```

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 是**单步贪心策略**,只考虑「当前最优行动」,不做长远规划。

**改进方向:**

  1. **Minimax 搜索**:向前看 N 步,选择最小化对方最大优势的方案

  2. **蒙特卡洛树搜索(MCTS)**:随机模拟多次战斗,统计获胜路径

  3. **A* 寻路**:计算到达目标位置的最优路径,而不只是「移动到最近的可达格」

  4. **群体协作**:多个 AI 单位协同,包围敌人而不是单独行动

  5. **规则引擎**:设置更复杂的战术规则(如保护将军、集火策略)