AoE2DE AI: The Stranger
Feb. 27, 2026, 12:52 a.m. edited March 15, 2026, 3:47 p.m.AoE2DEのAIを作ってみた: The Stranger AI1。人っぽい動きを目指して、なるべくユニットをグループで動かすようなAI。The Strangerという名前は、Outer Wildsというゲームのとある種族に由来している。彼らボードゲーム好きだし。GitHubレポジトリはここ。

↑こんな感じ。なお、このスクショのときよりはもうちょいマシな内政バランスをとるようになっている。
こだわっているのは、なるべくTCの裏に回り込んでぬくぬく内政してる農民を狙うように動くこと。身内でAI戦やるとき、難易度難しいのAI相手であっても、基本的にTCおよび塔で凌がれてしまって、本当は全然ラッシュに対抗できてないのに内政完了後の軍隊で引き倒すことができてしまっている。それは見ていてあんまりよくないので、レート700くらいの力量しかないもののそういう守り方では勝てないようなAIを目指した。あと後衛にもいきなり斥候が飛んでくるようにもしている。no more ぬくぬく。
AoE2のAI開発
ai フォルダに .per と .ai を作ることから始まる。 .ai は空ファイルで、本体は .per ファイル。はじめの一歩については、この動画を第7回まで見ておけば完璧。VSCode用の拡張機能もあるので使うといい2。
その後は、このサイトの Commands とひたすら睨めっこする時間が始まる。コマンドを組み合わせてAIを構築していくことになる。
完成したら、MODの公開方法を参考に、公開して完了。
なお、最初の頃はClaude Sonnet 4.5に任せてみたが、学習データがほぼないせいでまったくうまくいかなかったため、結局自分で最初から作ることになった。
落とし穴とか苦労したところ集
あらゆるところに落とし穴だらけ。それを思いついた順に書き連ねる。
素のAI
まず、そもそもAoE2のAIというのは、実は何もスクリプトを書かずとも勝手に動く。ただし、その動きは農民を一切作らず、農民2人 (1人?) を探索に向かわせ、斥候は動かず、農民1人で羊を食べ続けるというものである。何かスクリプトを作ったとしても、どこかに構文エラーがあって正常に読み込まれなかったらこの動きになってしまう。こういう素のAIを矯正してやるのが自作AIスクリプトの考え方となる。
すると、素のAIがどこまで動いてくれるのかを把握するのも大事となる。例えば、敵が攻めてきたら勝手にTCに駐留して矢を放つというのはやってくれるし、見つけた羊をTCに向かわせるのもやってくれるし、意外に色々やってくれる。
……逆に言えば、それ以外はぜーんぶ自分で作らないといけない。作っていくと、標準のDE版AIやHD版AIどころか、CD版AIですらすごいな……という気持ちになってくる。
探索
まーじで敵を見つけてくれない。すーぐに誰もいやしないマップの端っこをちまちま探索始める。時間経過 (game-time を見て disable-self するか、もしくはタイマー) で up-send-scout の ScoutMethod を切り替えてあげましょう。
イノ引き
HD版AIのイノ引き部分をそのまま持ってくるのが楽。
TCに突っ込んで即死
これ。ひたすらベクトル計算をコマンドに落とし込む作業をしていた。そのうえで、単純にTCから離れる方向にしてしまうと、離れる→近づく→離れる→…を延々と繰り返すだけになってしまうので、外積ライクに離れる先をずらすようにして、結果的にTCを回り込めるような動作とした。
ゴールと定数の困惑
(defconst hoge 101)
(defrule
(true)
=>
(set-goal hoge 5)
(up-chat-data-to-all "hoge is %d as const" c: hoge)
(up-chat-data-to-all "hoge is %d as goal" g: hoge)
(disable-self)
)
とすると、
101
5
と表示される。このように、通常のプログラミングでよくある変数として使いたい場合はゴールとして、ずっと変わらない定数として使いたい場合は定数として使う。なお、ゴールとして使う場合は、同じ数字の defconst にあまりすべきではないだろう。
そして、重要なのは、すべてのコマンドがゴールに対応しているわけではないということ。例えば、 (unit-type-count-total archer-line < 20) の archer-line の代わりに、 hoge へ (set-goal hoge knight-line) として (unit-type-count-total hoge < 20) としてもゴールに入れた騎士の数で比較されるわけではなく、定数の 101 が使われてしまう(なお、101に対応するユニットは存在しないはずなので、未定義動作となりそう)。このコマンドの場合は代わりに (up-object-type-count-total g: hoge < 20) が使えるので良いが、対応する up- 系コマンドがないと苦労するだろう3。
attack-now
とりあえずAIっぽい動きの攻撃をさせたいときは attack-now を使えばよい。これは (up-set-offense-priority c: lumber-camp c: 1) などでセットした優先度にしたがって死ぬまで永遠に攻撃を続ける。しかし、今回の目的ではもうちょっと賢く動いてほしくDUC (後述) を採用しているため、使わないことにしている。注意すべき点として、DUCと併用すると意図しない動きになることが多い。というのも、attack-now で命令されたユニットはいつまでもその戦略に沿って動き続けるため、DUCで動いた先のユニットを無視するということも起きうる。なので使うときはリセット系コマンドも忘れずに。
DUC (Direct Unit Control)
これ。ガイドにも2つ記事があるが、これをいかに実現するかが力量の問われるところになると思う。前述したTC避けもDUCの一種である。
具体的には up-find-local や up-find-remote を利用して自分のオブジェクトや focus-player のオブジェクトを検索し(フィルター系コマンドも併用しつつ)、その情報を up-get-search-state や up-set-target-object、 up-set-target-point などでとって、 up-target-point や up-target-object などへ利用していく流れになる。
自分はTC避け以外には、敵の資源場を巡ったり、TCの後ろの方を狙ったり、ユニットをグループ単位で選択したり、そういうことに使っている。おそらく、もっと頑張ると引き撃ちとかもできるんだろう。めっちゃ大変そう。
ゴールIDの制限
たまにコマンドによっては、 defconst で定義した数字が小さすぎたり大きすぎたりすると使えないものがある。たとえば enable-timer に使えるタイマーのゴールIDは 1 から 50 までである。たしか、これ違反してもエラーも何も出なくて、ただ変な動きするなあ?という違和感でしか気づけなかったはず。
定義されてないキーワード
up-set-target-point のアクションで action-attack-move をセットすると ERR2005: Invalid identifier というエラーが出る4。これはこのページに書いてあるように、自分で (defconst action-attack-move 19) と定義してあげないと使えないのである。同様に、こっちはどこにも情報がないが、 up-filter-status の引数はほとんど自分で定義してやらないと使えない。何が原因でエラー出てるのかめっちゃ悩んだやつ。
#load-if-defined 内で defconst 使えない
通常のプログラミング同様、ファイル分けをすると見通しがよくなる。特に前衛後衛で読み込むファイルを #load-if-defined によって変えることにしているが、そういうところで defconst を使うと、その時点ではエラーが出ないくせに、そこで定義された定数ないしはゴールを使った瞬間にエラーが出る。まあ確かに分岐によって定義有無が変わるから理解できなくも…いや、その中では絶対定義されてるんだからやっぱり理不尽。めっちゃ悩んだ。もちろん、 #load-if-defined 内ではないところで読み込んだファイル内での defconst は問題ない。
XSスクリプト……?
なんか、実はDE版以降ではC言語ライクなXSスクリプトなるものも使えて、include で読み込んで xs-script-call で呼べるらしい。結局使ってないものの、多分きっと便利な気がする
現状と展望
260228
実は軍兵塔 (しかも塔を柵で囲む!) や即弓騎兵をするスクリプトも入っているが、分岐読み込みを実装してないので、今のところ呼ばれることはない。(もったいない)
強さとしては、4vs4アラビアでDE版AIにごく稀に勝てることがあるという感じ。なお、身内でのレベルに合わせて難易度は難しいでしかテストしていない。一応、DE版AIが難易度によって作る農民数が変わるが、そこと合わせている。


即ユニークとか、即スコピとか、phosphoru戦術とか、そういうの載せたいですね。いつやるかはさておき……だって、軽い気持ちでここまで作ったらめっっちゃ大変だったんだもん………
260316
アプデ!!!なおv1.1のままバージョン番号が変わらない。どうやって変わるのか全然わからない。本当にアプデできているのか…????5
とにかく、アプデ内容としては、斥候や民兵が農民殴ったときに反撃されたら、自身の数が1人だけなら逃げるようにした。これで初期内政の荒らしが捗る〜〜。
そう、民兵である。まだ戦術をランダムで選ぶことはしないものの、文明固有の戦術を一部採用するようになった。今回はゴートの軍兵→ハス槍だくだく、ペルシアの開幕戦士小屋建造の民兵発進からの即城主弓騎兵パルティア戦術、そしてクメールの即城主スコピである。まあ酷い戦術ばかりであるが、そういうものほど作るのが楽しいので仕方ない。

これまでは4vs4でしかDE版AIに勝つことはなかった(難易度は前述のとおり難しいに設定)が、クメールのこの戦術でついに1vs1で勝つことができた。やったね!!
-
Age of Empiresのサイト、なんでログインしないとMODが見れないんだろう。AoE2DEゲーム内からなら普通にMOD検索から見つかる ↩
-
defconstで定義したものを補完する機能はない。また、steppe-lancer-lineやbombard-cannon-lineなど、 DE 版以降のものはあまり補完してくれない。 ↩ -
up-から始まるのは、 User Patch に起因して追加されたコマンド集だろう。便利なようにc:g:s:が使える場合が多い。 ↩ -
ほぼどんな問題が起きても同じこのエラー文出るから、行番号程度しか助けにならない…… ↩
-
info.jsonとか見てもバージョン番号の設定とか特にないんだよね…… ↩