🤖 用 TypeScript 打造一個 Chrono Divide AI Bot:從部署到進攻的全自動流程解析
Copyright Notice: This article is an original work licensed under the CC 4.0 BY-NC-ND license.
If you wish to repost this article, please include the original source link and this copyright notice.
Source link: https://v2know.com/article/1249
Chrono Divide 是一款基於 Red Alert 2 引擎重現的網頁即時戰略遊戲,提供了官方支援的 AI 開發 API。
本文將透過一份實戰程式碼,從零開始解析一個簡單的 AI Bot,教你如何讓 Bot 自動部署建造場、派兵出擊、發現敵人後主動攻擊!
🧱 前置準備
安裝套件
本教學使用 @chronodivide/game-api
套件來開發 AI Bot:
npm install @chronodivide/game-api
資源目錄
確保你已設定好 RA2 的資源目錄:
# Windows(例如 MIX 檔案放在 ./redalert2)
set MIX_DIR=./redalert2
📜 代碼總覽與註解說明
以下是完整的 Bot 原始碼,包含詳細中文註解,說明其如何管理狀態、行為決策與遊戲互動邏輯:
👇👇👇
請將以下原始碼替換為你整理過的那份註解代碼:
// 匯入 Chrono Divide 官方提供的核心 API 類型與工具
import {
cdapi,
OrderType,
ApiEventType,
Bot,
GameApi,
ApiEvent,
CreateBaseOpts,
CreateOpts
} from "@chronodivide/game-api";
// 定義 Bot 的內部狀態,用於控制其行為流程
enum BotState {
Initial, // 初始狀態:等待或部署 MCV
Deployed, // 建造場已部署,開始集結部隊
MovingToEnemy, // 移動至敵方初始位置
AttackingEnemy, // 發現敵人建築,開始攻擊
Defeated // 無可操作單位,被判定戰敗
}
// 建立一個簡單的 Bot,繼承自官方 Bot 類別
class ExampleBot extends Bot {
private botState = BotState.Initial; // Bot 狀態初始為 Initial
private tickRatio!: number; // 控制 AI 執行頻率的 tick 比率
private enemyPlayers!: string[]; // 紀錄敵方玩家名稱列表
// 遊戲啟動時呼叫,取得 tickRate、敵人資訊並計算 bot 的 tickRatio
override onGameStart(game: GameApi) {
const gameRate = game.getTickRate(); // 每秒 tick 數
const botApm = 300; // 假設 AI 有每分鐘 300 動作
const botRate = botApm / 60;
this.tickRatio = Math.ceil(gameRate / botRate); // 決定多久 tick 執行一次
// 找出非自己且非同盟的敵方玩家名稱
this.enemyPlayers = game.getPlayers().filter(p =>
p !== this.name && !game.areAlliedPlayers(this.name, p));
}
// 每個 tick 呼叫,根據 tickRatio 節流執行實際邏輯
override onGameTick(game: GameApi) {
if (game.getCurrentTick() % this.tickRatio === 0) {
switch (this.botState) {
case BotState.Initial: {
// 若已部署建造場則切換狀態
const baseUnits = game.getGeneralRules().baseUnit;
let conYards = game.getVisibleUnits(this.name, "self", r => r.constructionYard);
if (conYards.length) {
this.botState = BotState.Deployed;
break;
}
// 若還未部署建造場,嘗試對 MCV 使用 DeploySelected
const units = game.getVisibleUnits(this.name, "self", r => baseUnits.includes(r.name));
if (units.length) {
this.actionsApi.orderUnits([units[0]], OrderType.DeploySelected);
}
break;
}
case BotState.Deployed: {
// 部隊部署完成後,集合部隊準備前往敵方出生點
const armyUnits = game.getVisibleUnits(this.name, "self", r => r.isSelectableCombatant);
const { x: rx, y: ry } = game.getPlayerData(this.enemyPlayers[0]).startLocation;
this.actionsApi.orderUnits(armyUnits, OrderType.AttackMove, rx, ry);
this.botState = BotState.MovingToEnemy;
break;
}
case BotState.MovingToEnemy:
case BotState.AttackingEnemy: {
// 每次判斷是否還有部隊可用
const armyUnits = game.getVisibleUnits(this.name, "self", r => r.isSelectableCombatant);
if (!armyUnits.length) {
// 若全軍覆沒則退出遊戲
this.botState = BotState.Defeated;
this.actionsApi.quitGame();
break;
}
if (this.botState === BotState.MovingToEnemy) {
// 若尚未偵測到敵方建築,持續前進;一旦看到建造場則開始進攻
const baseUnits = game.getGeneralRules().baseUnit;
const enemyBase = game.getVisibleUnits(this.name, "hostile",
r => r.constructionYard || baseUnits.includes(r.name));
if (enemyBase.length) {
// 指揮部隊攻擊發現的第一個敵方建築
this.actionsApi.orderUnits(armyUnits, OrderType.AttackMove, enemyBase[0]);
this.botState = BotState.AttackingEnemy;
}
}
break;
}
default:
break;
}
}
}
// 當遊戲中出現事件(例如單位毀滅、所有權轉移)時被觸發
override onGameEvent(ev: ApiEvent) {
switch (ev.type) {
case ApiEventType.ObjectOwnerChange: {
this.logger.info(`Owner change: ${ev.prevOwnerName} -> ${ev.newOwnerName}`);
break;
}
case ApiEventType.ObjectDestroy: {
this.logger.info(`Object destroyed: ${ev.target}`);
break;
}
default:
break;
}
}
}
// 遊戲主函數,負責初始化遊戲環境並啟動對戰
async function main() {
// 初始化遊戲資源目錄 MIX_DIR
await cdapi.init(process.env.MIX_DIR || "./");
const mapName = "mp03t4.map"; // 使用指定的多人對戰地圖
// 設定遊戲初始參數(適用於離線與線上模式)
const baseOpts: CreateBaseOpts = {
buildOffAlly: false,
cratesAppear: false,
credits: 10000,
gameMode: cdapi.getAvailableGameModes(mapName)[0],
gameSpeed: 5,
mapName,
mcvRepacks: true,
shortGame: true,
superWeapons: false,
unitCount: 10
};
let opts: CreateOpts;
// 判斷是否為線上模式(依據是否設定 SERVER_URL)
const onlineMode = !!process.env.SERVER_URL;
if (onlineMode) {
// 從環境變數取得帳號設定
const botName = process.env.BOT_USER;
if (!botName) throw new Error(`Missing env BOT_USER`);
const botPassword = process.env.BOT_PASS;
if (!botPassword) throw new Error(`Missing env BOT_PASS`);
const playerName = process.env.PLAYER_USER;
if (!playerName) throw new Error(`Missing env PLAYER_USER`);
// 線上模式:Bot vs 人類玩家
opts = {
...baseOpts,
online: true,
serverUrl: process.env.SERVER_URL!,
clientUrl: process.env.CLIENT_URL!,
botPassword,
agents: [
new ExampleBot(botName, "Americans").setDebugMode(true),
{ name: playerName, country: "Africans" }
]
};
} else {
// 離線模式:Bot vs Bot 測試
opts = {
...baseOpts,
agents: [
new ExampleBot("Joe", "Americans").setDebugMode(true),
new ExampleBot("Bob", "Africans")
]
};
}
// 建立並啟動遊戲對戰
const game = await cdapi.createGame(opts);
// 主循環:直到遊戲結束
while (!game.isFinished()) {
await game.update();
}
// 儲存重播與清理資源
game.saveReplay();
game.dispose();
}
// 進入點,處理例外並啟動
main().catch(e => {
console.error(e);
process.exit(1);
});
🚦 執行方式
你可以透過兩種方式執行:
🔁 離線模式(Bot 對 Bot)
MIX_DIR=./redalert2 node bot.js
-
自動讓 2 個 Bot 在地圖上對戰,方便測試戰術邏輯。
🌐 線上對戰模式(Bot 對人類)
SERVER_URL=wss://your-server \
BOT_USER=ai1 BOT_PASS=secret \
PLAYER_USER=humanplayer \
MIX_DIR=./redalert2 node bot.js
-
AI Bot 會透過 WebSocket 連線並與人類對手進行 PvP。
🧠 行為解析:這個 Bot 做了什麼?
階段 | 行為邏輯 |
---|---|
🟢 初始(Initial) | 檢查是否已有建造場;否則命令 MCV 部署 |
🏗️ 部署(Deployed) | 建造場部署完成後,集結所有部隊,向敵人出生點出擊 |
🚶 移動(Moving) | 沿路攻擊前進,如偵測到敵方建築則切換為進攻狀態 |
🔥 攻擊(Attacking) | 鎖定第一個看到的敵方建築並持續進攻 |
💀 戰敗(Defeated) | 無可用單位時自動投降並退出遊戲 |
🔍 可擴充方向
這只是個起點,你可以嘗試:
-
➕ 加入建造邏輯(如電廠→兵營→坦克廠)
-
💰 加入資源採集與礦車邏輯
-
🎯 根據敵方部隊選擇不同戰術(反坦克、反步兵)
-
🧠 加入地圖偵查、視野規劃與智能決策
📝 結語
這篇文章帶你從 0 開始建立一個基礎的 Chrono Divide AI Bot,理解狀態管理與 API 的互動方式。
透過不斷測試與優化,你可以打造出一個擁有自我決策能力的智慧對手!
如果你對 AI 對戰有興趣,這會是個絕佳的開發起點!
未來我們將深入探討 資源控制、建築排布、單位編隊、行為切換 AI 等進階主題,敬請期待 🚀
This article was last edited at