Minecraft BEでJavaScriptを使用したアドオンを作る備忘録

# はじめに
BEのアドオン開発を書いたjavascript周りの記事がなかなか見つかりづらかった&ドキュメント見ても書いてないものがあったので備忘録として記載

::: note info
Minecraft Bedrock Edition 1.20.30 windows版
:::

#### 主な対象
javascriptやったことあるよ
やったことないけど他の言語やってなんとなく読めるよ

# 準備
minecraftのアドオンが置かれるフォルダを探す
隠しフォルダになってるAppDataの更に下にある
``` %localappdata%\Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang\ ```
↑をWin+R押して貼り付けるのが楽

development_behavior_packsに新規フォルダを作成
このフォルダ名は表に出てこないのでなんでも良いが、大量にアドオン作った場合わかりにくくなるので認識しやすい名前にした方が良い
とりあえずsampleフォルダを作成
```
com.mojans
　┗development_behavior_packs
　　┗sample
```

# manifest.json
これが無いとまず認識してくれない
先程作ったsampleフォルダ内にmanifest.jsonを作成
``` folder
com.mojans
　┗development_behavior_packs
　　┗sample
　　　┗manifest.json
```

manifest.jsonの中身を書く
``` manifest.json
{
    "format_version": 2,
    "header": {
        "name": "TNT",
        "description": "足元がTNTになる",
        "uuid": "【ここにUUID取ってきて入れる】",
        "version": [1,0,0],
        "min_engine_version": [1,16,0]
    },
    "modules": [
        {
            "type": "script",
            "version": [1,0,0],
            "uuid": "【ここにUUID取ってきて入れる】",
            "entry": "scripts/main.js"
        }
    ],
    "dependencies": [
        {
            "module_name": "@minecraft/server",
            "version": "1.5.0"
        }
    ]
}
```
読み込まれた際にnameとdescriptionがそれぞれこんな感じになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/290944/322a0577-ba73-6a8f-c559-d1246e428f93.png)

2箇所のuuidはそれぞれUUIDジェネレーターで取ってきた値を入れる
私は面倒なのでVSCodeのUUID Generatorという拡張機能で入れてます


modulesの書き方について書いてある記事が少なく見つけづらかった部分
#### type
scriptを指定 modules内で複数書く時にdataなら問題ないがものによっては競合するみたいな記載をどこかで見た　今回はmodules内が1個だけなので気にしない

#### entry
jsファイルが置いてあるとこを記載 上ではscripts/main.jsとあるがこれから作る

# javascriptファイルを作成
　manifest.jsonのentryで記載した通りにscriptsフォルダとmain.jsを作成
```
com.mojans
　┗development_behavior_packs
　　┗sample
　　　┣manifest.json
　　　┗scripts
　　　　┗main.js
```

ここまで用意すればゲームで該当のビヘイビアーパックを指定することで読み込みまでしてくれる
チート使用をオンにしておけばjsファイルを更新した際に/reloadで再読込してくれる

#### main.js内にプログラムを書く

``` main.js
import { world, system } from "@minecraft/server"

const INTERVAL_SEC = 1 //インターバルで1秒毎に呼ぶ

system.runInterval(() => {
    // 全プレイヤー情報を取得
    const players = world.getAllPlayers()

    // 全プレイヤーに対し処理を実行
    for (const player of players) {

        //playerの座標を取得
        let loc = player.location

        //y座標をプレイヤーの1つ下にセット
        loc.y = loc.y - 1

        // 現在の世界情報を取得 minecraft:overworld or minecraft:nether or minecraft:the_end
        let dimension = player.dimension.id

        // 足元のブロックをTNTに置換
        world.getDimension(dimension).runCommand(`setblock ${loc.x} ${loc.y} ${loc.z} minecraft:tnt`)
    }
}, INTERVAL_SEC * 20)
```
#### system.runInterval(func,tickInterval)
https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/system#runinterval
javascriptのsetIntervalと基本的に同じものですが、第二引数の値はミリ秒ではなくminecraftで使用される tick(0.05秒)単位なので秒数にしたい場合は×20する
あんまり頻度が高いとswitchやスマホ勢がカクつく影響あり
windows版でも1秒インターバルでジャンプブリッジして落ちた例あり


#### world.getAllPlayers()
https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/world#getplayers
全プレイヤー情報を取得

#### player.location
https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/player#properties
~~playerクラスのプロパティに書いてないけど座標情報持ってないはずはないと思って取りにいったらあった　このあたり公式ドキュメントが優しくない~~

**Entityクラスを拡張してPlayerが作られているのを見落としてました
↓のplayer.dimension.idも同様**

#### player.dimension.id
minecraft:overworldやらminecraft:netherやらminecraft:the_endを返してくる
どのディメンション＋座標のセットにしないとネザー行ってもオーバーワールド側変更しちゃってあれ？みたいになる

#### world.getDimension(dimension).runCommand(command)
dimensionのワールドでコマンド実行する
クリエイティブをあまりやらないのでわからないがそういうコマンドとか使える　実行者はサーバーなので近い人みたいな相対的になるオプションは使えない・・・と思う（試してない）



# おまけ備忘録

https://learn.microsoft.com/en-us/minecraft/creator/scriptapi/minecraft/server/playerspawnafterevent
PlayerSpawnAfterEventクラスがあってもどう使うかわからなかった
BEアドオン開発のdiscordまで見に行ってやっと理解

HogeFugaBeforeEventクラス/HogeFugaAfterEventクラスがあったらイベント登録方法は

【world】の【afterEvent/beforeEvent】の【afterEvent/beforeEventを抜いてキャメルケースにしたクラス名】をsubscribeする

``` example.js
import { world } from "@minecraft/server"
world.afterEvent.hogeFuga.subscribe(event => {
    //処理
})
world.beforeEvent.hogeFuga.subscribe(event => {
    //処理
})
```
なんじゃそりゃって感じではあるがイベントリスナー登録はこれで解決した