花AI塚のチュートリアルが欲しいな、ということでチュートリアルの連載を始めました。 おおよそ隔週ペースで全4回で進めていこうかと考えています。
今回の記事では花AI塚に付属しているサンプルAI「random-walk」について見ていきます。 ということでまずrandom-walk AIを実際に動かしてみます。
花AI塚でAIを動かすためにはまず設定が必要です。花AI塚のフォルダにある「setting.exe」を起動して設定を行います。
初めて花AI塚を使う場合は東方花映塚のexeファイル(th09.exe)の指定を行う必要があるので、まずこれを設定します。
次に今回動かすAI(つまりrandom-walk AI)を指定します。今回は1P側でAIを動かすことにします。 花AI塚のフォルダの「sample-scripts/random-walk/main.lua」を指定します。
ひと通り設定を終えると以下の通りになります。「OK」を押して終了しましょう。
設定が終わったのでいよいよAIを動かします。 「ka_ai_duka.exe」を起動すると花映塚が立ち上がります。 とりあえずマッチモード「人 vs 式」を選択して始めましょう。
対戦が始まると1Pがぶるぶる震える感じで動きます。 random-walk AIはランダムに色んな方向に動くだけのAIなので、すぐに弾や敵に激突してしまいます。 とはいえ実際にAIが動くことが確認できたと思います。
今回はsetting.exeで設定を行いましたが、ka_ai_duka.iniをメモ帳などのテキストエディタで直接編集して設定することもできます。
先ほど動かしたrandom-walk AIの中身を見ていきます。 random-walk AIは2つのファイルからできています。
main.lua
がAIの本体にあたるファイルで、keyutils.lua
は自機操作まわりをラップしているライブラリです。
ここでは`main.lua
を見ていきましょう。実際のmain.lua
にはデバッグ用のコードが含まれていますが、ここでは省略しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
花AI塚は対戦の間毎フレームmain
関数を呼び出します。よってAIの処理の起点はmain
関数となります。
それではmain
関数を見ていきます。 ここで行っている処理は3つです。
1フレームの間にできるすべての移動操作を列挙する
local candidates = generateCandidates();
列挙した移動操作からランダムに1つ選ぶ
local keys_to_send = choice(candidates);
選んだ移動操作に対応するキー入力を送る
local keys = keyutils.newstate()
for i,key in ipairs(keys_to_send) do
keys[key] = true;
end
keyutils.send(keys);
移動操作の列挙というのはつまり、「上に移動」、「右に移動」、「左下に低速移動」といったように移動操作を片っ端から挙げていくことです。 今回のrandom-walk AIに限らず、ゲームAIは
ということを行っています。random-walk AIの場合は、今できる操作として移動操作のみを挙げていて、選択肢から選ぶときの尺度がただのランダムといった感じになっています。
3行目~25行目で定義されている関数generateCandidates
は移動操作の列挙を行う関数です。 この関数は以下のようなテーブルを戻り値として返します。
{
{ keys = {} },
{ keys = {"up"} },
{ keys = {"up", "shift"} },
{ keys = {"up", "right"} },
{ keys = {"up", "right", "shift"} },
{ keys = {"right"} },
{ keys = {"right", "shift"} },
{ keys = {"right", "down"} },
{ keys = {"right", "down", "shift"} },
{ keys = {"down"} },
{ keys = {"down", "shift"} },
{ keys = {"down", "left"} },
{ keys = {"down", "left", "shift"} },
{ keys = {"left"} },
{ keys = {"left", "shift"} },
{ keys = {"left", "up"} },
{ keys = {"left", "up", "shift"} }
}
テーブルの各要素がkeys
というフィールドを持っていて、keys
の中には文字列の配列が格納されている、といったものです。 このkeys
の中身が移動操作のキー入力の組み合わせに対応しています。
例えば上移動なら
{ keys = {"up"} }
左下移動なら
{ keys = {"down", "left"} }
左下低速移動なら
{ keys = {"down", "left", "shift"} }
といった感じです。 まったく移動しない(停止する)操作は空の配列で表します。
{ keys = { } }
27行目~29行目で定義されている関数choice
は引数candidates
で渡されたテーブルの中からランダムに1つを選んで、その要素のkeys
フィールドの値を返します。 candidates
はさきほどの関数generateCandidates
の戻り値と同じものです。
例えばcandidates
の3番目の要素
{ keys = {"up", "shift"} },
を選んだとすると、関数choice
の戻り値は配列
{"up", "shift"}
となります。
つまり関数choice
の戻り値はどのキーを押すかの組み合わせを表す配列が返ってくるという訳です。
今度はいよいよ実際にAIを書いていきます。
最初なので、弾を避けるとか考えずに左右を往復するだけのAIを作ります。
適当なところにフォルダを作って、その中にmain.lua
を用意します。
hoge
+ main.lua
AIの処理は毎フレームmain
関数が呼ばれることで行われます。ということでとりあえずmain
関数を書きます。
1 2 |
|
random-walk AIのときはmain
関数の中身は
といったような流れでした。
左右に往復するAIの場合は選ぶ移動操作は
のいずれかです。毎フレーム、どちらかを選んでキー入力を送るというのが左右往復AIの場合のmain
関数の流れとなります。
右移動すべきか、左移動すべきかを決めるところについて考えてみましょう。 今仮に右移動していたとして、左に折り返す必要にせまられるのはいつかというと、画面右端までたどり着いた時です。 同じように左移動していたとき、右に折り返す必要にせまられるのは画面左端にたどり着いた時です。 ということで今の考えをコードに落とすとこんな感じになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
今自機は右に移動しているのか、そうでないのかを状態として持っておく必要があります。 ここではブール値の変数としてis_moving_right
を用意して、この変数で表すことにします。 そうするとさっきのコードは
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
のようになります。
残るは画面端にぶつかったかどうかの判定と、キー入力の送信だけです。
花AI塚ではゲームの状態に関する様々な情報を手に入れることができます。 自機の座標が分かれば画面端にぶつかったかどうかも分かります。 ということで自機の座標を取得するコードを加えてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
ゲームの状態に関する情報はgame_sides
という変数からアクセスできます。
game_sides[1] 1P側の情報
+ player 自機の情報
+ x
+ y
+ ...
+ enemies 敵の情報
+ bullets 弾の情報
+ exAttacks EXアタックの情報
+ items アイテムの情報
game_sides[2] 2P側の情報
+ ...
また、AIが1P側なのか2P側なのかはplayer_side
という変数から分かります(1P側なら1、2P側なら2)。 なのでgame_sides[player_side]
でAIの居る側の情報が取れるという訳です。
こうして手に入った自機座標を使って画面端にぶつかったかを判定します。 x座標がある値以上になったら右端にぶつかった、ある値以下になったら左端にぶつかったと判定することにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
X_MAX
やX_MIN
の値はどう決めた?X_MAX
やX_MIN
の値ですが、自機座標の値のログを取って決めてます。 花AI塚ではAIの動作中も人間のプレイヤーのキー入力を受け付けています。 なので下のように座標のログを取るだけのAIを用意して、AIの動作中に手動操作で自機を動かせばその自機の座標のログを取ることができます。
1 2 3 4 5 6 |
|
あとはキー入力を送るコードを書けば完成です。 random-walk AIでは別途keyutils
というライブラリを作っていましたが、今回は花AI塚で用意されている関数を直接呼ぶことにします。
キー入力の送信にはsendKeys
という関数を使います。この関数は整数値を引数に取ります。 この引数の各bitがどのキーを送るかに対応しています。 対応は以下の通りです。
例えば0x40
は7bit目だけが1で他がゼロなので、「←キー」だけの入力になります。 低速で左に移動したいときは3bit目と7bit目を1にするので、sendKeys(0x44)
となります。
キー入力のコードを加えるとAIのコードは下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
それでは実際にAIを動かしてみましょう。setting.exeで動かすAIを指定したあとで、ka_ai_duka.exeを起動すると動きます。
ここまででAIを実際に作るところまで見てきましたが、いかがだったでしょうか。
今回作ったAIは弾を避けたりしない単純なものでしたので味気ないかもしれません。 次回は弾や敵を避けるAIを作っていきます(予定)。