【Minecraft BE Addon講座#7】コンポーネントグループの付与・削除

本ページは**アドオン講座#7、「コンポネ編、コンポーネントグループの付与・削除」**です。
目次や、この講座については[#1の記事](https://qiita.com/pencilya/items/7b7e68265151f42a51ac)をご覧ください。

#コンポネ編で理解してほしいこと！
コンポネ編はこのアドオン講座の基礎編の次のページに位置し、**内容としては応用編の中でも基礎寄り**になります。
エンティティの追加には欠かせませんし、特に初回の#5の内容はアニコン編でもお世話になります。
基礎編を終えてとりあえず何かやってみようという方におすすめです！

コンポネ編は3ページからなり、**初回#5は基礎編の延長で、書き方(記法)と仕組みの理解**について、**#6では重要な条件分岐の役割を担う機能**について、**最後#7では実践としてタグによって体力(HP)が変わるアドオンを作って**みます。

このコンポネ編では、エンティティの持つコンポーネントを自由に追加消去編集することで、**エンティティに特殊な性質をもたせる、また特殊な動きをさせられるようになる**ことを目的としています。

* (前提#3~#4)基礎編
* (#5)エンティティとは？
* (#5)`entity.json`とは？
* (#5)エンティティのしくみとコンポーネント
* (#5)[実践]プレイヤーの名前を非表示にしてみよう
* (#6)フィルターとは？
* (#6)フィルターの記法
* (#7)コンポーネントを操るには？
* (#7)センサーとは？/コンポーネントグループとは？
* (#7)[実践]プレイヤーの体力をタグで変更してみよう

#コンポーネントを操るには？
今回#7はコンポネ編最終回です！
コンポネ編の目標を達成するため、さっそく材料の解説をしたいところですが、残りの材料はほぼ定型文みたいなもので、フィルターのような記法が複雑な機能ではなく、しくみの方が重要なので、先に**どういうルートで目標を達成するか！**について解説していきます。

前々回#5で実践したように、コンポネを足したり消したりすること自体は簡単です。
しかし、**#5で説明した方法ではコンポネを変更するために、ファイルを書き換え、その度にワールドを再起動する必要**があります。
今回#7で説明したいのは、**あらかじめコンポネのセットを作っておいて、それをゲーム内で切り替える方法**です。

前回#6でやった通り、フィルターではタグを検知できます。
それを利用してゲーム内からコンポネを操るのです。

流れはこうです。

* ①ゲーム内でタグの変更
* ②**センサー**内のトリガー、フィルターでタグを検知
* ③**センサー**によって**イベント**発動
* ④**イベント**で**コンポーネントグループ**に追加
* ⑤**コンポーネントグループ**内のコンポネが現在のコンポネに適用される

ようは、
タグ
→センサー
→イベント
→コンポーネントグループ
→コンポネ
の順番です！

**複雑すぎやろ！！！！**

センサー？コンポーネントグループ？
まずはここらへんから解説していきます。

#センサーとは？
センサーは**コンポネの一種**です。
主に**`minecraft:damage_sensor`、`minecraft:environment_sensor`**の2つが使われます。
ほかにもいくつかあるようですね。

`minecraft:damage_sensor`は、**そのエンティティがダメージを受けたときにフィルターを動かして、検知**をしてくれます[^1]。
`minecraft:environment_sensor`は、**フィルターを常時トリガー(≒実行、検知)してくれるもので、トリガーの中にフィルターを書くことで、常時検知**してくれます。

[^1]: `on_damage`がダメージが発生したとき、という意味合いを持っており、ダメージを受けると`on_damage`のカッコの中が実行されるよって感じです。

今回の[実践]のコーナーでは`minecraft:environment_sensor`を使用するので、ここでは`minecraft:damage_sensor`の方を例として挙げておきます。

```[センサー解説]behavior/entities/player.json
"minecraft:damage_sensor": {
  "triggers": [
    {
        "on_damage": {
            "filters": {
                "all_of": [
                  { "test": "has_tag", "subject": "self", "value": "t1" },
                  { "test": "has_tag", "subject": "other", "value": "t1" }
                ]
            }
        },
        "deals_damage": false
    },
    {
        "on_damage": {
            "filters": {
                "all_of": [
                  { "test": "has_tag", "subject": "self", "value": "t2" },
                  { "test": "has_tag", "subject": "other", "value": "t2" }
                ]
            }
        },
        "deals_damage": false
    }
  ]
}
```

`minecraft:damage_sensor`ではダメージを受けたエンティティが`self`、ダメージを与えたエンティティが`other`として扱われます[^2]。
この例のダメージセンサーは、「t1というタグを持っているプレイヤーはt1というタグを持っているプレイヤーに攻撃されたときダメージ無効」と「t2というタグを持っているプレイヤーはt2というタグを持っているプレイヤーに攻撃されたときダメージ無効」という効果を持っています[^3]。
要するに**同じチームに同じタグを付ければ、間違って攻撃してもダメージが入らないという、フレンドリーファイア(味方を攻撃してしまうこと)を無くすアドオンが作れます**。

[^2]: フィルターについて、詳しくは[前回#6](https://qiita.com/pencilya/items/b45be36b12589f4dc7e3)をご覧ください。
[^3]: 英単語`deal`は「～(攻撃)を加える」という意味です。`deals_damage`は「ダメージを加える」。これを`false`(＝偽、無効)にすることで、攻撃を無効にしています。英単語を知っているだけでアドオンが「読める…！読めるぞ…！」になったりします。

#イベントとは？
イベントは**それぞれのエンティティに紐づけられている固有の動作起点**です。
例えば、プレイヤーでは`minecraft:gain_bad_omen`、`minecraft:trigger_raid`など、村のレイドイベントが存在します。
他にはエンダークリスタルに`minecraft:crystal_explode`というイベントがあったりします。
バニラでも**エンティティが何らかの変化をするとき、裏ではイベントが発動している**ということです。

:::note info
＜追加情報＞
イベントはコマンド<code>/event</code>で起こすことができるほか、コマンド<code>/summon</code>でも実行できます。
例えば、エンダークリスタルの爆発だけを起こしたい場合は、
<code>/summon minecraft:ender_crystal ~~~ minecraft:crystal_explode</code>
を実行すれば、エンティティのスポーンと同時にイベントが発動し、すぐに爆発します。
もちろんこの文そのままでは<code>~~~</code>座標が爆発するので注意。
:::

イベントができることは

* **コンポーネントグループの追加、削除**
* **コマンドの実行**

です。

```[イベント解説]behavior/entities/[entity].json
"events": {
  "example_event": {
    "add": {
       "component_groups": [ "new_component_group" ]
    },
    "remove": {
       "component_groups": [ "old_component_group" ]
    },
    "sequence": [
      {
        "run_command": {
          "command": [
            "tell @s event_ok",
            "title @s actionbar event_ok"
          ],
          "target": "self"
        }
      }
    ]
  }
}
```

こんな感じで記述します。
コンポーネントグループ`new_component_group`に入り、コンポーネントグループ`old_component_group`から抜け、コマンドふたつを実行するイベントです。
コマンド実行の際、アニコンやスクリプトでは必要な`/`がいらないっぽいですね(なんで？)。

#コンポーネントグループとは？
コンポーネントグループはその名の通り、**コンポーネントのグループ、セット**です。
いくつかのコンポネを束ねておくことができます。

```[コンポーネントグループ解説]behavior/entities/[entity].json
"component_groups": {
  "グループ名": {
    "コンポネ名": {
      //コンポネ設定
    },
    "コンポネ名": {
      //コンポネ設定
    }
  }
}
```

こんな感じです。
**イベントによってコンポーネントグループが追加されると、デフォルトのコンポネ**(`"components"`のカッコの中のコンポネ)**に上書きで、そのコンポーネントグループの中のコンポネが適用されます**。

コンポーネントグループだけではできることがないので、具体例は実践で見せましょう。

#[実践]プレイヤーの体力をタグで変更してみよう
ではさっそくコンポネ編で解説したことを活かして実践してみましょう。

###①ゲーム内でタグの変更
まずは変更するためのタグの名前を決めましょう。
今回は、

| タグ | 効果 |
|:-:|:-:|
| なし  | 体力20(♡1列)  |
| `hp2`  | 体力40(♡2列)  |
| `hp3`  | 体力60(♡3列)  |
| `hp4`  | 体力80(♡4列)  |

として作っていきます。

###②③センサー内のトリガー、フィルターでタグを検知、イベント発動
> `minecraft:environment_sensor`は、フィルターを常時トリガーしてくれるもので、トリガーの中にフィルターを書くことで、常時検知してくれます。

先ほど手順に書いたように、**センサーではタグを検知**し、**イベントを発動**させます。

```[environment_sensor]behavior/entities/player.json
"minecraft:environment_sensor": {
  "triggers": [
  {
    "filters": { "all_of": [ { "test": "has_tag", "subject": "self", "operator": "!=", "value": "hp2" },
      { "test": "has_tag", "subject": "self", "operator": "!=", "value": "hp3" },
      { "test": "has_tag", "subject": "self", "operator": "!=", "value": "hp4" } ] },
    "event": "hp1"
  },
  {
    "filters": { "all_of": [ { "test": "has_tag", "subject": "self", "operator": "==", "value": "hp2" } ] },
    "event": "hp2"
  },
  {
    "filters": { "all_of": [ { "test": "has_tag", "subject": "self", "operator": "==", "value": "hp3" } ] },
    "event": "hp3"
  },
  {
    "filters": { "all_of": [ { "test": "has_tag", "subject": "self", "operator": "==", "value": "hp4" } ] },
    "event": "hp4"
  }
  ]
}
```

タグなし→イベント`hp1`実行
タグ`hp2`→イベント`hp2`実行
タグ`hp3`→イベント`hp3`実行
タグ`hp4`→イベント`hp4`実行
という対応です。

###④イベントでコンポーネントグループに追加

```[event]behavior/entities/player.json
"events": {
  "hp1": {
     "add": {
        "component_groups": [ "hp1" ]
     }
  },
  "hp2": {
     "add": {
        "component_groups": [ "hp2" ]
     }
  },
  "hp3": {
     "add": {
        "component_groups": [ "hp3" ]
     }
  },
  "hp4": {
     "add": {
        "component_groups": [ "hp4" ]
     }
  }
}
```

イベントから同名のコンポーネントグループに追加しています。

###⑤コンポーネントグループ内のコンポネが現在のコンポネに適用される

```[component_groups]behavior/entities/player.json
"component_groups": {
  "hp1": {
    "minecraft:health": {
      "value": 20,
      "max": 20
    }
  },
  "hp2": {
    "minecraft:health": {
      "value": 40,
      "max": 40
    }
  },
  "hp3": {
    "minecraft:health": {
      "value": 60,
      "max": 60
    }
  },
  "hp4": {
    "minecraft:health": {
      "value": 80,
      "max": 80
    }
  }
}
```

コンポーネントグループをこんな感じに書けば、体力がそれに応じて変わります。

###完成品

```behavior/entities/player.json
{
  "format_version": "1.17.10",
  "minecraft:entity": {
    "description": {
      "identifier": "minecraft:player",
      "is_spawnable": false,
      "is_summonable": false,
      "is_experimental": false
    },

    "component_groups": {
      "hp1": {
        "minecraft:health": {
          "value": 20,
          "max": 20
        }
      },
      "hp2": {
        "minecraft:health": {
          "value": 40,
          "max": 40
        }
      },
      "hp3": {
        "minecraft:health": {
          "value": 60,
          "max": 60
        }
      },
      "hp4": {
        "minecraft:health": {
          "value": 80,
          "max": 80
        }
      }
    },

    "components": {

      //省略

      "minecraft:environment_sensor": {
        "triggers": [
        {
          "filters": { "all_of": [ { "test": "has_tag", "subject": "self", "operator": "!=", "value": "hp2" },
            { "test": "has_tag", "subject": "self", "operator": "!=", "value": "hp3" },
            { "test": "has_tag", "subject": "self", "operator": "!=", "value": "hp4" } ] },
          "event": "hp1"
        },
        {
          "filters": { "all_of": [ { "test": "has_tag", "subject": "self", "operator": "==", "value": "hp2" } ] },
          "event": "hp2"
        },
        {
          "filters": { "all_of": [ { "test": "has_tag", "subject": "self", "operator": "==", "value": "hp3" } ] },
          "event": "hp3"
        },
        {
          "filters": { "all_of": [ { "test": "has_tag", "subject": "self", "operator": "==", "value": "hp4" } ] },
          "event": "hp4"
        }
        ]
      }
    },
    
    "events": {
      "hp1": {
         "add": {
            "component_groups": [ "hp1" ]
         }
      },
      "hp2": {
         "add": {
            "component_groups": [ "hp2" ]
         }
      },
      "hp3": {
         "add": {
            "component_groups": [ "hp3" ]
         }
      },
      "hp4": {
         "add": {
            "component_groups": [ "hp4" ]
         }
      }
    }
  }
}
```

できた～～～！！！

#コンポネ編終了！
応用編最初にあたるコンポネ編、いかがだったでしょうか！

マインクラフトの世界に動きを与える存在、エンティティ。
それらを構成するコンポーネントという部品。
それらを操るコンポーネントグループ、センサー、フィルター、イベント。

これらをマスターしたみなさんは**マインクラフトの世界に存在する動きを自由に付与することができます！**

実はコンポネは私が製作してきたアドオンのほとんどに活用されています。

**「[実践]プレイヤーの名前を非表示にしてみよう#5」でやった「名前の非表示」**

https://qiita.com/pencilya/items/89d0fdf6d575baea79c2#%E5%AE%9F%E8%B7%B5%E3%83%97%E3%83%AC%E3%82%A4%E3%83%A4%E3%83%BC%E3%81%AE%E5%90%8D%E5%89%8D%E3%82%92%E9%9D%9E%E8%A1%A8%E7%A4%BA%E3%81%AB%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86

はほぼすべてのアドオンに使われていますし、
**今回#7でやったフレンドリーファイア防止のダメージセンサー**は、JustBattleというPvPアドオン

http://pencilya.blog.jp/articles/20201206.html

に使われ、
**「[実践]プレイヤーの体力をタグで変更してみよう」でやった「体力変更」**は、雪合戦ワールド

http://pencilya.blog.jp/articles/20210212.html

に使われています。

このように、コンポネは様々なアドオンの基礎部分を担っています。
他にも応用編がありますが、**コンポネと組み合わせることで威力を発揮できる技術もあるため、他の応用編を読むうえでも、このコンポネ編で得た知識は役に立つと思います！**

ここまで読んでくれたみなさんが、このコンポーネントという技術をうまく活かして、作りたいアドオンが作れますように祈っております！

お疲れ様でした～～～～！！

↓目次#1へ↓

https://qiita.com/pencilya/items/7b7e68265151f42a51ac