概要
TypeSafe Jevを使って、AIが自動プレイするランゲームを作りました
Jevが障害物の種類や距離を見て、ジャンプやスライドなどのアクションを選択します
手動プレイもできるようになっているので、人間が遊んだあとにJevのプレイを見ることができます
実際に動くデモはこちらです
https://jevrunner.pages.dev/
作ったもの
ゲームを開始するときにMANUALとJEVを選択できます
JEVを選択すると、障害物が近づいてきたタイミングでAPIを呼び出し、自動でアクションを実行します
用意したアクションは下記の4つです
- JUMP
- DOUBLE JUMP
- SLIDE
- DASH
スペースキーまたは上キーでジャンプ、下キーでスライド、右キーでダッシュします
ジャンプ中にもう一度ジャンプすると二段ジャンプになります
ゲームの素材にはKenney New Platformer Packを使用しています
Jevについて
JevはStateと質問を送ると、構造化された回答を返してくれるモデルです
通常のLLMのように文章を生成してからJSONに変換する必要がなく、回答をそのままプログラムから利用できます
質問にはChoice、Score、Noulの3種類があります
今回は複数の候補から1つを選ぶChoiceを使用しました
Choiceでは選択結果のほかに、各候補の確率とConfidenceも取得できます
ゲームの状態を送る
障害物が画面内に入ったら、下記のようなStateをJevに送ります
{
"game": "endless side-scrolling runner",
"goal": "avoid the observed obstacle and stay alive",
"allowed_actions": ["jump", "double_jump", "slide", "dash"],
"runner": {
"grounded": true,
"current_action": "run"
},
"target": {
"kind": "hazard",
"type": "rock",
"width_px": 72,
"height_px": 76,
"distance_px": 987,
"time_to_impact_seconds": 2.35
},
"movement_physics": {
"jump_airtime_seconds": 0.79,
"jump_peak_height_px": 176
},
"course": {
"distance_m": 44,
"speed_px_per_second": 420,
"difficulty_level": 1
}
}
障害物の種類だけでなく、プレイヤーとの距離、障害物の大きさ、現在の速度なども送っています
コインも同じようにStateへ入れているので、障害物を避けるだけでなくコインを取りに行くこともできます
アクションを選択する
Jevには現在のStateを見て、4つの候補からアクションを1つ選択させています
questions: {
action: {
type: "choice",
instructions: "Choose the single action that best handles the target.",
criteria: {
jump: "Use one jump for a rock, slime, normal spikes, or saw.",
double_jump: "Use two jumps for a long spike field or high coin arc.",
slide: "Duck beneath a ceiling hazard that cannot be jumped over.",
dash: "Dash through and break a warning crate."
}
}
}
返ってくるのはjump、double_jump、slide、dashのいずれかです
判断結果を表示する
Jevが選んだアクションだけを表示すると、固定ルールで動いているようにも見えます
そのため画面下部に下記の情報を表示するようにしました
- 選択したアクション
- アクションごとの確率
- Confidence
- 実行タイミングを計算するゲームエンジン
- APIの応答時間
- アクション実行までの残り時間
上の画面では、検出した障害物に対するアクションごとの確率と、ゲーム側で計算した実行までの残り時間を表示しています
下のバーはアクションを実行するまでのカウントダウンです
実行タイミングはゲーム側で決める
最初はJevからjumpやslideなどのアクションだけを受け取っていました
ただし、アクションだけではゲーム内のどの位置で実行するか決まりません
一度はアクションだけでなく「Stateを取得してから何ms後に実行するか」もJevに選択させました
送信前の時刻を保存し、Jevが返した実行時間からAPIの往復時間を引く方法です
たとえばjump__1050msが返り、往復に687msかかった場合は、残り363ms待ってジャンプします
ただ、この方法ではアクションと実行時間を組み合わせた候補が増えます
通信時間より前の候補は実行できないため除外する必要があり、通信時間の変動によって選択できる候補も変わります
実際に動かすと、アクションの判断は合っていても実行時刻がずれて障害物に当たることがありました
そこでJevはアクションの選択だけを行い、正確な実行タイミングはゲーム側で計算する方法に戻しました
ゲーム側では現在の速度、障害物の横幅、ジャンプの初速、重力から実行位置を計算します
Jevの回答を受け取った時点の距離から計算するため、通信中に障害物が進んだ分もそのまま反映されます
AIには「何をするか」を任せ、ゲームエンジンには「その操作を正確に成立させること」を任せる構成です
コインを取る
コインは通常の高さと高い位置の2種類を配置しています
通常のコイン列はJUMP、高いコイン列はDOUBLE JUMPを選択するようにしています
障害物と同じようにコインの位置や高さをJevへ送っているため、コインを取るかどうかもJevの回答として確認できます
Jevを使ってよかったところ
一番よかったのは、回答をそのままプログラムで使えることです
通常のLLMで同じことをすると、文章からJSONを取り出したり、想定外の値を弾いたりする処理が必要になります
JevのChoiceでは最初から候補を定義しているため、返ってきた値でそのまま処理を分岐できました
確率とConfidenceが一緒に返ってくるところもよかったです
最終的に選ばれたアクションだけでなく、JUMPとDOUBLE JUMPでどのくらい迷っていたのかも画面に表示できます
今回のようなデモでは、AIが何を判断したのか見せやすくなりました
アクションの精度については、岩ならJUMP、長いトゲならDOUBLE JUMP、蜂ならSLIDEのように条件を具体的に書くと、狙ったアクションを選ぶ場面が多かったです
まだ足りないと感じたところ
今回の用途で一番気になったのはレスポンス時間です
ゲーム画面に表示しているJev APIの処理時間は200〜400ms程度で、先の障害物を判断する用途なら問題ありませんでした
一方でブラウザからサーバを経由して結果を受け取るまでの往復時間を測ると、今回の環境では600〜800ms程度かかることもありました
この時間はネットワークや実行環境でも変わるため、ミリ秒単位のアクションタイミングをそのまま任せるのは難しかったです
精度もStateとcriteriaの書き方にかなり影響されます
障害物の種類だけを送るより、位置、大きさ、速度、床との隙間まで送ったほうが判断は安定しました
反対に、アクションと実行時間を1つのChoiceで選ばせると候補数が一気に増え、どの時間を選ぶかが不安定になりました
連続値の実行時間を直接返せたり、選んだアクションを前提に次の質問を評価できたりすると、今回のようなリアルタイム制御にも使いやすくなりそうです
使ってみて、Jevは正確な物理制御そのものより、状況から候補を選ぶ部分に向いていると感じました
まとめ
TypeSafe Jevを使ってランゲームを自動プレイさせました
Choiceでアクションを取得できるので、ゲーム側では文章を解析せず、そのまま処理を分岐できます
また確率とConfidenceも返ってくるため、Jevがどのアクションを選ぼうとしているのか画面上に表示できました
今回はゲームに使用しましたが、複数の候補からリアルタイムに処理を選択したい場合にも使えそうです


