先読み射撃(偏差射撃)
あなたの担当 この章は、bot の中で使う計算の説明です。使うだけなら → 動く敵に当てる
止まっている敵には、まっすぐ狙えば当たります。 動いている敵には当たりません。 理由は単純で、弾が飛んでいるあいだに敵が移動するからです。
敵が「これから居るところ」を狙うのが偏差射撃です。ゲームでも実際の射撃でも同じ言葉を使います。
なぜ、まっすぐ撃つと当たらないのか
Section titled “なぜ、まっすぐ撃つと当たらないのか”CodeTank Arena の弾は 8.0 マス/秒で飛びます。かなり速いのですが、無限ではありません。
たとえば敵が 8 マス先にいるとき、弾が届くまでにかかる時間は
戦車の前進速度は 3.0 マス/秒なので、その 1.0 秒のあいだに敵は 3 マス動きます。 戦車の当たり判定は半径 0.40 マス、つまり直径 0.8 マスですから、3 マスのずれは「かすりもしない」量です。
当たらないのは腕の問題ではなく、狙う場所が間違っているのです。
着弾までの時間を出す
Section titled “着弾までの時間を出す”まず、弾が届くまでの時間 を求めます。 自分の位置を 、敵の位置を 、弾速を とすると
記号の読み方です。
- は位置。太字なのは「 と の 2 つ組」だからです。。
- は長さ。 で「 と のあいだの距離」です。 中身は三平方の定理そのもので、 を短く書いたものにすぎません。
- (マス/秒)。
つまりこの式は「距離 ÷ 速さ = 時間」と言っているだけです。
プログラムでは distance() がこの をそのままやってくれます。
t = distance(state.me.x, state.me.y, state.enemy.x, state.enemy.y) / 8.0未来の位置を予測する
Section titled “未来の位置を予測する”時間 が分かったので、その時間だけ敵が進んだ先を狙います。 敵の速度を とすると、狙う点 は
と に分けて書けば、見慣れた形になります。
「いまの位置 + 速さ × 時間」。小学校で習う「道のり = 速さ × 時間」を、 方向と 方向で 1 回ずつやっているだけです。
予測はずれる、だから繰り返す
Section titled “予測はずれる、だから繰り返す”ここまでの計算には、実はごまかしがあります。
を出すときに使ったのは「いまの敵の位置までの距離」でした。 しかし本当に狙うのは未来の位置 で、そこまでの距離は違います。 敵が遠ざかっていれば、弾はもっと長く飛ばねばならず、 はもっと大きいはずです。
対処は簡単で、出た答えでもう一度計算し直すだけです。
に最初の答えを入れて 2 回ほど回すと、値はほとんど動かなくなります。 弾速 8.0 マス/秒に対して戦車は 3.0 マス/秒と遅いので、収束が速いのです。
そのまま bot に貼って動きます。使っているのは distance と angle_to の 2 つだけです。
BULLET_SPEED = 8.0 # マス/秒FPS = 60.0 # 1 秒あたりのフレーム数
def aim_point(state): """敵の未来の位置(狙点)を返す。""" ex = state.enemy.x ey = state.enemy.y # vx / vy は マス/フレーム なので、マス/秒 にそろえる evx = state.enemy.vx * FPS evy = state.enemy.vy * FPS
tx = ex ty = ey i = 0 while i < 2: # 2 回で十分収束する t = distance(state.me.x, state.me.y, tx, ty) / BULLET_SPEED tx = ex + evx * t ty = ey + evy * t i = i + 1 return (tx, ty)
def update(state): tx, ty = aim_point(state)
# angle_to は「ここから、あそこ」を向く角度(度)を返す want = angle_to(state.me.x, state.me.y, tx, ty)
# いまの向きと狙いの差。-180〜180 に収まる diff = angle_diff(want, state.me.angle)
# 砲身が向ききる前に撃っても当たらない。4 度以内になってから撃つ fire = state.me.can_fire and abs(diff) < 4.0
return {"drive": "stop", "steer": want, "fire": fire, "barrier": False}steer には向きたい角度そのものを入れます。差分ではありません。
戦車は毎秒およそ 180 度(546 angle-units/frame)まで回れて、その角度へ最短回りで向きます。
組み込みの predict_lead は何をしているか
Section titled “組み込みの predict_lead は何をしているか”同じことをする組み込み関数があります。
tx, ty = predict_lead(state.enemy, 8.0, state.me.x, state.me.y)こちらは繰り返しではなく、方程式を解いて一発で答えを出しています。
「弾が 秒で進む距離」と「 秒後の敵までの距離」が等しい、と書くと
左右を展開して について整理すると、中学校で習う 2 次方程式になります。
ここで は内積で、 のことです。
解の公式 から出た 2 つの解のうち、
predict_lead は正で小さいほう(=いちばん早く当たる時刻)を採ります。
正の解がない、または 10 秒を超える場合は「当てられない」と判断し、敵の現在位置を返します。
- 壁ではね返る弾で、まっすぐ見えない敵を狙う → 跳弾のしくみ
- ここで使った関数が書ける Python の範囲 → Sub-Python の構文