はじめに
ディスプレイエンティティって便利ですよね。
モデルも大きさも自由に変えられるし。
今回はそんなディスプレイを使って、こんなレーザービームを作ってみましょう。
この記事は自分の試行錯誤の過程を辿りながら解説していきます。
結論だけ知りたい方は #上手くいく方法 から見てください。
レーザー召喚
赤色の色付きガラスを細長く引き伸ばすとレーザーっぽく見えると思うので今回はそのモデルを使います。
このコマンドでlaser
というタグが付いた赤色の色付きガラス
のブロックディスプレイが召喚されます。
summon minecraft:block_display ~ ~ ~ {Tags:["laser"],block_state:{Name:"red_stained_glass"}}
続いて、このコマンドでディスプレイのscale
を変更して細長くしましょう。
data modify entity @e[tag=laser,limit=1] transformation.scale set value [0.1f,0.1f,64f]
レーザーっぽいですよね?
もっと凝るならitem_display
でカスタムモデルを表示しても良いです。
エンティティの向きで方向を合わせてみる
ディスプレイは普通のエンティティと同じ様にtpコマンドで位置や向きを変えることができます。
ディスプレイをプレイヤーの頭の位置と向きにtpさせてみましょう。
execute as @p at @s anchored eyes run tp @e[tag=laser] ^ ^ ^ ~ ~
試しに遠くのアマスタを狙いながらこのコマンドを実行してみると…
これは私がクソエイムだからではないのです。
その証拠に、ディスプレイの位置と向きから再帰ファンクションでパーティクルの線を表示してみると、ちゃんとアマスタに当たっています。
しかし、ディスプレイの見た目はそれから大きくズレてしまっています。
実はこの現象には、ディスプレイだけではないマイクラのエンティティ全般のとある仕様が関わっています。
ざっくり言うと、エンティティの表示上の角度は一周256分割しかされておらず、360°/256≒1.4°
程度の分解能しかないのです。
(もう少し踏み入った話をすると、シングルプレイであってもマイクラのアプリケーションは内部的にサーバとクライアントに分かれて動作しており、サーバからクライアントへ通信する角度データが256通りの表現しかできないbyte型で表されていることが原因です。1)
エンティティの向きで合わせる方法は上手くいかなさそうです。
transformationで方向を合わせてみる
ディスプレイのNBTのtransformation
中の、モデルの回転を表すleft_rotation
とright_rotation
ならエンティティの向きよりも正確に回転をさせられます。
これを利用して向きを合わせる方法を試してみましょう。
その前に一度、ディスプレイを召喚しなおしてください。
kill @e[tag=laser]
summon minecraft:block_display ~ ~ ~ {Tags:["laser"],block_state:{Name:"red_stained_glass"}}
left_rotation
とright_rotation
は、軸角形式
と四元数形式
の二種類の形式で回転を表すことができますが、今回は扱いやすい軸角形式
の方をメインに扱います。
軸角形式
は{angle:0.785,axis:[0f,1f,0f]}
の様な形式で、axis
のベクトルを回転軸としたangle
の角度の回転を表します。
axis
のベクトルは長さが1(単位ベクトル)である必要があること、angle
の角度は度数法
ではなくラジアン
であること、軸角形式
で書き込んでも四元数形式
に自動的に変換されて保存されること、などに注意してください。
ところで、プレイヤーを含むエンティティの向きはRotation:[45f,-20f]
の様に横回転と縦回転の二つのパラメータで回転を表すオイラー角
の形式で保存されていますが、オイラー角
を軸角形式
に変換するためには本来計算が必要になります。
幸いtransformation
にはleft_rotation
とright_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
軸角形式
ではangle
とaxis
を同時に書き込む必要があるため、一度ストレージ上にデータを作ってからディスプレイにコピーをしています。
store result
時の倍率0.000001745
は、度数法からラジアンに変換するため掛ける円周率/180≒0.01745
という数値を、data get
時の倍率10000
で割った値です。
上のコマンドを常時実行すると、プレイヤーの頭の向きに綺麗に追従させることができました。
あとはscale
を変更してレーザーらしく細長くするだけで…?
あれ?形が変になってしまいました。
原因はright_rotation
を使ってしまったことにあります。
そもそも何故transformation
にleft_rotation
とright_rotation
の二種類の回転パラメータが用意されているのかというと、scale
に対する影響に違いがあるためなのです。
left_rotation
はscale
の変形の仕方に影響を及ぼしませんが、right_rotation
はscale
の変形の仕方に影響を及ぼします。
そのおかげでせん断
という斜めに押しつぶす様な変形が可能になるのですが、今回は変形してしまうと困ります。
どうしたものでしょうか…?
上手くいく方法
とうとう本命の上手くいく方法です。
今までの方法では、初期状態で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]
ここまでのコマンドを実行してみると
お!無事にレーザーが形を保ったままプレイヤーの頭の向きに追従しています!
しかし、よく見ると伸びている方向を軸に余計な回転をしてしまっているので、これも直してしまいましょう。
この様に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]
でX
とZ
の倍率が同じです。
回転で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]
データパックとして実装する
せっかくなのでデータパックとしての体裁を整えてみます。
スニークすると目からビームが出る仕様にしました。
tick.json
に登録された毎tick実行されるtick.mcfunction
です。
executeテクニックのスニーク検知
を利用しています。
# 計算用マーカー召喚
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
です。
# 近くにディスプレイが無ければ召喚
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
です。
# ディスプレイを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
おわりに
今回作ったデータパックのサンプルは ここ に置いておきました。
ご自由にお使いください。
お好みでモデルの変更をしたり、演出や攻撃判定の追加といった改良をしたり、またレーザービーム以外にも応用ができると思います。
皆さんのコマンド開発のお役に立てば幸いです。