こちらの記事はMinecraft Command Advent Calendar 2024 16日目の記事です。
はじめに
こんにちは。この記事では、Minecraft(JE版)のコマンドの話…の根底にある「プログラミング」についてお話ししようと思います。
対象読者
- Minecraftのコマンドを学びたい、または学んでいる最中の初心者コマンダー
- プログラミング言語を触った事がない方
プログラミングを学びませんか?
mcfunction、つまりコマンドには一種のプログラミング言語と呼べるほどの自由度があります。しかしコマンド特有の制限、情報の不足、バージョンアップによる仕様変更などから、0からコマンドを理解するのは難しくなってきています。そんなコマンドの理解の手助けになってくれるのがプログラミングです。
世で広く流通している、javascriptやPythonといったプログラミング言語1はコマンドに比べてとても読みやすく、1つ身につけてしまえば、学んだ知識をコマンドに応用できます。
いきなりコマンドをふれる前に、プログラミングを学んで背景知識を身に着けて、学習曲線を滑らかにしてみませんか?
余談:この記事を書くにあたって
上記の話題が、ある配布マップ制作鯖にて議論されていました。しかし初心者にプログラミングを理解させるのは、学習コストが高く非効率です。そして何よりも、初心者がコマンドへのやる気を失ってしまいかねません。そのため、「大事な概念だけ抽出してコスト削減をしたらいいのでは?」というアイデアのもと、この記事は作成されました。
この記事の狙い
この記事の目的は、コマンダーまたはコマンド初心者へ学習の助けとなる概念をまとめることにあります。ですが、あくまで「助け」であり、Minecraftの具体的なコマンドまで取り上げることはございません。また、プログラミングの基礎的概念である「変数」「関数」を説明しますが、あくまで「概念」の解説だけになっています。
なんで取り上げてくれないのさ?
理由は2つ。
1つ目に、具体的コマンド、プログラムまで取り上げると記事が完成しそうになかったから。
2つ目に、そもそも私はコマンドに強くないから。偉そうな口ぶりしてますが、私はただの初心者です。コマンドチョットワカルにいつかなりたいですね…。
本編
それでは学んでいきましょう。身構える必要はありません。「ほへ〜」と言いながら読んでみてください。ここから先には実際のプログラミング言語を用いたコードが登場しますが、ただの見本なのでお気になさらず。「コード」と聞いて身構えるべからず。シンプルにしてありますからご安心を。
変数とは
変数とは、「データを保存して自由に取り出せる、名前のついた箱」2です。
例えば、みかんの数を保存する変数を考えてみましょう。
みかんの数が知りたいのなら、変数の中身を見ればよいですね。
みかんの平均重量を知りたいのなら、みかんの総重量を、変数に入った値で割り算すれば求められます。
このように変数は、データを保存し、使いまわすことができます。次はその使い方を説明します。
あなたが変数を利用する時、以下3ステップを踏む必要があります。
- 変数を作る
- 変数に値を保存する
- 変数の中身を覗き、利用する
詳しく見ていきましょう。
1. 変数を作る
まずは、変数を作ることから始めましょう。と言っても、作り方はシンプルです。
変数は「変数名」と「データ型」の2つを決めればすぐに作れます。
「変数名」とは変数の名前です。「データ型」は、次の章で扱う内容のため、ここでは言及しません。
このようにして、変数を作ることを「変数の宣言」と言います。
「変数名」は、大文字小文字のアルファべット、一部の記号、数字を使います。ひらがなや漢字も使うことはできますが、あまり推奨されていません。慣習的にも英語を使う方が無難です。特にsuuji
(数字)やhiduke
(日付)のような日本語由来の名前ではなくnum
(numberの省略形)やdate
のように、英単語を使った変数名が多く利用されますね。
mcfunctionでは、変数名に日本語を利用できません。気をつけましょう。また、「x」といった意味のわかりにくい変数名は控えましょう。もっと詳しく知りたい方は「変数 命名規則」 「スネークケース」と調べてみてください。
2. 変数に値を保存する
変数に値を代入しなければ、値の読み取りができません。
変数に値を保存することを、変数に「代入する」、「保存する」、「格納する」と言います。
変数には、実際に使う値でなくて構わないので、エラー防止のために何か代入する事が多いです。例えば、数値を保存する変数に、予め0
を入れておく、とか。このような値を「初期値」と呼びます。
また、変数の宣言と値の代入を(同時に)行うことを「変数の初期化」と言います。mcfuncionでは初期化がしにくいので、宣言と代入の2行に分けることが多いです。
余談:再代入について
再代入は説明されることが少ないため、混乱する人が多いです。
変数にすでに値が格納された状態で、新しい値を代入しようとする操作を「再代入」と言います。再代入することは珍しくありません。
このとき、古いデータは上書きされ「消去される」ことに注意してください。
3. 変数の値を覗き、利用する。
例えば、orange_count
に3が入っているとします。
その後(orange_count
の中身の数字)+7という計算を行い、結果を表示します。
orange_count = 3 # orange_countという変数を宣言し3を代入します
print(orange_count+7) # orange_count + 7 の演算結果を表示します
# 10が表示されます
このときorange_count+7
という計算は、orange_count
の値によって結果が変化します。コンピュータはorange_count
の値が何か調べて、その値を計算式に適用する操作を行うため、結果が変化します。
このように、変数の中身を覗いて、値を取得すること、使うことを変数を「参照する」と言います。
以上の3ステップを踏んで、変数を利用しましょう。
よくある失敗は
- 変数の宣言忘れ
- 変数の名前間違え
- 値の格納忘れ
が多いです。これらはバグの原因になるので気をつけてくださいね。
データ型とは
データにも種類があります。データ型は、コンピュータが混乱しないように、データの種類を明らかにしてくれる存在です。
具体例を見てみましょう。次の「演算」は何になるでしょうか?
1+2
あ+か
1+あ
1の答えは簡単で、3
だと思います。
2の答えはどうでしょう?予想回答はあか
です。
では3は?数字と文字の足し算を、どう解釈すべきでしょうか?
あなたは、1
やあ
を数値なのか文字なのか識別したから、1と2の問題を答えられたのです。でも、コンピュータは、教えられるまで判断することができません(数値の1
も数字の”1”
もありますからね)
少し前に使った、みかんの例にも戻ってみましょうか。
もしも、みかんの数が小数になっていたらどうでしょう?(普通)みかんの個数は整数なので、困りますね。他のプログラムがエラーを吐いてしまうかも。みかんの数は整数値のみ受け付けるようにしたいですね。
以上の理由から、私たちは変数に入る予定の「データの種類」をコンピュータに伝える必要があります。このような「データの種類」のことを「データ型」と呼びます。
データの種類を明記するには、型を指定する他に、代入する値の方でも指定する必要があります。例えば、文字列を表すなら”もじれつ”
のように、”
(ダブルクオーテーション)で括る必要があります。数値の場合は、100.0f
や1b
のように、データの末尾にデータ型専用のアルファベット(接尾辞)を付ける必要があります。
正確には、このような接尾辞はデータ型によって省略可能です。ですが、省略して動かなくなることもあるため、つけるのが無難でしょう。
こちらのWikiにはMinecraftで使われるデータ型の詳細がまとめられています。
では、どんなデータ型があるのでしょうか。代表的なデータ方をいくつか紹介しましょう。
Int
一つ目の型はInteger,通称Int
型です。その名の通り、「整数」を表します。Minecraftのデータ型のうち唯一接尾辞がありません。それだけありふれたデータ型です。
例:11
,-4
,2024
Double
Double
型です。こちらは倍精度浮動小数点数を表します。何やら難しい用語ですが、「小数」と考えていただいて構いません。コンピュータの世界では分数も小数表記にします。
接尾辞はd
です。省略可能ですが、データ型の意識を持つために書くことを推奨します。
プログラミングの世界では、整数、小数をまとめて「実数」と言います。数学的な定義の実数とは意味が異なりますね。
例:1.36d
,3.00d
,3.1415
String
String
型は、文章、つまりは文字を並べた「文字列」を表すデータ型です。接尾辞はないですが、文字列を "
(ダブルクオーテーション)で括りましょう。Minecraftにおける全ての文字、文字列は全てString
型です。
例:"Test"
,"アドベントカレンダー"
String
を表示する時、 "
は表示されません。表示したいのなら \"
と入力してただの "
と区別します(こういうのを「エスケープシーケンス」と言います。改行したいときによく使います)
List
List
型は、リスト、または配列と呼ばれる型で、今までの型とは少し異なります。リストは、「データを1列に並べたデータの集合」です(厳密にはリストと配列には差異がありますが、Minecraftでは無視して構いません)3
リストは、「同じデータ型」を「コンマで区切って」並べ、まとめた型です。リスト全体の名前を「リスト名」、リストそれぞれのデータを、リストの「要素」と言います。要素には先頭から番号が振られていて「添字」と言います。
list = [1,3,5,7,9]
strings = ["私の","好きな","言語は"]
list[3] # 7
strings[1] # "好きな"
<配列名>[<整数>]
の形で要素を参照できます。[]
を使うことに注目しましょう。
ここで注意なのですが先頭の要素の番号は 0
です。1
から始まらないのはコンピュータの世界の慣習です。 少しだけ難しい話をすると、N
個の要素を持った配列の要素は、0
以上N-1
以下、または、0
以上N
未満の整数でアクセスすることができます。
例えば、Minecraftで座標情報を保持する Pos
NBTは Double
型のList
です。
(NBTがわからない方へ : Minecraftの「変数」だと考えてください)
ここに、Pos[1.00d,2.11d,3.5d]
というDouble
型を並べたList
があるとすると
-
Pos[0]
は先頭から0番目の要素:1.00d
-
Pos[1]
は先頭から1番目の要素:2.11d
-
Pos[2]
は先頭から2番目の要素:3.5d
として保存されています。
注意点として、存在しないはずの要素にアクセスしようとするとエラーになります。例えばPos[3]
という存在しない要素にアクセスしようとするとエラーになります。
Struct
Struct
型は構造体、Minecraftでは Component
と呼ばれる型です。構造体は「異なるデータ型」同士を「一つにまとめる」ことができます。構造体には「キー」と「値」があります。次の例を見てください。
struct Item{
String Name; // アイテムの名前が入るItem内部の変数です
int Count; // アイテムの個数が入るItem内部の変数です
}
Item stick = {
Name : "Stick";
Count: 64;
}
stick.Name // Stick
stick.Count // 64
Minecraftのアイテムは、たった一つのデータ型で表現できるような単純なものではありません。では、複数のデータ型を組み合わせて「大きなデータ型」を自作するのはどうでしょうか?これが、Struct
のアイディアです。
データ型を新しく作るのですから、「定義」が必要ですね。前半の4行は、Item
というStruct
はどういう構造体なのか定義しています。Name
という文字列のデータと、Count
という整数のデータを2つ組み合わせていますね。この場合、キーは Name
とCount
で、値のデータ型は String
型、Int
型で指定されています。
後半の行では、stick
という構造体を作成して、値を具体的に設定しています。Name
は"Stick"
、Count
は64
です。
構造体の中の変数を「メンバ」とも言います。
メンバを参照するために、見本では<構造体名>.<メンバ名>
でアクセスしていますね。
ここまでは少し単純でしたが、実際はもっと複雑な構造体が使われます。例えば、構造体の中に構造体が入っていることがよくあります。このような状態を「入れ子」または「ネスト」 と言います。例えば、こんなコマンドを見てみましょう。
/give @p minecraft:stick {display:{Name:[{"text:":"なんかすごいー棒"}]}} 1
display
という構造体のメンバにName
という構造体があって、Name
は構造体のリストを持っているようですね。構造体のネストは、コマンドで使われる「JSON」にも登場するので、ぜひ覚えてくださいね。
以上で変数の説明は終わりです。かなり長くなってしまいました…。
ですがもう一つ大事な概念があります。関数です。
関数とは
「関数」とは「一連の処理をまとめたもの」です。
処理をまとめることは
- バグの減少
- 管理が容易に
- 繰り返し使える、または部品として提供できる
このようなメリットがあります。ちょっと漠然としていてイメージしにくいですね。
では具体例として、「C」というプログラミング言語で書かれた次のコードを使って解説します。落ち着いて英単語の意味を追ってください。
int square_area(int width,int height){
return width * height;
}
この関数は「四角形の縦と横の長さを受け取り、面積を返す」ことを目的とした関数です。
「ただ二つの変数を掛け算するだけなのに、なんでまとめているんだ」って思いませんか?
では、こんなコードを追加してみましょう。
int square_area(int width,int height){
if((width * height)<=0)){
printf("面積が不正です");
error(); // 不正なデータであることを外部に伝えています
}
return width * height;
}
square_area(5,4); // 20
square_area(-2,6) // "面積が不正です" エラー
面積が0以下なってしまったらエラーを伝えるようにしました。これで、面積計算がより安全になりましたね。この時、編集したのは「関数を定義した部分」のみです。
もしも面積計算を関数にまとめていなかったら、ファイル内に何度も修正する必要が出てしまい面倒でした。面倒なだけなら最悪良いのですが、修正し忘れでバグが発生してしまうかもしれません。
プログラミングにはDRY(Don't Repeat Yourself)原則という考え方があります。意味は「同じコードを何度も書くな」。コードの記述量を減らして、楽をしてそしてバグを減らしていくために、関数を活用していきましょう。
関数名/引数/返り値
次はこの関数を詳細に見ていきましょう。
関数は
- 関数名
- 引数
- 返り値(戻り値)
- 処理
の4つで構成されます。
関数名は square_area
です。変数名と区別するために、square_area()
と ()
をつけることもあります。関数名は、関数を使うために必要な名前です。
関数を使うことを、関数の「呼び出し」といいます。
「引数(ひきすう)」 は2つのInt
型のデータwidth
と height
です。
引数とは、外部から入力を受け付けられる特別な変数です。主に「処理したいが具体的にわからないデータ」を引数に指定します。今回なら、値のわからない、長方形の横と縦の整数値(Int
型)を受け取ります。関数の処理内容によっては、引数が不要になることもあります。
処理は、if〜returnの一連の行部分です。ここで、関数の動作を全て定義します。
返り値は、return
のあとに書いてあるwidth * height
のことです。今回なら横と縦の値を掛け合わせた値です。このように、関数の「結果」として出力されるデータのことを 関数の「返り値」と言います。他にも「戻り値」と言う事もあります。
以上の4つを記述することで、関数が使えるようになります。
関数を作ることを 「関数の宣言」または、「関数の定義」といいます。変数の場合と同じですね。
以上で関数の説明は終わりです。関数は非常に便利です。繰り返される処理は関数にまとめて、安全で効率的なコードを書きましょう!
調べること
最後に、コマンドの勉強法を説明します。
コマンドは、トライアンドエラーの繰り返しです。コードを書いて、バグったら原因を探して、動いたら喜んで…このサイクルを回しながら、成長していきます。
この成長に必要な力は、ずばり「検索力」です。検索力さえあればだいたいの問題は解決できます。
皆様、ググりましょう。Googleって便利ですよね。使いましょう。
「コマンド力 = 検索力」です。
とにかく調べましょう。コマンドの情報は海外ソースであることも多いですが、英語に怯んではいけません。Google翻訳やDeepL翻訳を活用しましょう。新しい用語が出てきてわからないのなら、その用語まで調べましょう。とにかく調べるのです。
ですが、調べてもわからないことはたくさんあります。
そのため、質問することも大切です。
質問をする際には、自分がどこまで調べたか、どこまで知っているか(何も調べずに質問してはいけませんよ!)、何がわからないか、をはっきり伝えるようにしましょう。正確な返答が得やすくなり、また自分の思考の整理にもなります。
有名なコマンドDiscord鯖の招待リンクを貼っておきます。ログを読むだけでもかなり参考になりますのでぜひ参加してくださいね。
おわりに
お疲れ様でした。これらの概念を理解できたのなら、コマンドを学ぶのにちょっと楽できるはずです。当たり前ですが、プログラミングを学ぶ時でも役立ちます。それはそうと、アドベントカレンダーはそろそろ終盤ですね。最後まで盛り上がっていきましょう!
それでは、よきコマンドライフを〜