見出し画像

MinecraftScriptAPI解説講座【その2】


・この記事の目標

chatSendをマスターする。

・3.まずは基礎から。

このシリーズの【その1】で作ったフォルダ構造がある体で話を進めていきます。
まだの人は是非こちらを↓

・index.jsを編集

一旦前回のindex.jsで書いた内容を全て消し去ってもらって、下のように書きます。

import { world } from "@minecraft/server";

world.beforeEvents.chatSend.subscribe((eventData) =>
{
  let {sender, message} = eventData, target = "";
  eventData.cancel;
  world.sendMessage(`${sender}${message}とコメントしました`);//「誰が」「何を」を明確にして、ワールドにメッセージを送る
});

良いね、これだけでもうプログラムバリバリ書いてる感じになってきますねえ。
ほなミニ解説を。


・import { world } from "@minecraft/server";
「world」という機能を「@minecraft/server」からインポート。

・world.beforeEvents.chatSend.subscribe((eventData) => {ここにプログラムを書く});
チャットが送られて、チャット欄に表示される前に呼ばれるイベント。
チャット欄に表示される前に色々いじくれるので、チャットそのものを無かったことに出来る(キャンセル)。

・let {sender, message} = eventData, target = "";
魔法の言葉。
「world.beforeEvents」、「world.afterEvents」から始まる関数では書くべし。
てか書かないと動かなかったりするので、まあ書いとくに越したことはない。
今回のイベント、「(world.beforeEvents.)chatSend」にはいくつかの機能がありまして…。

・cancel(メッセージをキャンセルする)
・message(メッセージの内容)
・sender(メッセージを送ったプレイヤー名)
・targets(不明)

この4つの機能の中から必要な物を使うために、
「let {使いたい機能達} = eventData, target = "";」と書くと思えば良いです。
今メジャーな書き方ですし、「そういうもん」として覚えましょう。

・eventData.cancel;
上でも触れましたが、メッセージをキャンセルします。
今回の場合はメッセージをキャンセルして、
代わりに「world.sendMessage()」という関数で、
「誰が」「何を」言ったかが明確に分かるようなメッセージを送っています。
この「world.sendMessage()」、マイクラのチャットですと、通常のメッセージの見た目と同じなんですね。
つまり、マイクラのチャットでは「メッセージをその場で編集して、メッセージを送っている」ように見える訳です。
実際には「一度メッセージをキャンセルし、別のメッセージを送っている」のです。
ま、見た目一緒なんで特に問題無しですね。
ただ、こういう流れのプログラムだよ、ってのを理解しておいて欲しいです。

・4.応用してくお。

・現在のティックを取得してメッセージと一緒に表示してみる

import { world, system } from "@minecraft/server";

world.beforeEvents.chatSend.subscribe((eventData) =>
{
  let {sender, message} = eventData, target = "";
  eventData.cancel;
  world.sendMessage(`ティック${system.currentTick}に、${sender}${message}とコメントしました`);//「誰が」「何を」を明確にして、ワールドにメッセージを送る
});

【変更点】
・最初の文のimportに、「system」を追加
・world.sendMessageの内容変更

【ミニ解説】
・system
system.currentTickやら、それ系使うのに必要な大元の機能。
割とよく使うので、常時import文の中({}のトコ)に書いといて良いかも。

・system.currentTick
これ前回もやりましたが、ティックを取得できます。
秒数に直したいなら20で割れば良いので、「system.currentTick/20」となりますね。

・コメントを複数送ってみる

import { world } from "@minecraft/server";

world.beforeEvents.chatSend.subscribe((eventData) =>
{
  let {sender, message} = eventData, target = "";
  eventData.cancel;
  world.sendMessage(`このメッセージを打ったのは${sender}です。`);//「誰が」を明確にして、ワールドにメッセージを送る
  world.sendMessage(`このメッセージの内容は${message}です。`);//「何を」を明確にして、ワールドにメッセージを送る
});

【変更点】
・world.sendMessageを2つにした。
・それに伴い、world.sendMessageの内容も変更。

【ミニ解説】
さっきまではずっと1つのメッセージしか送ってきませんでしたが、別に複数送ることも可能です。
…ってくらいかあ()

・コメントの色を変えてみる

import { world } from "@minecraft/server";

world.beforeEvents.chatSend.subscribe((eventData) =>
{
  let {sender, message} = eventData, target = "";
  eventData.cancel;
  world.sendMessage(`§b§l${sender}§rが§d§l${message}§rとコメントしました`);//「誰が」を太文字の水色、「何を」を太文字のピンク色にして、ワールドにメッセージを送る
});

【変更点】
・world.sendMessageの内容を変更。

【ミニ解説】
・§b§l
「world.sendMessage」の内容にあるコレ、一体何かというと…。
§bは「カラーコード」、§lは「フォーマットコード」と呼ばれる物です。
これらはテキストを編集する為に使われます。
こちらのページで分かりやすくまとめられているので、是非どうぞ。
テキストを装飾する場合は必ず、カラーコードの後にフォーマットコードを書くようにしてください。
逆にした場合、フォーマットコードは反映されず、カラーコードのみ反映されます。
ちなみに…。「§r」はカラーコード、フォーマットコードの内容をリセットするフォーマットコードです。
これを書くと、通常のテキストと同じように書くことが出来ます。

・特定の単語に反応させてみる

import { world } from "@minecraft/server";

world.beforeEvents.chatSend.subscribe((eventData) =>
{
  let {sender, message} = eventData, target = "";
  if(message.includes("ping"))
  {
    world.sendMessage("pong!");//もし「ping」という文字列がメッセージに含まれていれば、ワールドに「pong!」とメッセージを送る
  }
});

【変更点】
・eventData.cancelを削除。
・特定の単語が含まれている場合、メッセージに返信する。
含まれていない場合、普通にメッセージが送られる。
・これに伴い、world.sendMessageの内容を変更。

【ミニ解説】
・if(条件)
「もし〜ならば」という条件に当てはまる場合(条件がtrueの場合)のみ、その中に書いてあるプログラムを実行。

・message.includes("ping")
「message」の中に、「ping」という文字列が含まれているかをチェック。
含まれている場合は、「true」を返す。

【追記】
「ping」が含まれるメッセージを消して、「pong!」とだけチャットに表示させたい場合は、「eventData.cancel」を書けばOK。

・⚠️【難易度高】 擬似コマンドを作ってみる

import { world, system } from "@minecraft/server";

world.beforeEvents.chatSend.subscribe((eventData) =>
{
  let {sender, message} = eventData, target = "";
  eventData.cancel;
  //特定の文字列で始まっているか(擬似コマンドかどうか)判断
  if(message.startsWith("! "))
  {
    system.runTimeout(() =>
    {
      //使う変数達を宣言
      let text = message;//「message」という変数は編集出来ないので、代わりに「text」という変数でメッセージを編集
      let textData = [];//コマンドの名前、引数を分割して入れる配列

      text.replace("! ", "");//メッセージからコマンド部分を削除
      if(text.include(" "))
      {
        textData = text.split(" ");//半角スペース毎に区切ってtextDataへ格納
      }
      else
      {
        textData.push(text);//textを直接格納
      }

      //textDataの0番目、つまりコマンド名によって条件分岐
      switch(textData[0])
      {
        case "say":
         if(textData.length == 2)//引数が一個
         {
           //メッセージを送信
           world.sendMessage(`sayコマンドを実行。内容:${textData[1]}`);
         }
         break;
        case "tpAll":
         if(textData.length == 4)//引数が三個
         {
           //マイクラのコマンドを実行する事も出来る
           sender.runCommandAsync(`tp @e ${textData[1]} ${textData[2]} ${textData[3]}`);
         }
         break;
        case "killAll":
         if(textData.length == 1)//引数無し
         {
           sender.runCommandAsync("kill @e");
         }
         break;
        case "giveWeapon":
         if(textData.length == 1)//引数無し
         {
           //マイクラのコマンドを実行し、完了した事をチャット欄で知らせる
           sender.runCommandAsync(`give ${sender} diamond_sword`);
           world.sendMessage(`ダイヤモンドの剣を${sender}に渡しました。`);
         }
         break;
        default:
         //上記のどれにも当てはまらない場合
         world.sendMessage("このコマンドは存在しません。");
         break;
      }
    });
  }
  else
  {
    //「! 」で始まっていない(擬似コマンドではない)場合、普通にメッセージを送信
    world.sendMessage(message);
  }
});

【変更点】
・import文に「system」を追加。
・擬似コマンドかどうかを判断出来るように。
・擬似コマンドの実装。
・これに伴い、指定した擬似コマンドが見つからなかった場合の処理を追加。

【擬似コマンド一覧】
・! say 内容
・! tpAll X座標 Y座標 Z座標
・! killAll(引数無し)
・! giveWeapon(引数無し)

【しっかり解説】
・message.startsWith("! ")
メッセージが「! 」から始まっているか。
この擬似コマンドでは、「/」の代わりに「! 」をコマンドと見なし、
プログラムを実行する。

・system.runTimeout(() => {});
後に出てくる、「runCommandAsync()」という関数を実行する為に必要。
この関数は任意の数字を入れる事も出来るが、今回は最速で処理をしたい為、書いていない。(待つ動作がない、つまり最速で次のプログラムへ移れる)
この関数は任意の数字を入れていなくても、システム的には必ず「待つ動作」、つまり「遅延(ラグと思ってもらえれば)」が存在する。
これを念頭に、下の文章を読んでください。


では、最速で処理をしたいのに何故、「system.runTimeout」を書いているのか。
それは、「beforeEvents」で「runCommandAsync()」などの、
非同期処理と呼ばれる処理を遅延無しで実行する事は禁止されているからです。
簡単に言うと、「beforeEvents」で遅延させずに非同期処理を行う事は出来ません。
理屈…はあるとは思いますが、とにかくそういうルールです。
では、「beforeEvents」で非同期処理をしたい場合はどうすれば良いのか。
答えは簡単、遅延させれば良いんです。
遅延させるプログラムを非同期処理より前に書いておけば、非同期処理はエラーになる事なく動いてくれます。
なので、遅延させるプログラムの一種(他にも数種類あります)である、
「system.runTimeout」を書いています。

・let text = message;
プログラムの方にも書いてますが、「message」って、
値を見る事はできるけど値を変更する事が出来ない変数なんです。
見る(データ取得)専用ですね。
んで、今回は「message」を編集したかったので、「text」という適当な変数に「message」の内容を代入して、編集出来るようにした訳です。
「text」というのはこちらが宣言した変数で、textの前に「let」と書いてあるので、編集可能です。


変数には「let」、「const」、「var」の三種類がありますが、
let、varは編集可能。constは編集不可能(見る専用)となってます。
letとvarの違いについてはここでは触れませんが、個人的にはletを強くオススメします。
特に理由が無いのであれば、letで良いかなあと思ってます。

・let textData = [];
「配列」と呼ばれる、これまた変数の一種を宣言しています。
配列は簡単に言うと、「その変数の中にいくつものデータを保存出来る」ものです。便利だなあ。好き。
今回は「コマンド名」「引数」をそれぞれ格納(保存)する用に使っています。
こうする事で、「配列のn番目」を選択してデータを取ってくるだけで、簡単にプログラムが書けるようになります。


コマンド名を選択したいだけであれば、
「tp @s ~ ~ ~」よりも、
配列0番目:tp
配列1番目:@s
配列2番目:~
配列3番目:~
配列4番目:~
の方が、よりコマンド名を取得しやすそうですよね。
(ちなみに、配列は1、2、3、…ではなく、0、1、2、…と数えます)

・textData = text.split(" ");
先ほどの配列「textData」に、代入してます。
何を代入してるかと言うと、
「text」(メッセージ)を任意の文字列毎に区切った物を代入してます。
今回の場合は半角スペースですね。
この「区切る」作業が、ただの文字列を配列で扱う形式に「変換」させる作業になります。
んで、区切ったやつは配列の形式になってます。
それをtextDataに代入してるんですから、
textDataに無事、要素が追加される(代入なので、厳密には置き換えですが)事になります。

・textData.push(text);
textに任意の文字列(今回は半角スペース)が含まれていない場合、文字列の区切りようがないので、直接配列に要素を押し込んで(push)ます。
配列名.pushでも、配列に要素を追加する事が可能です。

・switch(textData[0])

配列のn番目は、「配列名[n]」と表記されます。
textDataの0番目、つまりコマンド名を取得したいので、textData[0]と書きます。


じゃあ「switch」は何やねんと思った方。
条件分岐です。
Q.そもそも条件分岐ってなーに?
A.その条件に合うかどうかで処理を変えていくことです。

例えば…。
「箱に赤と青のボールが入っています。ボールを一個取り出して、それが赤いボールだった場合は左手を、青いボールだった場合は右手をあげなさい。」
という命令?があったとしましょう。
この場合だと、「ボールを取り出した結果が条件」です。
赤いボールなら左手を上げるという処理(行動)を、
青いボールなら右手を上げるという別の処理(行動)をしますよね。
つまり、条件によって処理(行動)が変わってくるんです。
これが、条件分岐です。


「switch」も条件分岐の一種で、
〜の時
〜の時
〜の時
以上の条件に当てはまらなかった時
という書き方をしていきます。
コードを書くと、

switch(条件)
{
  case A://「〜の時」と同じ意味
   処理A;//処理をする
   break;//この処理を書かないと、次の「〜の時」も考えるようになる。敢えて書く場合もあるけど、普段は書いといて良いと思う。
 case B:
   処理B;
   break;
 case C:
   処理C;
   break;
 default://以上の条件に当てはまらなかった時
   処理;
   break;
}

・textData.length
配列「textData」の要素の数を取得します。
こちらは0、1、2、…という数え方ではなく、普通に1、2、3、…という数え方になります。

・if(textData.length == コマンド毎に定義したコマンド名と引数の数)
この部分は何をやっているかというと、入力されたコマンド名と引数の数が、
コマンド毎に定義されてるコマンド名と引数の数と同じかを判断してます。
コマンドと引数は共に「textData」に格納されているので、その要素の個数を調べれば、コマンド毎に定義されてるコマンド名と引数の数と同じか判断出来ます。

・runCommandAsync()
非同期処理
です。
マイクラで定義されてるコマンドを実行する事が出来ます。
普段チャットに打ち込むコマンドを、直接この関数に書けばオッケーです。
ただし、()の中身は文字列である必要があるので、基本は
「runCommandAsync("各コマンド")」となると思います。
例外↓

・・・さっきの続き
let command = "各コマンド";
sender.runCommandAsync(command);

こんな感じで、文字列を定義した変数をぶち込むことも可能です。

・終わりに

かなーり長くなってしまいましたが、これにて「world.beforeEvents.chatSend」は完璧かと思います。
「world.beforeEvents.chatSend」もありますが、「cancel」が使えない以外は特に違いがないので、割愛します。
今回は「world.beforeEvents.chatSend.subscribe」でしたが、実は
「world.beforeEvents.chatSend.unsubscribe」というのもあります。
こちらはあまり使わない(個人談)のと、使い道不明なので、分かったらまた書きます。
見た感じ、unsubscribeはそのイベントをオフにする役割かと思ってまして、特定のチャットをしたらチャットイベントが反応しなくなるようにする、なんて使い方が出来るのでは?と考え中。
ま、これについては進展あればそのうち書きます。
長くなりましたが、ほな今回はこの辺で。

次の記事はこちら↓
製作中…

まとめはこちら↓

いいなと思ったら応援しよう!