LoginSignup
5
1

ディスプレイでレーザービームを撃とう

Last updated at Posted at 2023-12-14

はじめに

ディスプレイエンティティって便利ですよね。
モデルも大きさも自由に変えられるし。

今回はそんなディスプレイを使って、こんなレーザービームを作ってみましょう。
Replay 2023-12-10 16-24-56.gif

この記事は自分の試行錯誤の過程を辿りながら解説していきます。
結論だけ知りたい方は #上手くいく方法 から見てください。

レーザー召喚

赤色の色付きガラスを細長く引き伸ばすとレーザーっぽく見えると思うので今回はそのモデルを使います。
このコマンドでlaserというタグが付いた赤色の色付きガラスのブロックディスプレイが召喚されます。

summon minecraft:block_display ~ ~ ~ {Tags:["laser"],block_state:{Name:"red_stained_glass"}}

2023-12-10_18.06.20.png

続いて、このコマンドでディスプレイのscaleを変更して細長くしましょう。

data modify entity @e[tag=laser,limit=1] transformation.scale set value [0.1f,0.1f,64f]

2023-12-10_18.07.05.png
レーザーっぽいですよね?
もっと凝るならitem_displayでカスタムモデルを表示しても良いです。

エンティティの向きで方向を合わせてみる

ディスプレイは普通のエンティティと同じ様にtpコマンドで位置や向きを変えることができます。
ディスプレイをプレイヤーの頭の位置と向きにtpさせてみましょう。

execute as @p at @s anchored eyes run tp @e[tag=laser] ^ ^ ^ ~ ~

2023-12-10_18.12.43.png
額からレーザーが出てるみたいになりましたね。

2023-12-10_18.15.35.png
試しに遠くのアマスタを狙いながらこのコマンドを実行してみると…

2023-12-10_18.16.10.png
あれ?外れました。もう一度…

2023-12-10_18.17.12.png
…当たりません。

これは私がクソエイムだからではないのです。
その証拠に、ディスプレイの位置と向きから再帰ファンクションでパーティクルの線を表示してみると、ちゃんとアマスタに当たっています。
しかし、ディスプレイの見た目はそれから大きくズレてしまっています。
2023-12-10_18.38.27.png

実はこの現象には、ディスプレイだけではないマイクラのエンティティ全般のとある仕様が関わっています。
ざっくり言うと、エンティティの表示上の角度は一周256分割しかされておらず、360°/256≒1.4°程度の分解能しかないのです。
(もう少し踏み入った話をすると、シングルプレイであってもマイクラのアプリケーションは内部的にサーバとクライアントに分かれて動作しており、サーバからクライアントへ通信する角度データが256通りの表現しかできないbyte型で表されていることが原因です。1

エンティティの向きで合わせる方法は上手くいかなさそうです。

transformationで方向を合わせてみる

ディスプレイのNBTのtransformation中の、モデルの回転を表すleft_rotationright_rotationならエンティティの向きよりも正確に回転をさせられます。
これを利用して向きを合わせる方法を試してみましょう。

その前に一度、ディスプレイを召喚しなおしてください。

kill @e[tag=laser]

summon minecraft:block_display ~ ~ ~ {Tags:["laser"],block_state:{Name:"red_stained_glass"}}

left_rotationright_rotationは、軸角形式四元数形式の二種類の形式で回転を表すことができますが、今回は扱いやすい軸角形式の方をメインに扱います。
軸角形式{angle:0.785,axis:[0f,1f,0f]}の様な形式で、axisのベクトルを回転軸としたangleの角度の回転を表します。
axisのベクトルは長さが1(単位ベクトル)である必要があること、angleの角度は度数法ではなくラジアンであること、軸角形式で書き込んでも四元数形式に自動的に変換されて保存されること、などに注意してください。

ところで、プレイヤーを含むエンティティの向きはRotation:[45f,-20f]の様に横回転と縦回転の二つのパラメータで回転を表すオイラー角の形式で保存されていますが、オイラー角軸角形式に変換するためには本来計算が必要になります。
幸いtransformationにはleft_rotationright_rotationの二つの回転があるので、それぞれに横回転と縦回転を代入すれば計算を避けることができそうです。
left_rotation.axisを横回転の回転軸とし、left_rotation.angleにプレイヤーの向きの横角度Rotation[0]を代入、right_rotation.axisを縦回転の回転軸とし、right_rotation.angleにプレイヤーの向きの縦角度Rotation[1]を代入しましょう。

# 回転軸設定
data modify storage laser: left_rotation set value {axis:[0f,-1f,0f]}
data modify storage laser: right_rotation set value {axis:[1f,0f,0f]}

# 角度代入
execute store result storage laser: left_rotation.angle float 0.000001745 run data get entity @p Rotation[0] 10000
execute store result storage laser: right_rotation.angle float 0.000001745 run data get entity @p Rotation[1] 10000

# ストレージからディスプレイにコピー
execute as @e[tag=laser] run data modify entity @s transformation.left_rotation set from storage laser: left_rotation
execute as @e[tag=laser] run data modify entity @s transformation.right_rotation set from storage laser: right_rotation

軸角形式ではangleaxisを同時に書き込む必要があるため、一度ストレージ上にデータを作ってからディスプレイにコピーをしています。
store result時の倍率0.000001745は、度数法からラジアンに変換するため掛ける円周率/180≒0.01745という数値を、data get時の倍率10000で割った値です。

Replay 2023-12-10 19-36-33.gif
上のコマンドを常時実行すると、プレイヤーの頭の向きに綺麗に追従させることができました。

あとはscaleを変更してレーザーらしく細長くするだけで…?
2023-12-10_20.28.44.png
あれ?形が変になってしまいました。

原因はright_rotationを使ってしまったことにあります。
そもそも何故transformationleft_rotationright_rotationの二種類の回転パラメータが用意されているのかというと、scaleに対する影響に違いがあるためなのです。
left_rotationscaleの変形の仕方に影響を及ぼしませんが、right_rotationscaleの変形の仕方に影響を及ぼします。
そのおかげでせん断という斜めに押しつぶす様な変形が可能になるのですが、今回は変形してしまうと困ります。

どうしたものでしょうか…?

上手くいく方法

とうとう本命の上手くいく方法です。

今までの方法では、初期状態でRotation:[0f,0f]方向に伸びているディスプレイに、オイラー角に合わせて縦横二回の回転操作を行うことで方向を合わせていました。
発想を転換して、初期状態で真上方向に伸びたディスプレイを横に倒す一回の回転操作で方向を合わせてみましょう。

まず、このコマンドでディスプレイを真上方向に細長く変形しましょう。

data modify entity @e[tag=laser,limit=1] transformation.scale set value [0.1f,64f,0.1f]

プレイヤーの向きの真横方向が倒す回転軸となるので、下のコマンドで計算用マーカーがtpする座標から回転軸ベクトルが分かります。

# 計算用マーカー召喚
summon marker ~ ~ ~ {Tags:["laser_marker"]}

# 原点からプレイヤーの向きの真横1ブロックへマーカーを移動
execute rotated as @p positioned 0.0 0.0 0.0 run tp @e[tag=laser_marker,limit=1] ^1 ^ ^ ~ ~

# マーカーの座標をストレージに代入
data modify storage laser: left_rotation.axis set from entity @e[tag=laser_marker,limit=1] Pos

倒す角度は、プレイヤーの縦角度が-90°の時は0°、縦角度が0°の時は90°、縦角度が90°の時は180°であり、縦角度に+90すれば良いことが分かります。
スコアボード上で+90の計算をしても良いのですが、今回はせっかくなのでスコアボードを使わずにexecute幾何学を使って計算してみます。
縦角度から横角度への変換テクニックを利用してマーカーの縦角度[-90~90]マーカーの横角度[0~180]に変換し、その横角度をleft_rotation.angleに代入しましょう。

# execute幾何学で、縦角度[-90~90]⇒横角度[0~180]に変換
execute rotated as @p rotated -90 ~ positioned 0.0 0.0 0.0 positioned ^ ^ ^1 rotated ~90 ~ positioned ^ ^1 ^ facing 0.0 ~ 0.0 run tp @e[tag=laser_marker,limit=1] 0.0 0.0 0.0 ~ ~

# マーカーの横角度をストレージに代入
execute store result storage laser: left_rotation.angle float 0.000001745 run data get entity @e[tag=laser_marker,limit=1] Rotation[0] 10000

ストレージ上のデータをディスプレイにコピーしましょう。

# ストレージからディスプレイに書き込み
execute as @e[tag=laser] run data modify entity @s transformation.left_rotation set from storage laser: left_rotation

忘れずに計算用マーカーの削除もしておきます。

# 計算用マーカー削除
kill @e[tag=laser_marker]

ここまでのコマンドを実行してみると
Replay 2023-12-14 21-27-53.gif
お!無事にレーザーが形を保ったままプレイヤーの頭の向きに追従しています!

しかし、よく見ると伸びている方向を軸に余計な回転をしてしまっているので、これも直してしまいましょう。

この様にright_rotationを利用することで回転を打ち消すことができます。

# マーカーの向きをプレイヤーと同じ向きに
execute rotated as @p positioned 0.0 0.0 0.0 run tp @e[tag=laser_marker,limit=1] ~ ~ ~ ~ ~

# マーカーの横角度をストレージに代入
execute store result storage laser: right_rotation.angle float 0.000001745 run data get entity @e[tag=laser_marker,limit=1] Rotation[0] 10000

# 回転軸設定
data modify storage laser: right_rotation set value {axis:[0f,-1f,0f]}

# ストレージからディスプレイに書き込み
execute as @e[tag=laser] run data modify entity @s transformation.right_rotation set from storage laser: right_rotation

あれ?right_rotationを使うと変形しちゃうんじゃなかったの?と思われたかもしれません。
今回のright_rotation.axis[0f,-1f,0f]Y軸方向を回転軸としているため、ブロックの頂点はX軸Z軸方向にのみ移動します。
そしてscale[0.1f,64f,0.1f]XZの倍率が同じです。
回転でXとZ軸方向に動きますが、XとZ軸方向の倍率が同じであるため、変形しないのです。

ここまでのコマンドを整理しつつ纏めるとこのようになります。

# 計算用マーカー召喚
summon marker ~ ~ ~ {Tags:["laser_marker"]}

# 原点からプレイヤーの向きの真横1ブロックへマーカーを移動 & 同じ向きに
execute rotated as @p positioned 0.0 0.0 0.0 run tp @e[tag=laser_marker,limit=1] ^1 ^ ^ ~ ~

# マーカーの座標をストレージに代入
data modify storage laser: left_rotation.axis set from entity @e[tag=laser_marker,limit=1] Pos

# マーカーの横角度をストレージに代入
execute store result storage laser: right_rotation.angle float 0.000001745 run data get entity @e[tag=laser_marker,limit=1] Rotation[0] 10000

# 回転軸設定
data modify storage laser: right_rotation.axis set value [0f,-1f,0f]

# execute幾何学で、縦角度[-90~90]⇒横角度[0~180]に変換
execute rotated as @p rotated -90 ~ positioned 0.0 0.0 0.0 positioned ^ ^ ^1 rotated ~90 ~ positioned ^ ^1 ^ facing 0.0 ~ 0.0 run tp @e[tag=laser_marker,limit=1] 0.0 0.0 0.0 ~ ~

# マーカーの横角度をストレージに代入
execute store result storage laser: left_rotation.angle float 0.000001745 run data get entity @e[tag=laser_marker,limit=1] Rotation[0] 10000

# ストレージからディスプレイに書き込み
execute as @e[tag=laser] run data modify entity @s transformation.left_rotation set from storage laser: left_rotation
execute as @e[tag=laser] run data modify entity @s transformation.right_rotation set from storage laser: right_rotation

# 計算用マーカー削除
kill @e[tag=laser_marker]

Replay 2023-12-14 21-51-39.gif
これで完璧になりました。

データパックとして実装する

せっかくなのでデータパックとしての体裁を整えてみます。
スニークすると目からビームが出る仕様にしました。

tick.jsonに登録された毎tick実行されるtick.mcfunctionです。
executeテクニックのスニーク検知を利用しています。

tick.mcfunction
# 計算用マーカー召喚
summon marker ~ ~ ~ {Tags:["laser_marker"]}

# スニークしているプレイヤーにファンクションを実行
execute as @a at @s anchored eyes positioned ^ ^ ^ positioned ~ ~-1.27 ~ if entity @s[distance=..0.0001] at @s anchored eyes positioned ^0.05 ^0.15 ^ positioned ~ ~-0.2 ~ run function laser:every_player

# 計算用マーカー削除
kill @e[tag=laser_marker]

# 処理されなかったディスプレイをkill
kill @e[tag=laser,tag=!laser_rotated]

# 処理済みタグ削除
tag @e[tag=laser] remove laser_rotated

各プレイヤーを実行者として、tick.mcfunctionから実行されるevery_player.mcfunctionです。

every_player.mcfunction
# 近くにディスプレイが無ければ召喚
execute unless entity @e[tag=laser,tag=!laser_rotated,limit=1,distance=..1] run summon minecraft:block_display ~ ~ ~ {Tags:["laser"],block_state:{Name:"red_stained_glass"},interpolation_duration:1,teleport_duration:1,transformation:{right_rotation:{axis:[0f,-1f,0f],angle:0},scale:[0.1f,50f,0.1f],left_rotation:{axis:[1f,0f,0f],angle:0},translation:[0f,0f,0f]},brightness:{block:15,sky:15}}

# 近くのディスプレイにファンクション実行
execute as @e[tag=laser,tag=!laser_rotated,sort=nearest,limit=1,distance=..1] run function laser:every_display

プレイヤーの近くのディスプレイを実行者として、every_player.mcfunctionから実行されるevery_display.mcfunctionです。

every_display.mcfunction
# ディスプレイをtp
tp @s ~ ~ ~

# execute幾何学で、縦角度[-90~90]⇒横角度[0~180]に変換して、倒す角度特定
execute rotated -90 ~ positioned 0.0 0.0 0.0 positioned ^ ^ ^1 rotated ~90 ~ positioned ^ ^1 ^ facing 0.0 ~ 0.0 run tp @e[tag=laser_marker,limit=1] 0.0 0.0 0.0 ~ ~
execute store result storage laser: left_rotation.angle float 0.000001745 run data get entity @e[tag=laser_marker,limit=1] Rotation[0] 10000

# 倒す軸方向ベクトル特定
execute positioned 0.0 0.0 0.0 run tp @e[tag=laser_marker,limit=1] ^1 ^ ^ ~ ~
data modify storage laser: left_rotation.axis set from entity @e[tag=laser_marker,limit=1] Pos

# right_rotationの角度を特定
execute store result storage laser: right_rotation.angle float 0.000001745 run data get entity @e[tag=laser_marker,limit=1] Rotation[0] 10000
data modify storage laser: right_rotation.axis set value [0f,-1f,0f]

# 変形反映
data modify entity @s transformation.left_rotation set from storage laser: left_rotation
data modify entity @s transformation.right_rotation set from storage laser: right_rotation

# ブロックディスプレイのアニメーション補完のバグ回避のためにshadow_radiusを書き換えている
# アイテムディスプレイの場合は必要ない
execute store success entity @s shadow_radius float -1 if data entity @s {shadow_radius:0f}

# アニメーション補完
data modify entity @s start_interpolation set value 0

# 処理済みタグ付与
tag @s add laser_rotated

Replay 2023-12-10 16-24-56.gif
これでスニークすると目からビームが出せるようになりました。

おわりに

今回作ったデータパックのサンプルは ここ に置いておきました。
ご自由にお使いください。
お好みでモデルの変更をしたり、演出や攻撃判定の追加といった改良をしたり、またレーザービーム以外にも応用ができると思います。
皆さんのコマンド開発のお役に立てば幸いです。

  1. ソース:https://wiki.vg/Protocol#Definitions:angle

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1