コマンドで偏差射撃の予測位置を計算する

この記事は [Minecraft Command Advent Calendar 2023](https://qiita.com/advent-calendar/2023/mccmd) 17 日目の記事です。

# はじめに
この記事では私が作成している飛行機データパックにおいて、偏差射撃の位置予測をするアルゴリズムについて解説します。
動作としては以下のような動画みたいに飛んでいる飛行機のどれくらい先を撃てば弾が当たるかを表示してくれます。

https://twitter.com/uran2471/status/1730887210327843004

記事内に記載されているコードは飛行機データパック用に最適化されていますので、別のデータパック等に実装する際は適宜書き換えが必要となります。

# 背景
私が作っているマインクラフトに戦闘機を追加するデータパックにおいて、敵機を射撃する際の偏差が分かりにくいという問題がありました。そのため、偏差射撃の位置を予測するアルゴリズムを実装し射撃の難易度を下げることを試みます。

# 偏差射撃とは
移動している物体を射撃する際に着弾までのラグを見越して少し先の位置を撃つことを偏差射撃と呼びます。通常は射手の目視によって予測位置を判定して実施するため、高い技量が求められます。

# 偏差射撃のアルゴリズム
### 前提
偏差射撃を作るにあたり以下の前提を置いて実装していきます。
* 攻撃対象は直線移動をしているものと仮定する
* 攻撃対象の移動速度は一定であり変化しない
* 弾丸の速度は一定で空気抵抗等で減速しない

また、マインクラフトのコマンドの性質上以下の制約も付けます
* ベクトル計算等の数値計算を極力行わない(コマンドでの浮動小数演算のコストが高すぎるため)

### アルゴリズム
アルゴリズムは以下のステップになります。
1. n tick後の敵機の予測位置p<sub>n</sub>を計算
1. p<sub>n</sub>からn tickで弾が到達できる範囲d<sub>n</sub>に自機がいるか確認
1. 自機がいた場合p<sub>n</sub>が偏差射撃位置である
1. 自機がいなかった場合nを+1してstep1に戻る

上記アルゴリズムを図にするとこんな感じになります。
![hensa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3589337/3572a85f-de11-39ef-08e9-abe6d5cb4607.png)
各tickで敵機の未来位置と弾の到達範囲より、弾が敵機に最初に届くtickを判定します。するとそのtickにおける敵機の未来位置が偏差射撃の位置ということになります。

# 飛行機データパックへの実装
前段で示したアルゴリズムを飛行機データパックに実装してみます。せっかくなので最近追加された要素のマクロを活用してみます。
```text:fcs.mcfunction
# ターゲットを選定
execute as @e[distance=0.1..,sort=nearest,limit=1] run tag @s add fcs-target

# ターゲットの移動速度と方向取得
data remove storage voxel-planes:input input
execute store result storage voxel-planes:input input.enemy_speed double 1 run scoreboard players get @s vp.speed

# 弾の到達範囲取得
data modify storage voxel-planes:input input.reachable_range set from storage _.weapon.data.speed

# 自分の弾速取得
execute store result score #bullet-speed vp.input run data get storage _.weapon.data.speed 10

#再帰のステップ数初期化
scoreboard players set #recursive-step vp.reg1 0

# ターゲットの移動速度と自分の弾速から交点を出す
execute at @e[tag=fcs-target] run function plane:weapon/util/gun-fcs/aiming-recursive with storage voxel-planes:input input

# 交点方向にパーティクル表示
execute rotated as @s as 0-0-0-0-4 positioned 0.0 1.0 0.0 unless entity @s[distance=..1] positioned as @s positioned ~ ~3.625 ~ run function plane:weapon/util/gun-fcs/particle

# 終了処理
tag @e[tag=fcs-target] remove fcs-target
tp 0-0-0-0-4 0.0 1.0 0.0
```

```text:aiming-recursive.mcfunction
# 移動先が弾の到着範囲内に入っていたら目印をつける
$execute positioned ^ ^ ^$(enemy_speed) if entity @s[distance=..$(reachable_range)] run tp 0-0-0-0-4 ~ ~ ~

# 弾の到達範囲更新
$data modify storage minecraft:plane-datapack temporary.new_bullet_speed set value $(reachable_range)
execute store result score #new-bullet-speed vp.reg1 run data get storage minecraft:plane-datapack temporary.new_bullet_speed 10
execute store result storage voxel-planes:input input.reachable_range double 0.1 run scoreboard players operation #new-bullet-speed vp.reg1 += #bullet-speed vp.input

# 再帰
scoreboard players add #recursive-step vp.reg1 1
$execute if score #recursive-step vp.reg1 matches ..20 positioned ^ ^ ^$(enemy_speed) unless entity @s[distance=..$(reachable_range)] run function plane:weapon/util/gun-fcs/aiming-recursive with storage voxel-planes:input input
```

余談ですがファイル内にちらほらみられるfcsという単語はfire controll sysyemの略で兵器に置いて射撃の偏差位置などの計算をしてくれるシステムのことです。

# 動作のデモ
実際に動かしてみた結果がこちらになります。

https://twitter.com/uran2471/status/1730887210327843004

パーティクルが見にくいのでその辺りは要改善ですね。

# まとめ
コマンドで偏差射撃の予測位置を表示する仕組みを作ってみました。
通常のゲームならベクトルの演算位置を予測するかと思いますが、Minecraftのコマンドは四則演算の機能が弱いため別の方法で実装しています。
似たような機構を作ろうとしている人がいたら参考になれば幸いです。

また、今回の題材にして飛行機データパックは以下のサイトで配布しております。もしよろしければ遊んでみてください。
https://www.planetminecraft.com/data-pack/war-plane-datapack/