0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Minecraftデータパック開発備忘録: 02 安心できるワークスペースを作りたい

0
Posted at

「安心できる…」というタイトルでありながら、初手警告は変だと思いますが…

この記事の作業を行う時は必ずバックアップを取ってください!

ファイル同期コマンドを利用しているため、同期元と同期先の指定を誤って逆にしてしまうと全てが消滅します!!

正しく同期されていることが確認できるまで、データパック・リソースパックはバックアップして安全な場所に保管しておいてください。

ワークスペース問題

ワールドデータ内がワークスペースなのは安全?

皆さんはデータパックを開発する時、どこをワークスペースにしていますか?

もしかして saves/<ワールド>/datapacks でしょうか?

.minecraft
└── saves
    └── <ワールド>
        └── datapacks         ←ここ!
            ├── <データパック>
            ├── <データパック>
            └── <データパック>

だとしたら、バグでワールドが壊れた際に、データパックごとワールドを削除しないよう注意してくださいね。
せっかくデバッグしたのに、そのコードごとゴミ箱へ…いえ、ゴミ箱で済めばよいのですが…。

ワールドは Minecraft のゲーム内から削除できるデータです。
しかも、ゲーム内から削除するとゴミ箱を経由せず、復元は困難を極めるでしょう。

あなたのワークスペースがそういうワールドデータ内にあるというのは、少々危険かもしれません。

リソースパックと同時開発したい時

データパックはリソースパックとの連携も可能です。
単なる見栄えや音声リソースだけでなく、多言語対応でも密接に関わっていますね。

ただその場合、あなたのワークスペースは

.minecraft
├── resourcepacks             ←ここ!
│   ├── <リソースパック>
│   ├── <リソースパック>
│   └── <リソースパック>
└── saves
    └── <ワールド>
        └── datapacks         ←ここ!
            ├── <データパック>
            ├── <データパック>
            └── <データパック>

の二か所になるかと思います。
それ自体にあまり問題はないのですが、まとめて管理できないのはちょっと面倒かなと…個人的には思います。

まとめて管理できるワークスペースがほしくない?

正直な話、危険なのも嫌だし、一か所にまとまってないのも管理しづらい…。

そもそもですが、私としては、ワークスペースの場所を Minecraft 実行環境ディレクトリの外 に用意したいというのが本音です。

データパックとリソースパックのコアな部分だけをひとまとめにして持ち運べるワークスペース…バックアップも取りやすいし、GitHub に簡単に投げることができて、楽に使える形がほしい!

まあ、GitHub を使うかどうかは別として…

あなたも誤って削除しない安全な場所にひとまとまりで作業できるワークスペースを用意したくないですか?

これは、そんな私が望みを叶えるためにいろいろ奮闘してきた結果の備忘録です。

手順

今回行う手順は以下の通りです。

  1. ワークスペース構造を決める
  2. ワークスペースから本来の場所にコピーするスクリプトを書く
  3. ショートカットでコピーを実行できるようにする

見ての通り3つもステップがあるので、流石にお手軽とは言えませんが、それぞれの作業はほぼコピペで実現可能です。

1. ワークスペース構造を決める

使いやすいワークスペース構造は人それぞれだと思いますが、今回は以下のようなシンプルな構造にします。

作業場(ワークスペース)のディレクトリ構造

下部に謎のファイルがいくつかありますが、骨組みは

<ワークスペース>
├── datapacks
│   ├── <データパック>
│   ├── <データパック>
│   └── <データパック>
├── js
│   └── sync.js (今回コピーに使用するスクリプト)
└── resourcepacks
    ├── <リソースパック>
    ├── <リソースパック>
    └── <リソースパック>

という感じです。

2. ワークスペースからコピーするスクリプトを書く

ここで少しプログラミング要素が入ります。しかしながらほぼコピペで問題ないのでサクッと進んでいきますね。

有名どころのOSにはフォルダ丸ごとコピーどころか、綺麗にミラーリングできるコマンドが用意されています。

ぶっちゃげると、それを直接呼び出すのが最速なのですが、いくつか問題があったのでプログラム言語で制御しながら呼び出すことにしました。

【問題点】
1つのコマンドでデータパックとリソースパックを別々の場所にミラーリングはできない。
次のプロセスでコマンドは1つに制限されるのでこれは不都合。

【問題点】
datapacks フォルダを丸ごとミラーリングすると、 PaperMC のようにデフォルトで固有データパックが導入されるワールドや、事前に導入していた他のデータパックがあるワールドでは、それらが削除されてしまう(データパック毎に処理が必要)。

今回は JavaScript + node.js で実装します。
補足の URL から node.js をダウンロードし、インストールを完了させておいてください。いくらか設定を問われるかもしれませんが、ここで利用する分には全部デフォルトの設定でインストールして問題ありません。

Javascript
ブラウザでお馴染みのプログラム言語。
基本ブラウザでしか動作しません。

node.js https://nodejs.org/ja
ブラウザでしか動作しない JavaScript をそれ以外の場所で動作させるようにした実行環境。
インストールすればコマンドラインからでも js ファイルを実行できます。

以下のコードはコピペで使えます。

解説はJavaScriptの知識がいるのでスキップしてもらって構いません。

js/sync.js
// 1. 無名関数スコープ
(()=>{
  // 2. 厳密モード
  'use strict';

  // 3. モジュール読み込み
  const exec = require('child_process').exec;
  const path = require('path');
  const fs   = require('fs');
  const os   = require('os');

  // 4. 引数チェック
  if (process.argv.length < 6) {
    console.error('ERROR: Invalid arguments has been given.');
    process.exit(1);
  }

  // 5. 引数取得
  const datapacks_source = path.resolve(process.argv[2]);
  const datapacks_destination = path.resolve(process.argv[3]);
  const resourcepacks_source = path.resolve(process.argv[4]);
  const resourcepacks_destination = path.resolve(process.argv[5]);
  const option = process.argv[6];

  // 6. プラットフォームごとの同期コマンド取得
  function commandSynchronize(source, destination, file) {
    if (os.platform() === 'win32') {
      source = path.join(source, file)
      destination = path.join(destination, file)
      if (option === "--xcopy") {
        // Run on Save を利用する場合は robocopy はうまく動作しません.
        // コマンドの末尾に --xcopy を追記してください(削除機能がなくなります).
        return `xcopy "${source}" "${destination}" /E /Y`;
      } else {
        return `robocopy "${source}" "${destination}" /MIR /XF ".*"`;
      }
    } else {
      source = path.join(source, file)
      return `rsync -av --delete --exclude=".*" "${source}" "${destination}"`;
    }
  }

  // 7. exec 用ハンドラ
  function handle(error, stdout, stderr) {
    if (error) {
      console.error('ERROR:', error);
      process.exit(1);
    }
    console.log(`${stdout}`);
    console.error(`${stderr}`);
    console.log('sync complete successfully.');
  }
  
  // 8. フォルダの同期
  try {
    fs.readdirSync(datapacks_source).forEach(file => {
      exec(commandSynchronize(datapacks_source, datapacks_destination, file), handle);
    })
    fs.readdirSync(resourcepacks_source).forEach(file => {
      exec(commandSynchronize(resourcepacks_source,  resourcepacks_destination, file), handle);
    })
  } catch (error) {
    console.error('ERROR:', error);
    process.exit(1);
  }
})();

sync.js の仕様

> node js/sync.js <データパックの同期元> <データパックの同期先> <リソースパックの同期元> <リソースパックの同期先> [--xcopy]

<データパックの同期元> <データパックの同期先> <リソースパックの同期元> <リソースパックの同期先> を指定することで同期元から同期先へミラーリングが行われます。

同期先は同期元と全く同じ状態になるよう追加・変更・削除が行われます。

そういう仕様のため、冒頭で言った通り、同期元と同期先を逆にしてしまうとデータパックが全部消えます。
最初に稼働させるときは同期先が空っぽですからね…。逆にしてしまったら大事なデータパック本体が無を取得することになります。
正しい動作を確認するまでバックアップは取っておきましょう。

sync.js の解説

1. 無名関数スコープ JavaScript は関数内で宣言された変数・関数は、その関数のスコープ内に限定されます。

ファイル全体を無名関数 ()=>{...} で囲んで変数・関数の利用範囲を実質ファイル内に限定しつつ (()=>{...})() で無名関数を即座に実行しています。無名関数なので、それ自体もグローバル関数に影響がありません。

関数が生成するスコープ機能のおいしいところだけいただく定型文です。

2. 厳密モード

JavaScript はスコープの最初で 'use strict' と記述することで、よりよいチェックをしてくれるようになります。

3. モジュール読み込み いくつかモジュールを読み込んでいますが、非常に基本的なもので node.js に最初から全部組み込まれています。 別途モジュールをインストールする必要はありません。
4. 引数チェック コマンドライン引数の数をチェックしています。 最低限すべてのコピー元・コピー先が指定されている必要があります。
5. 引数取得 コマンドラインで渡された引数を取得しています。
6. プラットフォームごとの同期コマンド取得 Windows と Linux, MaxOS では同期コマンドが全く違う形式なので、プラットフォームごとに場合分けしています。

ここで返される文字列はコマンド内容を意味し、 exec に引数として渡すことで、実際にそのコマンドが実行されます。

Run on Save のように保存時にコピーさせる拡張機能で利用する際は robocopy が有効に動作しません。その場合の代価としてコマンド末尾に --xcopy と書き足した場合 xcopy に切り替わるよう処理しています。

7. exec 用ハンドラ `exec` の処理結果にどのように対応するかを記述したコールバック。

exec にコマンド文字列と一緒に引数として渡すと、処理が終了した際にこの関数が呼び出されます(正常終了したら sync complete successfully. というメッセージが表示されるはずです)。

8. フォルダの同期

datapacks フォルダと resourcepacks フォルダ内のそれぞれのパッケージを同期します。

datapacks フォルダそのものを本来の datapacks フォルダと同期させればもっとコードはシンプルになりますが、事前に入っていた他のデータパックがある場合、消滅してしまうので、データパック単位で同期させています。

これを js/sync.js に保存します。
以下のようなコマンドをターミナルから入力すれば、実際に実行されるようになります。

> node js/sync.js datapacks "<データパック同期先>" resourcepacks "<リソースパック同期先>"

ターミナルの開き方はメニューの [ターミナル] - [新しいターミナル] です(ショートカットキーはOSによって異なります)。

ターミナルは初期位置をワークスペースのディレクトリに設定しているので、相対パス js/sync.js でそのまま記述できます。便利です。

3. ショートカットでコピーを実行できるようにする

Visual Studio Code は Ctrl+Shift+B でデフォルトのビルドが実行されるようになっています。

そして、データパックにビルドは存在せず、その空白のビルド内容はカスタマイズ可能です。

つまり、ビルド内容を「sync.js を実行させるコマンド」に設定すれば、ショートカットキー一発でワークスペースから本来の場所に最新のデータパックとリソースパックを同期させることができるようになります。

拡張機能 Run on Save を使えば、保存するタイミングで同期を実行させることも可能なのですが、残念ながらrobocopy が機能してくれません。

編集中のファイルに遠慮してしまうのか robocopy 同期できず失敗に終わるようです。

一応 xcopy に差し替えることで単純コピーという形で似たことはできるので、sync.js に対策を施しているのですが、同期と違ってトラブルが起きやすいかもしれません(不要なものを削除してくれない)。

この設定方法は最後に書き記しておきます。

まず「ターミナル」から「既定のビルドタスクの構成」をクリック。

ターミナル - 既定のビルドタスクの構成

すると中央上部にポップアップが出ます。
ここで「テンプレートから tasks.json を生成」を選択します。

テンプレートから tasks.json を生成

もう一度中央上部にポップアップが出るので「Others 任意の外部コマンドを実行する例」を選択します。

Others 任意の外部コマンドを実行する例

そうすると、作業場に新たに .vscode というディレクトリが生成され、tasks.json という設定ファイルがテンプレート様式で準備されます。

設定フォルダ

初期の設定は以下のようになっています。

デフォルトのタスク設定

これを、次のように書き換えます。

設定ファイル書き換え例

.vscode/tasks.json
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "sync",
      "type": "shell",
      "command": "node",
      "args": [
        "js/sync.js",
        "datapacks",
        "../client/1.21.10/saves/environment/datapacks",
        "resourcepacks",
        "../client/1.21.10/resourcepacks"
      ],
      "problemMatcher": [],
      "group": {
        "kind": "build",
        "isDefault": true
      }
    }
  ]
}

../client/1.21.10/saves/environment/datapacks
をあなたの環境で実行される本来のデータパックディレクトリに
../client/1.21.10/resourcepacks
をあなたの環境で実行される本来のリソースパックディレクトリに書き換えてください。

保存後、Ctrl+Shift+B でデフォルトビルドを実行すると、ターミナルが開き、以下のように表示されるはずです。

実行時のターミナル

上記のような表示がなく、Error という文字列を見つけた場合、おそらくファイルパスに問題があるのかもしれません(もちろん私のソースコードのバグの可能性もありますよ…)。

最後に確認を忘れずに

そして、無事本来の場所にデータパックがコピーされているのを確認したら、作業完了です。お疲れ様でした!

データパックの同期確認

Run on Save を使う場合

Run on Save は複数存在し、Run and Save などの派生版も存在しているのですが、今回は認証済み拡張機能である emeraldwalk 製の Run on Save での解説をします。

Run on Save の拡張機能から設定を選び、以下のように [ワークスペース] タブを選択してから [settings.json] をクリックします。

設定ファイル生成

すると、以下のような位置に設定ファイルが生成されるので、次のコードに差し替えます。

設定ファイル

.vscode/settings.json
{
  "emeraldwalk.runonsave": {
    "autoClearConsole": true,
    "commands": [
      {
        "match": "\\.(mcfunction|mcmeta|json|nbt)$",
        "cmd": "node js/sync.js datapacks <データパック同期先> resourcepacks <リソースパック同期先> --xcopy"
      }
    ]
  }
}

既に settings.json が存在していて、別の設定項目が記述されている場合は "emeraldwalk.runonsave" の項目だけを差し替えましょう。

まとめ

こんな感じです。
そこそこの作業量ですが、ほぼコピペで何とかなると思います。

これのメリットは設定ごとまとめて持ち運べる点でしょうか。

設定ファイルをワークスペースとしたディレクトリ内に収めているのでショートカットキーで同期する処理ごと持ち運べます。

ただ、パスをミスるとデータが消えるのはどうしても怖いですね…。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?