開発者は追加したい
データパックで追加したい要素は結構あります。
- ブロック
- アイテム
- パーティクル
- エンティティ
基本的にこれらの要素は新規の要素として追加できません。既存の内容を変更することはいろんな項目でできますが、純粋に新規追加できる機能はデータパック内でも少なめです。
ただ、擬似的に実装することはある程度可能です。
その中でも特にアイテムに関しては、コンポーネントによる振る舞いの変更を使い、まるで新規のアイテムのように見せることができるようになってきました。
エンティティなどの他の要素を組み合わせる必要もなく、基本的にアイテム単体で完結するのも楽な点です(機能発動後どれだけ複雑なことをするか、したいかは別の話)。
今、追加要素に手をつけるならアイテムがオススメですよ!
というわけで、今回は一番汎用的で扱いやすい「アイテムの右クリックを検知する」方法について備忘録で記していきます。
盾と進捗 minecraft:using_item
進捗には「何かを使用している状態」で達成できるものが存在します。
それが minecraft:using_item です。
これは advancement ディレクトリで実装される進捗の追加で利用できます。
データパックにおける進捗
バニラにおいて進捗は「何かを達成した」ことを称える機能です。
そして次の達成目標を示してくれる存在、ゲームをどれだけやり込んだかというまさに進捗と言えます。
データパックはこの進捗に別の目標を追加することができるのですが、しばしば何らかの動作に対して反応する検知機 として使われることがあります。
{
"display": {
"icon": {
"id": "minecraft:knowledge_book"
},
"title": "<進捗タイトル>",
"description": "<進捗説明>"
},
"criteria": {
"using": {
"trigger": "minecraft:using_item",
"conditions": {
"item": {
"items": [
"minecraft:bow",
"minecraft:crossbow",
"minecraft:honey_bottle",
"minecraft:milk_bucket",
"minecraft:potion",
"minecraft:shield",
"minecraft:spyglass",
"minecraft:trident",
"minecraft:ender_eye",
"minecraft:bread",
"minecraft:..."
]
}
}
}
}
}
上記のような進捗を追加すると「アイテムを使用している状態」になると目標達成、つまり進捗達成となります。
対象となるアイテムは
- 弓・ボウガン・トライデントなどの溜めて発射する武器
- ポーション・牛乳・蜂蜜などの飲むことのできるアイテム
- 食べることのできるアイテム
- 盾
- 望遠鏡
- エンダーアイ
これらになります。クリックした瞬間処理が完結するアイテムは対象外で、昔懐かしのトリガーである釣り竿も残念ながら進捗の対象には入りません。
RPG風配布マップ「フライシェン」をよく遊んでいましたが、そこで使われた魔法トリガーが釣り竿(杖)でしたね。
手法というのは時の流れでどんどん変わっていくものです…。
ここで注目すべきなのは使っている状態を検知する点です。
使っている状態なので
# 進捗剥奪.
advancement revoke @a only xxx:using_any
のようにして進捗をクリアしてしまえば、次のティックでも再度検知が可能になります。
そのため、ティック毎に達成と剥奪を繰り返すことで「アイテムを使用し続けている間」はその行動を検知し続けることができます。
データコンポーネント minecraft:blocks_attacks
データコンポーネントはNBTに取って代わりそうなデータ形式で、現在はアイテムに広く普及しています。
データコンポーネントはアイテムの情報だけでなく、振る舞いも設定できます。
本来食べることのできないアイテムでも
"components": {
"consumable": {
"animation": "eat",
"consume_seconds": 10
},
"food": {
"nutrition": 4,
"saturation": 6,
"can_always_eat": false
}
}
というコンポーネントを追加すれば、どんなものでもムシャムシャ食べます。
コンポーネントの中には盾機能を追加する minecraft:blocks_attacks という項目もあります。
"components": {
"minecraft:blocks_attacks": {
"bypassed_by": "#minecraft:bypasses_shield",
"damage_reductions": [
{"base": 0,"factor": 0, "horizontal_blocking_angle": 90}
],
"disable_cooldown_scale": 1,
"block_delay_seconds": 0,
"item_damage": {"threshold": 2, "base": 2, "factor": 0.5},
"block_sound": {
"sound_id": "xxx:yyy",
"range": 8
},
"disabled_sound": {
"sound_id": "xxx:zzz",
"range": 8
}
}
}
この盾機能、構える動作だけで全く防御しないように設定することも可能です。
"components": {
"minecraft:blocks_attacks": {
"damage_reductions": [{"base": 0,"factor": 0}]
}
}
盾を構える動作は、右クリックの押しっぱなしで発生します。
つまり、これを追加すれば、あらゆるアイテムに右クリックでの動作を実装できるようになります。
2つを組み合わせたら…
盾を構えることを検知できる進捗 minecraft:using_item
盾機能を追加するコンポーネント minecraft:blocks_attacks
これらを組み合わせると、右クリック動作するアイテムを作るのは可能だと思いませんか?
実際に実装してみましょう。
カスタムアイテムで右クリックしたい
仕様を決める
今回はコンポーネントの一種 minecraft:custom_data を利用します。
カスタムデータは開発者が自由にデータを格納できる領域です。
"components": {
"minecraft:custom_data": {
"<any>": {...}
}
}
今回は以下のようなカスタムデータを埋め込まれたアイテム全般が対象になるように設計していきます。
"components": {
"minecraft:custom_data": {
"minecraft:custom_data": {"xxx": {"type": "device"}}
}
}
以下登場する xxx 部分はあなたのデータパック名などに自由に置き換えてください。
これを「デバイス機能を持ったアイテム」としてこのカスタムデータを運用していきます。こういうアイテムが右クリック可能になり、それを検知できるようになるまでが目標です。
進捗を実装
まずは進捗を実装します。
{
"display": {
"icon": {
"id": "minecraft:knowledge_book"
},
"title": "xxx:device.title",
"description": "xxx:device.description",
"show_toast": false,
"announce_to_chat": false,
"hidden": true
},
"criteria": {
"using": {
"trigger": "minecraft:using_item",
"conditions": {
"item": {
"predicates": {
"minecraft:custom_data": {"xxx": {"type" : "device"}}
}
}
}
}
}
}
アイテムの種類は指定せず、カスタムデータだけで判定しています。この際、以下のように components を使ってはいけません。
"conditions": {
"item": {
"components": {
"minecraft:custom_data": {"xxx": {"type" : "device"}}
}
}
}
components は完全一致で predicates は部分一致です。predicates にすれば他のコンポーネント要素が存在していても問題なく一致判定してくれます。
display 項目に以下の内容が追加されています。
進捗達成の情報が表示されないようにするものです。
"display": {
"show_toast": false,
"announce_to_chat": false,
"hidden": true
}
関数を実装
次に関数です。
{
"replace": false,
"values": ["xxx:load"]
}
{
"replace": false,
"values": ["xxx:tick"]
}
scoreboard objectives add xxx.input dummy
scoreboard players set @a[advancements={xxx:device=true}] xxx.input 1
scoreboard players set @a[advancements={xxx:device=false}] xxx.input 0
execute as @a if score @s xxx.input matches 1 \
run tellraw @s [{text:"Using device!"}]
advancement revoke @a only xxx:device
xxx:device の進捗達成の有無から xxx.input に入力状態を設定し、最後に xxx:device の進捗を剥奪します。
これで「デバイス機能を持ったアイテム」で右クリックを検知できるようになります。
xxx.input の値を確認して tellraw を呼び出していますが、同様にして好きなコマンド・関数を実行するよう改造していってください。
レシピを実装
エンジン部分は完成しました。
あとはゲーム内でどのように取得するかです。
その中の一つの提案としてカスタムレシピがあります。
クラフトで生成されるアイテムに「デバイス機能」を追加します。
{
"type": "minecraft:crafting_shapeless",
"ingredients": ["minecraft:book", "minecraft:lapis_lazuli"],
"result": {
"id": "minecraft:totem_of_undying",
"count": 1,
"components": {
"minecraft:item_name": "Grimoire",
"minecraft:lore": ["Device"],
"minecraft:item_model": "minecraft:knowledge_book",
"minecraft:blocks_attacks": {
"damage_reductions": [{"base": 0,"factor": 0}]
},
"minecraft:custom_data": {"xxx": {"type": "device"}}
"!minecraft:death_protection": {}
}
}
}
これで「本」と「ラピスラズリ」を組み合わせて「魔導書」がクラフトできるようになります。
ちなみに生成するアイテムのベースは「不死のトーテム」です。
"result": {
"id": "minecraft:totem_of_undying",
"count": 1,
"components": {
"!minecraft:death_protection": {}
}
}
こうすることで何の機能もなく、かつ本体が素材として派生しないアイテムを生成できます。
カスタムアイテムに適したベースアイテムの条件
- 余計な機能を持っていない
- それ自体が他のクラフト素材になっていない
が重要です。
前者はコンポーネントで機能を削れるので簡単ですが、後者はかなり候補が絞られます。
「棒」をベースにカスタムすると、そのカスタムアイテムは「棒」を使ったあらゆるクラフト素材にできてしまいます。
レアなカスタムアイテムなのに間違って素材にして消滅は怖いので、ここは慎重に検討しましょう。
"components": {
"minecraft:item_name": "Grimoire",
"minecraft:lore": ["Device"],
"minecraft:item_model": "minecraft:knowledge_book"
}
この部分で外観を「知恵の本」、名称を「魔導書」に設定しています。アイテムは金床で名称変更できるので、デバイスであることは別途 minecraft:lore で表示します。
そして先ほど説明したコンポーネントも追加してあります。
"components": {
"minecraft:blocks_attacks": {
"damage_reductions": [{"base": 0,"factor": 0}]
},
"minecraft:custom_data": {"xxx": {"type": "device"}}
}
鍛冶台を使う場合
せっかく汎用的に「デバイス機能」を実装したので、どのようなアイテムにも付与できるとよいですね。
ということで、補足で鍛冶台を使うパターンを記載しておきます。
{
"type": "minecraft:smithing_transform",
"base":"minecraft:iron_sword",
"addition": "minecraft:lapis_lazuli",
"result": {
"id": "minecraft:iron_sword",
"count": 1,
"components": {
"minecraft:lore": ["Device"],
"minecraft:blocks_attacks": {
"damage_reductions": [{"base": 0,"factor": 0}]
},
"minecraft:enchantable": {"value": 1},
"minecraft:custom_data": {"xxx": {"type": "device"}}
}
}
}
この場合、アイテム一種ごとに実装しないといけないのでちょっと大変です。
ルートテーブルで実装
これで全部完了ですが、念のためデバッグ用にルートテーブルから取得できるようにしておきます。
{
"type": "minecraft:generic",
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "minecraft:item",
"name": "minecraft:totem_of_undying",
"functions": [
{
"function": "minecraft:set_name",
"name": "Grimoire"
},
{
"function": "minecraft:set_lore",
"lore": ["Device"],
"mode": "append"
},
{
"function": "minecraft:set_components",
"components": {
"minecraft:blocks_attacks": {
"damage_reductions": [{"base": 0, "factor": 0}]
},
"minecraft:custom_data": {"xxx": {"type": "device"}}
}
}
]
}
]
}
]
}
こうしておけば give で長いコンポーネントを一列に書くより編集しやすく、取得する時も
loot give @s loot xxx:grimoire
のシンプルな一行で事足ります。
ルートテーブルは他のルートテーブルを参照できるため、他のルートテーブルにデバイス機能付きアイテムを組み込むのも楽になります。
ガチャで入手できる場合などにもルートテーブル実装は使えますが、デバッグ用のツールキット構築にも使えます。
{
"type": "minecraft:generic",
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "minecraft:item",
"name": "minecraft:white_shulker_box",
"functions": [
{
"function": "minecraft:set_contents",
"entries": [
{"type": "minecraft:loot_table", "value": "xxx:grimoire"},
{"type": "minecraft:loot_table", "value": "xxx:..."},
{"type": "minecraft:loot_table", "value": "xxx:..."},
{"type": "minecraft:loot_table", "value": "xxx:..."}
],
"component": "minecraft:container"
}
]
}
]
}
]
}
これを以下のコマンドで取得し
loot give @s loot xxx:toolkit
得られたシュルカーボックスを設置すると、各種ルートテーブルで得られたアイテムが格納されています。
まとめ
ひとまずこれらの「進捗」「関数」「レシピ」(+「ルートテーブル」)で運用できるレベルの環境を構築できるはずです。
「データパックの機能を複数組み合わせて複雑…」
と、思うかもしれませんが、正直今まで実装してきた右クリック検知の中で圧倒的に楽です。それに、今回は「デバイス機能」という形式で実装していったので、一度実装してしまえば広く使うことができるはずです。
もし、デバイス機能の中でも分類をしたい場合は
"components": {
"minecraft:custom_data": {"xxx": {"type": "device", "fearure": "..."}}
}
のように feature (機能)項目のようなものを追加して判別していくとよいと思います。この場合 predicate を使うと後々の変更管理がしやすいのでオススメです。
{
"entity": "this",
"condition": "minecraft:entity_properties",
"predicate": {
"equipment": {
"mainhand": {
"predicates": {
"minecraft:custom_data": {
"xxx":{"type":"device", "feature": "sorcery"}
}
}
}
}
}
}
さらに言えば、機能をエンチャントに振り分けるのもいいですね。
カスタムエンチャントの実装には装備しているだけで実行され続ける処理項目があります。
{
"description": "sorcery",
"supported_items": ["minecraft:iron_sword"],
"weight": 2,
"max_level": 1,
"min_cost": {
"base": 10,
"per_level_above_first": 10
},
"max_cost": {
"base": 20,
"per_level_above_first": 10
},
"anvil_cost": 1,
"slots": ["mainhand"],
"effects": {
"minecraft:tick": [
{
"effect": {
"type": "minecraft:run_function",
"function": "xxx:update_sorcery_enchantment"
}
}
]
}
}
まあ、手前に必須項目が色々あって手間ですが、重要なのは必須項目以外のこの部分。
{
"slots": ["mainhand"],
"effects": {
"minecraft:tick": [
{
"effect": {
"type": "minecraft:run_function",
"function": "xxx:update_sorcery_enchantment"
}
}
]
}
}
これは、メインハンドで所持している時に毎ティック xxx:update_sorcery_enchantment が実行され続ける設定です。
カスタムエンチャントの適用方法
カスタムエンチャントは /reload コマンドでは再読み込みされません。
変更をした場合、シングルプレイならワールドに入りなおす、サーバーなら立て直す必要があります。
これを知らず(忘れて)ドツボにはまるパターンはあるので、ぜひ覚えておいてください。
xxx:update_sorcery_enchantment 関数内で先ほど取得した xxx.input の状態をチェックし、効果発動処理を行えば、エンチャント別に効果を振り分けることができるはずです。
ここまで考えると、かなり便利に思えてきませんか?
ぜひとも皆さんもカスタムアイテムを実装してみましょう!
最後にちょっとだけ…
…と、ここまで書くと完璧な実装に見えますが、あと一息足りてない部分があります。それは
入力し続けることを連続で検知してしまう点
です。
正直なところ、カチっと1クリックしたら1回だけ効果が発動することを求めることは多いと思います。いや、むしろそっちの方が明らかに多いですかね…。
ある意味生の実装ではあると思います。
入力し続けるのを工夫して1クリック1回発動の形式にすることはできますが、デフォルトで1クリック1回発動から入力し続ける形式にすることはできませんからね。
むしろ、入力形式の幅が広がったと考えた方がよいでしょう。
この生の実装ではカチっっと1クリックしてからボタンを離すまで毎ティック効果が発動してしまいます!
フルオートで連続発動させたいとしても、発動の間にウェイトが欲しいというもの…。
ここら辺は様々な工夫ができると思いますが、ここでは一番シンプルな形式に説明を留めることとします。
直前の入力状態を保持しておく
scoreboard objectives add xxx.input dummy
scoreboard objectives add xxx.input.prev dummy
scoreboard players operation @s egg.input.prev = @s egg.input
scoreboard players set @a[advancements={xxx:device=true}] xxx.input 1
scoreboard players set @a[advancements={xxx:device=false}] xxx.input 0
execute as @a \
if score @s xxx.input.prev matches 0 \
if score @s xxx.input matches 1 \
run tellraw @s [{text:"Using device!"}]
advancement revoke @a only xxx:device
直前の入力状態を意味する xxx.input.prev が追加されました。各ファイル1行のみの追加、そして発動時の条件が1つ追加されただけのシンプルなものです。
ごくわずかな変化ですが、これで1クリック1回の発動にすることができます。
重要なのは「直前は未入力」「現在は入力中」のタイミングを押さえることです。
ここから先は皆さんの工夫次第!
実際開発していて、ここからが楽しいと思える部分なので、思い切り開発を楽しんでいってください(バグに出会いませんことをお祈り申し上げます)。