はじめに
カスタムエンチャントは実験的機能です。
基本的な内容は、過去の自分の記事に書いています。
カスタムエンチャントでできることはとても多く、全て紹介するのは大変なので、今回は銃の製作を例として紹介していきます。
エンチャントが全く分からない人も、「こんなことができるんだ、へぇ~」くらいに聞き流してもらえればなと思います。
※これは銃を作る事が目的ではなくエンチャントを使う事を目的にしています。
実際に記述する際は、misode氏のエンチャントジェネレーターが使いやすくおすすめです。
Java版 Minecraft 1.21.3 時点の情報です
本記事ではバグの挙動を利用している場合があります
カスタムエンチャントとは
エンチャントを自由にカスタマイズできる1.21から追加されたデータパックの新要素です。
バニラエンチャントの拡張要素だけでなく、バニラには無いエンチャントやコマンドと合わせた使い方ができ、全く新しいエンチャントを作ることができます。
エンチャント基本知識
氷渡りのついた防具を装備しているゾンビは水を凍らせたり、パンチのついた弓から放った矢は当てるとノックバックが増加するように、エンチャントはプレイヤー以外のリビングエンティティや発射された矢にも付与できます。
矢のエンティティデータにはwepon
タグがあり、何のエンチャントされた武器から放たれたかという情報が保持されています。
リビングエンティティにはゾンビやスケルトンのようなMOBだけでなく、アーマースタンドも含まれます。
弾の制作
コマンドで銃弾を正確に再現するとき、基本的に再帰関数を使用すると思いますが、処理が多くなったり角抜けしたりと正確な判定を取るのが難しくもあります。
今回はエンチャントを使用することもあり弾には矢を使用します。
矢を使うことで、角抜けや当たり判定が正確になり、誰が攻撃したかなどが分かりやすくなります。
矢の見た目
矢を使うにあたって問題になるのが、矢が見えることです。
銃を撃っていて矢が出てくるのはおかしいので消して使います。
リソースパックで矢のテクスチャを消すことで解決できるのですが、普通に使う矢は見えるようにしたいので少し工夫します。
リソースパックにはarrow
(矢)とtipped_arrow
(効果付きの矢)の二種類あり、tipped_arrow
の方のテクスチャを消します。
デフォルトでは効果付きの矢もarrow
のテクスチャを使用しているので影響はありません。
しかし、コンポーネントのpotion_contents
のcustom_color
を指定した時のみtipped_arrow
のテクスチャを参照します。
矢がヒットした音やブロックに刺さった音を出したくない場合は、Silent:true
も追加します。
summon arrow ~ ~ ~ {item:{id:"arrow",components:{potion_contents:{custom_color:1}}}}
注意として、ブロックに刺さってから30秒で通常の矢に戻ります。
今回は着弾したときにキルするため問題ないです。
特定の矢だけ見えなくする pic.twitter.com/5xwpXWWBkV
— ひろばお🙃コマンド (@Hirobao1) December 25, 2024
刺さらない矢
矢はプレイヤーに撃つと、体に刺さった矢が表示されてしまいます。
これはカスタムエンチャントで解決できます。
使用するエンチャント効果はprojectile_piercing
とpost_attack
です。
projectile_piercing
は貫通のエンチャントに使われているものです。
発射物に貫通する効果を持たせることができます。(召喚された矢のPierceLevel
が上がる)
貫通する矢はプレイヤーに刺さることはありません。
コマンドで矢を召喚する場合は、エンチャントではなく矢にPierceLevel
タグをつけます。
post_attack
は火属性や棘の鎧のエンチャントに使われているものです。
攻撃したことや、攻撃されたことをトリガーにいろいろな効果を付けることができます。
今回は矢がエンティティを攻撃した時コマンドを実行し、自身をキルするようにします。
ついでに、ブロックに刺さった時もキルするようにします。
これにはhit_block
のエンチャント効果を使います。
hit_block
は召雷のエンチャントに使われているものです。
ブロックに発射体が刺さった時や、ブロックを左クリックしたことをトリガーにいろいろな効果を付けることができます。
矢がエンティティとブロックに当たった時にキルするエンチャント
{
"description": "arrow_kill",
"supported_items": [],
"weight": 1,
"max_level": 1,
"min_cost": {
"base": 0,
"per_level_above_first": 0
},
"max_cost": {
"base": 0,
"per_level_above_first": 0
},
"anvil_cost": 0,
"slots": [
"hand"
],
"effects": {
"post_attack": [
{
"requirements": {
"condition": "entity_properties",
"entity": "direct_attacker",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "run_function",
"function": "kill"
},
"enchanted": "attacker",
"affected": "damaging_entity"
}
],
"hit_block": [
{
"requirements": {
"condition": "entity_properties",
"entity": "this",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "run_function",
"function": "kill"
}
}
]
}
}
エンチャントから実行するコマンド
kill @s
$execute at @s anchored eyes positioned ^ ^ ^0.3 run summon arrow ~ ~ ~ {LeftOwner:1b,crit:0b,PierceLevel:127b,item:{id:"arrow",components:{potion_contents:{custom_color:1}}},weapon:{id:"bow",components:{enchantments:{arrow_kill:1}}},Owner:$(UUID)}
実行時にマクロを使用し、矢のOwnerタグにプレイヤーのUUIDとMotionを指定します。
Motionの指定
Motionの取得方法は、マーカーエンティティを座標(0,0,0)の位置から掛けたいMotionの値だけ実行者の視線方向に移動させ、その位置がそのままMotionの値になります。
execute at @s summon marker run function motion
# Motion計算
execute positioned 0.0 0.0 0.0 run tp @s ^ ^ ^3
data modify storage adcl:motion Pos set from entity @s Pos
execute store result storage adcl:motion x double 0.01 run data get storage adcl:motion Pos[0] 100
execute store result storage adcl:motion y double 0.01 run data get storage adcl:motion Pos[1] 100
execute store result storage adcl:motion z double 0.01 run data get storage adcl:motion Pos[2] 100
# Marker消去
kill @s
弾のダメージ
次に弾のダメージを決めていきます。
矢のダメージ量は速度に比例します。
また、クリティカルの場合ランダムでダメージが加算されます。
矢はエンチャントなどでダメージを0にした場合、プレイヤーに刺さらず跳ね返ります。
そのため最低でも1のダメージは与える必要があります。
ここで使用するエンチャント効果はdamage
です。
damage
はダメージ増加や特攻系エンチャントに使われているものです。
エンティティに与えるダメージの値を変更できます。
矢のダメージ量の計算式はダメージ×速度(block/tick)で小数点以下は切り上げです。
矢の速度がある程度早くなっても1を超えないような小さな値にします。
{
"damage": [
{
"effect": {
"type": "set",
"value": 1e-8
}
}
]
}
クリティカルがあるとそこからダメージにランダムな値が加算されるので、召喚時には矢のcrit
タグをfalse
にしておきます。
ダメージ量を増加させたい場合は、post_attack
からdamage_entity
などを使用して追加の効果をつけます。
post_attack
はダメージを与えた時に追加で効果を付けるものです。
なので、damage
エンチャントによって1ダメージ与えた後に処理されます。
damage_entity
はエンティティにダメージを与える効果です。
ダメージ量は操作したいのでエンチャントレベルによって変化するようにします。
レベルによってdamageが変化するエンチャント
{
"post_attack": [
{
"requirements": {
"condition": "entity_properties",
"entity": "direct_attacker",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "damage_entity",
"damage_type": "arrow",
"min_damage": {
"type": "linear",
"base": 0,
"per_level_above_first": 1
},
"max_damage": {
"type": "linear",
"base": 0,
"per_level_above_first": 1
}
},
"enchanted": "attacker",
"affected": "victim"
}
]
}
エンチャントレベルが1の時は追加で0ダメージを与え、レベルが上がるごとに与えるダメージが1ずつ増えていきます。
damage
エンチャントで最初に1ダメージを与えているので、この矢はエンチャントレベルと同じ値のダメージ量になります。
パーティクルの表示
矢のテクスチャを消したため、銃を撃ったとしても何が起こっているのか分からない状態です。
そのため、弾道や弾が敵や壁に当たった時、パーティクルを表示して視覚的に分かるようにします。
使用するエンチャント効果は、壁や敵に当たった判定はpost_attack
とhit_block
を、弾道はtick
を使用します。
壁や敵に当たった時にパーティクル表示
{
"post_attack": [
{
"requirements": {
"condition": "entity_properties",
"entity": "direct_attacker",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "spawn_particles",
"particle": {
"type": "damage_indicator"
},
"horizontal_position": {
"type": "in_bounding_box",
"scale": 1
},
"vertical_position": {
"type": "in_bounding_box",
"offset": -0.25,
"scale": 0.5
},
"horizontal_velocity": {},
"vertical_velocity": {}
},
"enchanted": "attacker",
"affected": "victim"
}
],
"hit_block": [
{
"requirements": {
"condition": "entity_properties",
"entity": "this",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "spawn_particles",
"particle": {
"type": "smoke"
},
"horizontal_position": {
"type": "entity_position"
},
"vertical_position": {
"type": "entity_position"
},
"horizontal_velocity": {},
"vertical_velocity": {}
}
}
]
}
tick
はいろいろな効果を毎tick実行できます。
今回は毎tickパーティクルを表示するために使いますが、requirements
でいろんな条件による弾の制御にも利用でき、撃った後に矢の軌道を変えたり、ダメージを変更したりなどにも使えたりします。
tick
エンチャントは矢単体ではできないのでアーマースタンドを使用します。
矢の召喚時にエンチャントを持ったアマスタを矢に乗せて召喚します。
弾道パーティクル表示エンチャント
{
"description": "弾道表示",
"supported_items": [],
"weight": 1,
"max_level": 1,
"min_cost": {
"base": 0,
"per_level_above_first": 0
},
"max_cost": {
"base": 0,
"per_level_above_first": 0
},
"anvil_cost": 0,
"slots": [
"armor"
],
"effects": {
"tick": [
{
"effect": {
"type": "spawn_particles",
"particle": {
"type": "crit"
},
"horizontal_position": {
"type": "entity_position"
},
"vertical_position": {
"type": "entity_position",
"offset": -0.5
},
"horizontal_velocity": {},
"vertical_velocity": {}
}
},
{
"requirements": {
"condition": "inverted",
"term": {
"condition": "entity_properties",
"entity": "this",
"predicate": {
"type": "armor_stand",
"vehicle": {
"type": "arrow"
}
}
}
},
"effect": {
"type": "run_function",
"function": "kill"
}
}
]
}
}
バグでアマスタだけ残らないように矢に乗っていないアマスタに対してkillする処理も入れています。
次は矢の方に掛かるエンチャントです。
エンティティに当たった時にはpost_attack
でdamage_indicator
パーティクルを、壁に当たった時はhit_block
でsmoke
パーティクルを表示します。
矢がヒット時のパーティクル表示エンチャント
{
"post_attack": [
{
"requirements": {
"condition": "entity_properties",
"entity": "direct_attacker",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "spawn_particles",
"particle": {
"type": "damage_indicator"
},
"horizontal_position": {
"type": "in_bounding_box",
"scale": 1
},
"vertical_position": {
"type": "in_bounding_box",
"offset": -0.25,
"scale": 0.5
},
"horizontal_velocity": {},
"vertical_velocity": {}
},
"enchanted": "attacker",
"affected": "victim"
}
],
"hit_block": [
{
"requirements": {
"condition": "entity_properties",
"entity": "this",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "spawn_particles",
"particle": {
"type": "smoke"
},
"horizontal_position": {
"type": "entity_position"
},
"vertical_position": {
"type": "entity_position"
},
"horizontal_velocity": {},
"vertical_velocity": {}
}
}
]
}
エンチャントから実行するコマンド
execute on passengers run kill @s
kill @s
矢からアマスタをキルするために1行目を追加しました。
矢と防具立てを召喚するコマンド
$execute at @s anchored eyes positioned ^ ^ ^0.3 run summon arrow ~ ~ ~ {LeftOwner:1b,crit:0b,Silent:1b,PierceLevel:127b,item:{id:"arrow",components:{potion_contents:{custom_color:1}}},weapon:{id:"bow",components:{enchantments:{arrow_gun:1}}},Motion:[$(x)d,$(y)d,$(z)d],Owner:$(UUID),Passengers:[{id:armor_stand,Silent:1b,Marker:1b,Invisible:1b,ArmorItems:[{id:"stone",count:1,components:{enchantments:{"arrow_particle":1}}},{},{},{}]}]}
item
コマンドなどでは、MOBの頭以外の部分には防具しか装備させられませんが、召喚する時には防具以外のアイテムも入れられ、見えないアイテムかつエンチャントを付与できます。(item_model
コンポーネントの"air"
でも可)
アマスタとは別に弾の見た目用に、アイテムディスプレイ等を乗せてもいいのですが、矢の速度が速すぎると表示位置がずれるバグがあるので注意してください。
ファイルのまとめ
いままで1つずつ説明してきましたが、エンチャントの数が多いので全てを1つのエンチャントにまとめます。
requirements
で矢やアマスタ等の条件を付けることで、複数のエンチャントを1つにまとめることができます。
全てをまとめたエンチャント
{
"description": "銃のエンチャント",
"supported_items": [],
"weight": 1,
"max_level": 255,
"min_cost": {
"base": 0,
"per_level_above_first": 0
},
"max_cost": {
"base": 0,
"per_level_above_first": 0
},
"anvil_cost": 0,
"slots": [
"any"
],
"effects": {
"post_attack": [
{
"requirements": {
"condition": "entity_properties",
"entity": "direct_attacker",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "damage_entity",
"damage_type": "arrow",
"min_damage": {
"type": "linear",
"base": 0,
"per_level_above_first": 1
},
"max_damage": {
"type": "linear",
"base": 0,
"per_level_above_first": 1
}
},
"enchanted": "attacker",
"affected": "victim"
},
{
"requirements": {
"condition": "entity_properties",
"entity": "direct_attacker",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "spawn_particles",
"particle": {
"type": "damage_indicator"
},
"horizontal_position": {
"type": "in_bounding_box",
"scale": 1
},
"vertical_position": {
"type": "in_bounding_box",
"offset": -0.25,
"scale": 0.5
},
"horizontal_velocity": {},
"vertical_velocity": {}
},
"enchanted": "attacker",
"affected": "victim"
},
{
"requirements": {
"condition": "entity_properties",
"entity": "direct_attacker",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "run_function",
"function": "kill"
},
"enchanted": "attacker",
"affected": "damaging_entity"
}
],
"hit_block": [
{
"requirements": {
"condition": "entity_properties",
"entity": "this",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "run_function",
"function": "kill"
}
},
{
"requirements": {
"condition": "entity_properties",
"entity": "this",
"predicate": {
"type": "arrow"
}
},
"effect": {
"type": "spawn_particles",
"particle": {
"type": "smoke"
},
"horizontal_position": {
"type": "entity_position"
},
"vertical_position": {
"type": "entity_position"
},
"horizontal_velocity": {},
"vertical_velocity": {}
}
}
],
"damage": [
{
"effect": {
"type": "set",
"value": 1e-8
}
}
],
"tick": [
{
"requirements": {
"condition": "entity_properties",
"entity": "this",
"predicate": {
"type": "armor_stand"
}
},
"effect": {
"type": "spawn_particles",
"particle": {
"type": "crit"
},
"horizontal_position": {
"type": "entity_position"
},
"vertical_position": {
"type": "entity_position",
"offset": -0.5
},
"horizontal_velocity": {},
"vertical_velocity": {}
}
},
{
"requirements": {
"condition": "all_of",
"terms": [
{
"condition": "entity_properties",
"entity": "this",
"predicate": {
"type": "armor_stand"
}
},
{
"condition": "inverted",
"term": {
"condition": "entity_properties",
"entity": "this",
"predicate": {
"vehicle": {
"type": "arrow"
}
}
}
}
]
},
"effect": {
"type": "run_function",
"function": "kill"
}
}
]
}
}
今回作成したfunctionも下記にまとめて書いておきます。
全functionファイル
execute on passengers run kill @s
kill @s
# Motion計算
execute positioned 0.0 0.0 0.0 run tp @s ^ ^ ^3
data modify storage adcl:motion Pos set from entity @s Pos
execute store result storage adcl:motion x double 0.01 run data get storage adcl:motion Pos[0] 100
execute store result storage adcl:motion y double 0.01 run data get storage adcl:motion Pos[1] 100
execute store result storage adcl:motion z double 0.01 run data get storage adcl:motion Pos[2] 100
# Marker消去
kill @s
$execute at @s anchored eyes positioned ^ ^ ^0.3 run summon arrow ~ ~ ~ {LeftOwner:1b,crit:0b,Silent:1b,PierceLevel:127b,item:{id:"arrow",components:{potion_contents:{custom_color:1}}},weapon:{id:"bow",components:{enchantments:{arrow_gun:1}}},Motion:[$(x)d,$(y)d,$(z)d],Owner:$(UUID),Passengers:[{id:armor_stand,Silent:1b,Marker:1b,Invisible:1b,ArmorItems:[{id:"stone",count:1,components:{enchantments:{"arrow_particle":1}}},{},{},{}]}]}
上記のエンチャントとコマンドを入れた後、次のfunctionをプレイヤーを実行者として実行することで弾が発射されます。
playsound entity.firework_rocket.blast master @a ~ ~ ~ 0.5 0.5 0
data modify storage adcl:motion UUID set from entity @s UUID
execute at @s summon marker run function motion
function summon with storage adcl:motion
さいごに
今回は銃を作るという例でカスタムエンチャントを紹介しましたが、まだまだできることは多く想像次第でいろいろな使い方ができます。
自分は以前にカスタムエンチャントのexplotion
(爆発する効果)を使用してプレイヤーに任意の値のMotionを付与するデータパックをgithubに公開しました。
カスタムエンチャントが広まりより多くの人たちが使い、開拓されていけばいいなと思います。