Minecraft BE Add-on 開発メモ

Minecraft BE 1.9.0 あたりでエンティティの追加ができるようになったらしいので、試してみた。その時に分かったことをメモしておこうと思う。プログラミングに明るい人向けだけど、何かの助けになれば。

こんなのを作った。プレイヤーのように振る舞う人型友好モブ。まだいろいろ機能足す必要があるけど……

![moonlighter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32800/5548c688-0da9-54ef-4d8c-fce203650e77.gif)

以前挑戦したときも 1.9.0 だった気がしなくもないが、とにかくその時は上手いことモブを追加することができなかった覚えがある。その時の詰みポイントはリソースパックだった。

いろいろ重要な用語が出てくるので、羅列しておこう…

* エンティティ設定
* アニメーション設定、アニメーション名
* アニメーションコントローラ設定、アニメーションコントローラ名
* MoLang

## 環境

* Windows 10
* Minecraft Bedrock Edition v1.10.0
* VSCode(テキストエディタ)

## リソースパックでオリジナルモブを描画する

以前挑戦したときにビヘイビアパックの作り方は把握したのだけど、リソースパックの設定がうまくいかず、ずっと透明人間がゾンビと戦う様を見ているだけだった。リソースパックの書き方のドキュメントなさすぎ……

最終的な**エンティティ定義**のjsonはこんな感じ。この後の節で詳しく見ていく。

```json
{
  "format_version": "1.10.0",
  "minecraft:client_entity": {
    "description": {
      "identifier": "numani:starspread",
      "materials": {
        "default": "entity_alphatest"
      },
      "textures": {
        "default": "textures/entity/starspread/moonlighter-skin"
      },
      "geometry": {
        "default": "geometry.steve"
      },
      "animations": {
        "idle": "animation.humanoid.base_pose",
        "move": "animation.humanoid.move",
        "look_at_target_default": "animation.humanoid.look_at_target.default",
        "look_at_target_gliding": "animation.humanoid.look_at_target.gliding",
        "look_at_target_swimming": "animation.humanoid.look_at_target.swimming",
        "holding": "animtion.humanoid.holding",
        "charging": "animation.humanoid.charging",
        "damage_nearby_mobs": "animation.humanoid.damage_nearby_mobs",
        "swimming": "animation.humanoid.swimming",
        "use_item_progress": "animation.humanoid.use_item_progress",
        "move_controller": "controller.animation.humanoid.move",
        "look_at_target_controller": "controller.animation.humanoid.look_at_target"
      },
      "scripts": {
        "pre_animation": [
          "variable.tcos0 = (Math.cos(query.modified_distance_moved * 38.17) * query.modified_move_speed / variable.gliding_speed_value) * 57.3;",
          "variable.tcos1 = -variable.tcos0;",
          "variable.attack_body_rot_y = Math.sin(Math.sqrt(variable.attack_time) * 360.0) * 11.46;",
          "variable.cos_attack_body_rot_y = Math.cos(variable.attack_body_rot_y);",
          "variable.sin_attack_body_rot_y = Math.sin(variable.attack_body_rot_y);",
          "variable.internal_swim_pos = Math.mod(query.modified_distance_moved, 26.0);",
          "variable.attack = Math.sin((1.0 - (1.0 - variable.attack_time) * (1.0 - variable.attack_time)) * 180.0) * 57.3;",
          "variable.attack2 = Math.sin(variable.attack_time * 180.0) * 57.3;",
          "variable.z_bob = Math.cos(query.life_time * 103.13244) * 2.865 + 2.865;",
          "variable.x_bob = Math.sin(query.life_time * 76.776372) * 2.865;"
        ],
        "animate": [
          "move_controller",
          "look_at_target"
        ]
      },
      "render_controllers": [ "controller.render.steve" ],
      "spawn_egg": {
        "base_color": "#FFFFFF",
        "overlay_color": "#000000"
      },
      "enable_attachables": true
    }
  }
}
```

### 詰まったところ

で、このjsonの中でも、以前試して上手くいかなかった状態から動く状態への主な差分は

* format_version が 1.10.0 である必要がある(以前は1.8.0にしてた)
* animation_controller オブジェクトは 1.10.0 では使わないので削除する

このあたり。

でも…… Minecon 2018 を機に公式から配布されている `Minecon2018Resources` リソースパックだと、追加されている「サソリ」エンティティのjsonを見てみると、format_version が 1.8.0 になってた……なんで今回 1.10.0 に変えたことで上手くいったのかが不明な状態。公式さんもっとドキュメントちょうだい～

`Minecon2018Resources` リソースパックの入手元は[こちら](https://minecraft.gamepedia.com/Add-on)

## アニメーションつける

format_version を 1.10.0 にするなどしてエンティティが表示されたので、移動するときに歩きアニメーションが再生されるようにしたい。アニメーションの定義方法は結構複雑で、次のような手順で作成する。

1. アニメーション設定を書く
2. アニメーション設定をエンティティ設定から参照する
3. エンティティ設定を参照しながらアニメーションコントローラ設定を書く
4. アニメーションコントローラ設定をエンティティ設定から参照する

### 1. アニメーションを定義する

リソースパックの中に、`animations/starspread.json` みたいな感じで**アニメーション定義**ファイルを作成する。ちなみに、starspreadっていうのは今回の追加モブのIDだけど、別に同じにしないといけないわけではない。あと今回の追加モブは他のアニメーション定義ファイルの内容を流用しているので、実際には作らなかった。

アニメーション定義ファイルの中身はこんな感じ。

```json
{
    "format_version": "1.8.0",
    "animations": {
        "animation.humanoid.move": {
            "loop": true,
            "bones": {
                "rightArm": { "rotation": [ "variable.tcos1", 0.0, 0.0 ] },
                "leftArm": { "rotation": [ "variable.tcos0", 0.0, 0.0 ] },
                "rightLeg": { "rotation": [ "variable.tcos0 * 1.4", 0.0, 0.0 ] },
                "leftLeg": { "rotation": [ "variable.tcos1 * 1.4", 0.0, 0.0 ] }
            }
        }
    }
}
```

`format_version` フィールドとか `animations` オブジェクトは必須。その中の `animation.humanoid.move` はアニメーション名で、自分で命名する。これはさっき見せたエンティティ設定の中か、または**アニメーションコントローラ設定**で使ってる。

そしてアニメーション名のついたオブジェクトの中身は、アニメーションの内容を書く。`rightArm` とか `leftLeg` とかが、右腕や左足の動きを設定する。`rotation` は回転。このアニメは両手両足を振りながら歩くモーションに使う目的で書かれているので、それぞれのパーツを回転させているというわけ。他にも `scale` で拡縮したりできるのかな？

さらに `rotation` の中には、 X, Y, Z 軸それぞれの回転角度を書くわけだけど、X軸(たぶん)のところに `variable.tcos1 * 1.4` などと不思議な計算式が書かれている……

Minecraft BE のアドオンの中では、**MoLang**という文法で計算式を埋め込める部分がある。`variable.tcos1` とかいうのは、変数にアクセスする式。この変数がどこで定義されているかというと……エンティティ設定の、`minecraft:client_entity/description/scripts/pre_animation`のところを探すと、変数の値を編集するスクリプトが書かれているのが見つけられる。それは以下の部分。(jsonってコメントつけられないのだけど、省略や説明のためにつけてます。許してね)

```json
{
  "format_version": "1.10.0",
  "minecraft:client_entity": {
    "description": {

      // (略)

      "scripts": {
        "pre_animation": [
          "variable.tcos0 = (Math.cos(query.modified_distance_moved * 38.17) * query.modified_move_speed / variable.gliding_speed_value) * 57.3;",
          "variable.tcos1 = -variable.tcos0;",
          "variable.attack_body_rot_y = Math.sin(Math.sqrt(variable.attack_time) * 360.0) * 11.46;",
          "variable.cos_attack_body_rot_y = Math.cos(variable.attack_body_rot_y);",
          "variable.sin_attack_body_rot_y = Math.sin(variable.attack_body_rot_y);",
          "variable.internal_swim_pos = Math.mod(query.modified_distance_moved, 26.0);",
          "variable.attack = Math.sin((1.0 - (1.0 - variable.attack_time) * (1.0 - variable.attack_time)) * 180.0) * 57.3;",
          "variable.attack2 = Math.sin(variable.attack_time * 180.0) * 57.3;",
          "variable.z_bob = Math.cos(query.life_time * 103.13244) * 2.865 + 2.865;",
          "variable.x_bob = Math.sin(query.life_time * 76.776372) * 2.865;"
        ],
        // (略)
      },

      // (略)

    }
  }
}
```

たぶんjavascriptが書かれているのかな。このスクリプトが適切なタイミングで呼ばれる(いつ呼ばれるんだろう)ことで、アニメーションを制御しているというわけだ。

### 2. アニメーションをエンティティに割り当てる。

前節で書いたアニメーション設定は、まだエンティティに割り当てられていない状態。さっそく今回のエンティティに割り当ててみよう。最初に見せたエンティティ設定をまた見てみると、アニメーションを割り当てている部分は以下の通り。

```json
{
  "format_version": "1.10.0",
  "minecraft:client_entity": {
    "description": {

      // (略)

      // ここでアニメーションを設定
      "animations": {
        "idle": "animation.humanoid.base_pose",
        "move": "animation.humanoid.move",
        "look_at_target_default": "animation.humanoid.look_at_target.default",
        "look_at_target_gliding": "animation.humanoid.look_at_target.gliding",
        "look_at_target_swimming": "animation.humanoid.look_at_target.swimming",
        "holding": "animtion.humanoid.holding",
        "charging": "animation.humanoid.charging",
        "damage_nearby_mobs": "animation.humanoid.damage_nearby_mobs",
        "swimming": "animation.humanoid.swimming",
        "use_item_progress": "animation.humanoid.use_item_progress",
        "move_controller": "controller.animation.humanoid.move",
        "look_at_target_controller": "controller.animation.humanoid.look_at_target"
      },

      // (略)

    }
  }
}
```

`animations` オブジェクトの中に、使用したいアニメーションをリストアップする。フィールド名はここで適当に決めて書くが、フィールドの中身は先ほどのアニメーション設定で作成したアニメーション名を指定する。

例として、以下の部分を見てみよう：

```json
"move": "animation.humanoid.move"
```

これで、「`animation.humanoid.move` アニメーションを使います、それに `move` という別名を付けます」という意味になる。エンティティ設定からアニメーションを参照するときは、こういう設定をリストアップする形で書くということ。

### 3. アニメーションコントローラを定義する

一部のアニメーション制御には、**アニメーションコントローラ**が必要になることがある。一部のアニメーションっていうのは、アニメーションを再生する条件を決めたり、アニメーションとアニメーションの間を滑らかに繋ぐなど、複数のアニメーションの組み合わせを適切に制御しないといけないもの。

アニメーション設定ファイルは、リソースパックの `animation_controllers` フォルダの中にjsonファイルを作って書く。以下のような感じ。

```json
{
    "format_version": "1.8.0",
    "animation_controllers": {
        "controller.animation.humanoid.riding": {
            "states": {
                "default": {
                    "transitions": [
                        { "riding": "query.is_riding" }
                    ]
                },
                "riding": {
                    "animations": [
                        { "riding.arms": [] },
                        { "riding.legs": [] }
                    ],
                    "transitions": [
                        { "default": "!query.is_riding" }
                    ]
                }
            }
        }
    }
}
```

`format_version` フィールドと `animation_controllers` オブジェクトは必須。で、 `controller.animation.humanoid.riding` とかいうのはアニメーションコントローラ名で、自分でつける。この名前はエンティティ設定の中で使う。ちなみに、このデータはバニラのデータの中身から入手していて、humanoidっていうのは人型モブの間で使いまわされているアニメーションコントローラ設定。

`controller.animation.humanoid.riding` オブジェクトの中には `states` オブジェクトが必須で、その中に実際のアニメーション制御の設定が書かれている。

`states` というだけあって、アニメーションコントローラではステートマシンを使ってアニメーション同士の間を切り替えるための設定が書ける。`states` オブジェクトの中身は `default`, `riding` オブジェクトなので、このアニメーションコントローラには `default`, `riding` の2つのステートがあるということになる。それぞれ、「騎乗していない状態」「騎乗している状態」のアニメーションを設定するというわけ。

ステートの中には、`animations`, `transitions` オブジェクトが書ける。エンティティが該当する状態にあれば、`animations` の中に書いたアニメーションが再生されるというわけ。`animations` の中では、エンティティ設定でつけたアニメーションの別名を使って参照する。

```json
"animations": [
    { "riding.arms": [] },
    { "riding.legs": [] }
],
```

という書き方なので、`rinding.arms`, `riding.legs` という別名のついたアニメーションが再生される。フィールドの値として配列が渡されているが、なにやらアニメーションに対して引数が渡せるらしい。この辺の仕様はまだあまり調べてない。

`transitions` オブジェクトのことだが、`default` 状態を表すオブジェクトの中には、以下の `transitions` オブジェクトがある：

```json
"transitions": [
    { "riding": "query.is_riding" }
]
```

ここには、どんな条件を満たしたときにどのステートへ遷移するか、を指定できる。

`query.is_riding` はクエリと呼ばれるもので、この書き方なら今回のエンティティが他のエンティティに騎乗していればtrueを返す。そうしたら、晴れて `riding` 状態へ遷移する。

### 4. アニメーションコントローラを参照する

アニメーションコントローラ設定も、やはりエンティティ設定から参照しないと意味がない。アニメーションコントローラは、アニメーションと同じように、エンティティ設定の `animations` オブジェクトの中から参照する。`animations` オブジェクトの中には、アニメーションとアニメーションコントローラを混在させて書くというわけ。

最初の例から該当部分を抜き出すと以下の通り。

```json
{
  "format_version": "1.10.0",
  "minecraft:client_entity": {
    "description": {

      // (略)

      "animations": {
        // (略)
        "move_controller": "controller.animation.humanoid.move",
        "look_at_target_controller": "controller.animation.humanoid.look_at_target"
      },

      // (略)

    }
  }
}
```

なるほど、先ほど登場した `controller.animation.humanoid.move` アニメーションコントローラを参照している。

エンティティ設定の`animations` オブジェクトに指定されているアニメーションコントローラが更に、エンティティ設定の `animations` オブジェクトの中身を参照しているため少し複雑だが、こんな感じで設定ができるはず。

## つかれた

ほんとはビヘイビアパックのほうも弄ったんだけど、記事書くの疲れたからここまで。BE版アドオン制作は日本語の情報が皆無などころか、英語の情報も限りなく少ない……とはいえ、アドオン制作がサポートされたのはごく最近なので、当然といえば当然なんだけどね。

この記事が少しでもアドオン作りたい日本人の助けになれば……！