マンガサイト
漫画サイト。 それは漫画がブラウザで読める web サイトのことである。
漫画サイトには大きく分けて二種類ある。一方が違法サイトで、もう一方が合法サイトである。 仕組みは表面上はどちらもよく似ている。内部的には違うが、目的が違うので手段が違うと言っていいだろうが、大まかには同じようなものだ。
ここでは、違法サイトについて調べてみたい。
まず、あるひとつの違法サイトでのコンテンツの総数を数えてみたい。 現段階で、数え上げたのは四万四千のタイトル数だった。これは全てがコミックの1巻分ではなくて、第何話や、週刊誌の第何号というものを含めての数で、プラスこの数の20%分くらいがまだ隠れ層になっていて表出していない。
これはどう云うことかというと、いずれ説明するが、今回とった手法が関係しているが、例えば 100 回ランダムに違法サイトのページアドレスを出して、データベースに記録するということを繰り返して、次回はデータベースに記録されているもの以外をデータベースに記録していくようにしたとして、100 回のうち 20 回ほどが、記録されてないアドレスになってきた場合、続けていくと100 回のうち 15 、10 、とだんだんと記録されていないアドレスがランダムでは出にくくなっていくことになる。 このことをここで、隠れ層と云った。 あくまでも、違法サイトのコンテンツページのアドレスを前もって知っているわけではなく、ページにあるマンガ画像が表示されない方法で、ダウンロードせずにコンテンツが埋め込まれている URL を収集して、漫画のタイトルや著者情報を分析していく。
Lua Programming
wikibooks https://en.m.wikibooks.org/wiki/Lua_Programming
Lua Programming
GNU Lesser General Public License, GNU Free Documentation License, GNU GENERAL PUBLIC LICENSE
- Lua Programming
- Lua Programming
- Introduction
- Expressions
- Statements
- Functions
- Standard libraries
- Appendix:Software testing
- Glossary
Lua Programming
Introduction
Lua(「LUA」という表記は正しくありません)は、強力で、高速で、軽量で、埋め込み可能なプログラミング言語です。多くのフレームワーク、ゲーム、その他のアプリケーションで使用されています。単独で使用することもできますが、他のアプリケーションに簡単に組み込むことができるように設計されています。これは、非常に移植性の高いCプログラミング言語のサブセットであるANSI Cで実装されています。つまり、他のほとんどのスクリプト言語では実行できない多くのシステムやデバイスで実行できます。この本の目的は、以前のプログラミング経験に関係なく、誰にでもLuaプログラミングを教えることです。この本は、プログラミングの紹介として、これまでプログラミングしたことがない人のために、またはLuaの紹介として、他の言語でのプログラミング経験のある人のために使用できます。Luaを使用する開発プラットフォームやゲームはたくさんあるので、この本はLuaの使い方を学び、その開発プラットフォームでそれを使用するためにも使用できます。
この本は、Lua の最新バージョンの使用法を教えることを目的としています。これは、Lua の新しいバージョンがリリースされると、定期的に更新が試みられることを意味します(Luaのリリースはそれほど頻繁ではないため、それほど難しくはありません)。現在、この本は以前のバージョンである Lua5.2 の最新版です。5.xブランチ(Lua5.0およびLua5.1)で古いバージョンの Lua を使用する組み込み環境で Lua を使用している場合でも、資料は十分に関連しているはずです。
Lua は、リオデジャネイロのポンティフィカルカトリック大学の研究所で設計および保守されています。作成者は、 Roberto Ierusalimschy、Waldemar Celes 、Luiz Henrique de Figueiredoです。
「ルア」(LOO-ahと発音)はポルトガル語で「月」を意味します。そのため、頭字語でも略語でもありませんが、名詞です。より具体的には、「ルア」は名前、地球の月の名前、そして言語の名前です。ほとんどの名前と同様に、小文字で最初の大文字、つまり「Lua」を使用して記述する必要があります。ひどくて紛らわしい「LUA」とは書かないでください。人によって意味が異なる略語になります。だから、ぜひ「Lua」と書くようにしてくださいね!
Luaは、TeCGraf(リオデジャネイロのポンティフィカルカトリック大学の研究所)によって設計された2つの言語、DELとSolに由来します。 DELは「データ入力言語」を意味し、Solは「単純なオブジェクト言語」を意味し、ポルトガル語で太陽も意味します。そのため、ポルトガル語で「月」を意味するため、Luaという名前が選ばれました。ブラジルの石油会社であるPetrobrasのために作成されましたが、TeCGrafの他の多くのプロジェクトでも使用され、現在では世界中の多数のプロジェクトで使用されています。 Luaは、組み込みゲーム開発の分野における主要な言語の1つです。
Lua の主な利点の1つは、そのシンプルさです。一部の企業は、その利点のためだけにそれを使用しています。プログラミング言語を使用して特定のタスクを実行できれば、従業員はよりよく働くことができると考えていますが、複雑なプログラミング言語のフルコースを従業員に提供する余裕はありません。ここでの Bash や Batch のようないくつかの非常に単純な言語は、これらのタスクを実行するのに十分強力ではありませんが、Lua は強力で単純です。 Luaのもう1つの重要な利点は、組み込み機能です。これは、開発全体を通じて最も重要な特性の1つでした。 World of Warcraft や ROBLOX のようなゲームは、ユーザーが使用できるように、アプリケーションにLuaを埋め込むことができる必要があります。
プログラミングは、組み込みアプリケーション内で実行されるプログラムの場合はスクリプトとも呼ばれ、コンピュータープログラムを作成するプロセスです。プログラミング言語は、コンピュータープログラムに含まれているコンピューターコードを介してコンピューターに指示を与えるために使用される言語です。プログラミング言語は、英語の文法に似た構文と、言語で提供される基本関数であるライブラリの2つで構成されています。これらのライブラリは、英語の語彙と比較できます。
Hello, world
Lua は、アプリケーションに埋め込まれて使用することも、単独で使用することもできます。この本では、Luaをコンピューターにインストールするプロセスについては説明していませんが、codepadまたはthe Lua demoを使用してコードを実行できます。この本の Lua コードの最初の例は、基本的で伝統的な「hello world」プログラムです。
「Hello World」のプログラムは、表示装置に「Hello world」出力するコンピュータプログラムです。これは通常、ほとんどのプログラミング言語で可能な最も単純なプログラムの1つであるため、プログラミング言語の最も基本的な構文を初心者に説明したり、言語またはシステムが正しく動作していることを確認したりするためによく使用されます。
print("Hello, world!")
上記のコードは、Hello, world! というテキストを出力します。紙に何かを印刷するのではなく、出力にテキストを表示することを参照してプリントします。これは、print
という関数を呼び出すことによって引数として文字列 "Hello, world!" を使用して行われます。これについては、関数に関する章で説明します。
Lua はほとんどの場合、低レベルのアプリケーションに埋め込まれていることに注意してください。つまり、print
関数は、ユーザーに表示される領域にテキストを常に表示するとは限りません。これらのアプリケーションのプログラミングインターフェイスのドキュメントでは、一般に、テキストをユーザーに表示する方法について説明します。
Comments
コメントとは、プログラミング言語によって無視されるコード注釈です。コメントは、1行または複数行のコードの説明、プログラムの文書化、コードの一時的な無効化、またはその他の理由で使用できます。Luaがコメントだと認識できるようにするには、接頭辞として2つのハイフン--
を付ける必要があり、独自の行または別の行の末尾に配置できます。
print("This is normal code.") -- This is a comment print("This is still normal code.") -- This is a comment at the end of a line of code.
これらのコメントはショートコメントと呼ばれます。長い括弧--[[
で始まり、多くの行に続く長いコメントを作成することもできます。
print("This is normal code") --[[Line 1 Line 2 ]]
長い角かっこは2つの角かっこ [[
で 構成され、その中央に任意の数の等号 =
を付けることができます。その等号の数は、長い括弧のレベルと呼ばれます。長い角かっこは、同じレベルの次の角かっこがあるところまで続きます。等号のない長い角かっこは、レベル0の長い角かっこと呼ばれます。このアプローチでは、2つの角かっこの中央に等号を追加することにより、長いコメントの内側で閉じている二重角かっこを使用できます。コメントを使用してコードのブロックを無効にする場合は、これを行うと便利なことがよくあります。
--[==[ This is a comment that contains a closing long bracket of level 0 which is here: ]] However, the closing double bracket doesn't make the comment end, because the comment was opened with an opening long bracket of level 2, and only a closing long bracket of level 2 can close it. ]==]
上記の例では、レベル0の閉じている長い括弧(]]
)はコメントを閉じませんが、レベル2の閉じている長い括弧(]==]
)で閉じます。
Syntax
プログラミング言語の構文は、文法が文や単語の書き方を定義するのと同じように、ステートメントや式をそのプログラミング言語で書く方法を定義します。文と表現はそれぞれ文と単語と比較することができます。式は、値を持ち、評価できるコードの断片です。一方、ステートメントは、実行可能で、命令とその命令を使用する1つまたは複数の式を含むコードの断片です。たとえば、3 + 5
は式であり、variable = 3 + 5
はその式の値を変数に設定するステートメントです。
Lua の構文全体は、Lua Webサイトの拡張バッカスナウア記法で見つけることができますが、それを読んでも何も理解できません。拡張バッカスナウア記法はメタ言語であり、メタウェブサイトがウェブサイトに関するウェブサイトであるように、別の言語を説明するために使用される言語であり、Lua ではメタテーブルが他のテーブルの動作を定義するテーブルです(この本の後半のメタテーブルとテーブルについてで学習します)。ただし、この本では、拡張バッカスナウア記法を学ぶ必要はありません。Luaのような言語はメタ言語を使用して説明できますが、単語や文を使用して英語で説明することもできます。これはまさに、この本がしていることです。
英語は別の言語を説明するために使用できるため、それ自体がメタ言語である必要があります(メタ言語の定義に対応しているため)。これは確かに事実です。また、プログラミング言語の目的は命令を記述することであり、英語でそれを行うことができるため、英語もプログラミング言語である必要があります。これは、ある意味で、そうです。実際、英語は多くのことに使用できる言語です。ただし、拡張バッカスナウア記法は特殊言語であり、プログラミング言語も特殊言語です。専門化は、特定のことを行うのは非常に得意であるが、他のことを行うことができないという特徴です。拡張バッカスナウア記法は他の言語の記述に非常に優れていますが、指示を書いたりメッセージを伝えたりするために使用することはできません。プログラミング言語は指示を与えるのに非常に優れていますが、言語の記述やメッセージの伝達には使用できません。
英語は、言語の説明、指示の提供、メッセージの伝達など、すべてを行うことができます。しかし、プログラミング言語と同等のことはあまり得意ではありません。実際、英語では指示を与えるのは非常に苦手なので、コンピューターに指示を与えるために使用された場合、コンピューターは何も理解しません。これは、コンピューターが非常に正確で明確な指示を必要とするためです。
Obtaining Lua
Lua は、Lua の公式Webサイトのダウンロードページから入手できます。手順もそこにあります。ダウンロードボタンはソースコード用ですが、おそらくあなたが望むものではありません。おそらくバイナリを探しているので、ページを見てそれらに関する情報を見つける必要があります(使用しているプラットフォームによって異なります)。この本の目的は、Lua 言語を教えることだけであり、Lua ツールの使用法を教えることではありません。この本では読者は組み込み環境で Lua を使用すると想定れていますが、必ずしもそのとおりであるわけではありません。それは、この本の中では、Lua の使用法をスタンドアロン言語として説明していないことを意味するだけです。
Quiz
この章の内容を理解したことを確認するために質問がいくつかあります。これらの質問の対する答えを見つけるには、この章に記載されていない知識が必要になる場合があることに注意してださい。これは正常なことです。クイズは学習体験の一部であり、本の他の場所では入手できない情報を紹介することができます。
2 レベル0の長いコメントはどれですか?
選択肢 | |
---|---|
--Comment |
|
[[Comment]] |
|
--[[Comment]] |
|
--[=[Comment]=] |
|
[=[Comment]=] |
3 拡張バッカスナウア記法とは何ですか?
選択肢 | |
---|---|
A language | |
A programming language | |
A natural (or ordinary) language | |
A notation | |
A metalanguage | |
A markup language |
Expressions
前に説明したように、式は値を持ち、評価できるコードの断片です。これらは(関数呼び出しを除いて)直接実行できないため、式で構成される次のコードのみを含むスクリプトはエラーになります。
3 + 5 -- The code above is erroneous because all it contains is an expression. -- The computer cannot execute '3 + 5', since that does not make sense.
コードは一連のステートメントで構成されている必要があります。これらのステートメントには、ステートメントが命令を実行するために操作または使用する必要のある値となる式を含めることができます。
この章の一部のコード例は、式のみで構成されているため、有効なコードを構成していません。次の章では、ステートメントについて説明し、有効なコードの記述を開始できるようにします。
Types
式を評価することは、式を計算してその値を見つけることです。特定の式が評価する値は、環境とスタックレベルに依存する可能性があるため、コンテキストごとに異なる場合があります。この値は、数値、テキスト、その他の多くのデータ型のいずれかになる場合があります。そのため、型があると言われます。
Lua および一般的なプログラミングでは、式は通常、0個以上の演算子を持つ1つ以上の値で構成されます。一部の演算子は、一部のタイプでのみ使用できます(たとえば、テキストを分割しようとするのは非論理的ですが、数値を分割することは理にかなっています)。演算子には、単項演算子と二項演算子の2種類があります。単項演算子は、1つの値のみを取る演算子です。たとえば、単項演算子は、パラメータとして -5、-3、-6 などの1つの数値のみを取ります。パラメータとして1つの数値を取り、その数値を無効にします。ただし、同じ演算子ではない2項演算子は、2つの値を取り、最初の値から2番目の値を減算します:5-3、8-6、4-9など。
type
関数を使用して、数値の型を文字列として取得することができます。
print(type(32425)) --> number
Numbers
数字は一般的に数量を表しますが、他の多くのことに使用できます。Luaの数値型は、実数とほとんど同じように機能します。数値は、整数、10進数、10進数の指数、または16進数で構成できます。有効な番号は次のとおりです。
3
3.0
3.1416
314.16e-2
0.31416E1
0xff
0x56
Arithmetic operations
操作 | 構文 | 説明 | 例 |
---|---|---|---|
算術否定 | -a | aの符号を変更し、値を返します | -3.14159 |
添加 | a + b | aとbの合計を返します | 5.2 + 3.6 |
減算 | a-b | aからbを減算し、結果を返します | 6.7 - 1.2 |
乗算 | a * b | aとbの積を返します | 3.2 * 1.5 |
べき乗 | a ^ b | aをbの累乗、またはaのbによるべき乗に返します | 5 ^ 2 |
分割 | a / b | aをbで除算し、結果を返します | 6.4 / 2 |
モジュロ演算 | a % b | aをbで割った余りを返します | 5 % 3 |
最後のものを除いて、これらの演算子(基本的な数学演算子と同じ)はすべてすでに知っているでしょう。最後のものはモジュロ演算子と呼ばれ、ある数値を別の数値で除算した余りを単純に計算します。たとえば、5 % 3 は、式の意味するところは 5 を 3 で割った余りであるため、結果として 2 ということになります。モジュロ演算子は他の演算子ほど一般的ではありませんが、複数の用途があります。
Integers
数値の新しいサブタイプである整数がLua5.3で追加されました。数値は整数または浮動小数点数のいずれかです。浮動小数点数は上記の数値に似ていますが、整数は小数部のない数値です。浮動小数点の除算(/
)とべき乗は、常にオペランドを浮動小数点数に変換しますが、他のすべての演算子は、2つのオペランドが整数の場合、整数を返します。その他の場合、フロア分割演算子(//
)を除いて、結果はフロートになります。
Nil
Nilは値 nilのタイプであり、その主なプロパティは他の値とは異なります。これは通常、有用な値がないことを表します。nilを値に持つものの例:
- 値を割り当てる前にアクセスする変数の値
- スコープ外の変数にアクセスしようとしたときに取得する値
- 割り当てられていないテーブル内のキーの値
tonumber
によって文字列を数値に変換できない場合に返される値
より高度な注意点として、意図的にnil値を割り当てると、変数またはテーブルへの参照が削除され、ガベージコレクターがそのメモリを再利用できるようになります。
Booleans
ブール値は true または false のいずれかになりますが、それ以外はありません。これは、予約キーワードであるtrue
とfalse
として Lua で記述されています。注意すべき重要な点は、これは前述nil
のように異なるデータ型であるということです。and
、or
、not()
は通常ブール値と関連していますが、Luaの任意のデータ型で使用することができます。
操作 | 構文 | 説明 |
---|---|---|
ブール否定 | not a | aがfalseまたはnilの場合、trueを返します。それ以外の場合は、falseを返します。 |
論理積 | a and b | falseまたはnilの場合、最初の引数を返します。それ以外の場合は、2番目の引数を返します。 |
論理和 | a or b | falseでもnilでもない場合、最初の引数を返します。それ以外の場合は、2番目の引数を返します。 |
基本的に、not
演算子はブール値を否定するだけで(trueの場合はfalseにし、falseの場合はtrueにします)、and
演算子は両方がtrueの場合はtrueを返し、そうでない場合はfalseを返し、or
演算子はいずれかの引数がtrueの場合はtrueを返します。それ以外の場合はfalse。ただし、上記の表で正確な動作方法が説明されているため、これは正確には動作しません。Luaでは、論理式では値falseとnilの両方がfalseと見なされ、その他はすべてtrueと見なされます(0と空の文字列も含む)。
次の章で紹介する関係演算子(<
、>
、<=
、>=
、~=
、==
)は必ずしもオペランドとしてブール値を取ることはありませんが、常に結果としてブール値が得られます。
これを調整するのは難しい場合があります。わかりやすくするために、ここにいくつかの真理値表または式と結果のペアを示します。ここでx
はnil
、true
またはfalse
:
式 | 結果 |
---|---|
true and x |
x |
false and x |
false |
nil and x |
nil |
true or x |
true |
false or x |
x |
nil or x |
x |
これはやや直感に反することを意味します
式 | 結果 |
---|---|
false and nil |
false |
nil and false |
nil |
加えて
式 | 結果 |
---|---|
false == nil nil == false |
false |
nil and false |
nil |
式 | 結果 |
---|---|
not(false) not(nil) |
true |
not(true) |
false |
Strings
文字列は、テキストを表すために使用できる文字のシーケンスです。これらは、コメントに関するセクションで前に説明した二重引用符、一重引用符、または長い角かっこで囲むことにより、Lua で記述でき[ます。コメントと文字列には、コメントの場合は2つのハイフンを前に付けて、両方を長い角かっこで区切ることができるという事実以外に共通点がないことに注意してください)。長い括弧に含まれていない文字列は、1行だけ続きます。このため、長い角かっこを使用せずに多くの行を含む文字列を作成する唯一の方法は、エスケープシーケンスを使用することです。これは、特定の場合に一重引用符または二重引用符を挿入する唯一の方法でもあります。エスケープシーケンスは、Luaでは常にエスケープ文字であるバックスラッシュ( ' \ ' )と、エスケープする文字を識別する識別子の2つで構成されます。
Escape sequence | Description |
---|---|
\n | 改行 |
\" | 二重引用符(ダブルクォーツ) |
\' | 一重引用符(またはアポストロフィ) |
\ | バックスラッシュ |
\t | 水平tab |
### | 0から255 までの数字でなければなりません。結果は対応する ASCII文字 . |
文字を文字列に直接入れると問題が発生する場合は、エスケープシーケンスを使用します。たとえば、二重引用符で囲まれ、二重引用符を含める必要があるテキストの文字列がある場合は、文字列を別の文字で囲むか、二重引用符をエスケープする必要があります。長い角かっこで区切られた文字列内の文字をエスケープする必要はありません。これはすべての文字に当てはまります。長い括弧で区切られた文字列内のすべての文字は、そのまま使用されます。%
文字は、魔法の文字をエスケープする文字列パターンで使用されていますが、用語のエスケープは、別のコンテキストで使用されています。
"This is a valid string." 'This is also a valid string.' "This is a valid \" string 'that contains unescaped single quotes and escaped double quotes." [[ This is a line that can continue on more than one line. It can contain single quotes, double quotes and everything else (-- including comments). It ignores everything (including escape characters) except closing long brackets of the same level as the opening long bracket. ]] "This is a valid string that contains tabs \t, double quotes \" and backlashes \\" "This is " not a valid string because there is an unescaped double quote in the middle of it."
便宜上、開いている長い文字列ブラケットの直後に新しい行が続く場合、その新しい行は無視されます。したがって、次の2つの文字列は同等です。
[[This is a string that can continue on many lines.]] [[ This is a string that can continue on many lines.]] -- Since the opening long bracket of the second string is immediately followed by a new line, that new line is ignored.
単項長演算子 ( '#' )を使用すると、文字列の長さを数値として取得できます。
print(#("This is a string")) --> 16
Concatenation
形式言語理論とコンピュータプログラミング、文字列の連結は、 2つの文字列&usg=ALkJrhi_O9cTd4GHux39enA4M1-llG5B1w)エンドツーエンドをつなげる操作です。たとえば、「雪」と「ボール」の連結は「雪玉」です。
In formal language theory and computer programming, string concatenation is the operation of joining two character strings) end-to-end. For example, the concatenation of "snow" and "ball" is "snowball".
Luaの文字列連結演算子は、2つのドット ('..') で示されます。これは、「snow」と「ball」を連結して結果を出力する連結の例です。
print("snow" .. "ball") --> snowball
このコードは「snow」と「ball」を連結し、結果を出力します。
Other types
Lua の4つの基本タイプ(数値、ブール値、nil、文字列)については前のセクションで説明しましたが、関数、テーブル、ユーザーデータ、スレッドの4つのタイプがありません。関数は、呼び出して値を受け取り、値を返すことができるコードの断片です。テーブルは、データ操作に使用できるデータ構造です。ユーザーデータは、Luaが組み込まれているアプリケーションによって内部的に使用され、Lua がアプリケーションによって制御されるオブジェクトを介してそのプログラムと通信できるようにします。最後に、スレッドはコルーチンによって使用されます。これにより、多くの関数を同時に実行できます。これらはすべて後で説明するので、他のデータ型があることだけを覚えておいてください。
Literals
リテラルは、ソースコードで固定値を表すための表記法です。Lua ではスレッドとユーザーデータを除くすべての値は、リテラルとして表すことができます。文字列リテラル(文字列として評価されるリテラル)は、たとえば、テキストの文字列を一重引用符、二重引用符、または長い角かっこで囲んで構成します。一方、数値リテラルは、10進表記(例: 12.43
)、科学的記数法(例:3.1416e-2
および0.31416E1
)、または16進表記(例:0xff
)を使用して表現された数値で構成されます。
Coercion
Coercion とは、あるデータ型の値を別のデータ型の値に変換することです。Lua は、文字列値と数値の間の自動変換を提供します。文字列に適用される算術演算は、この文字列を数値に変換しようとします。逆に、文字列が予期され、代わりに数値が使用される場合は常に、数値は文字列に変換されます。これは、Lua演算子とデフォルト関数(言語で提供される関数)の両方に適用されます。
print("122" + 1) --> 123 print("The number is " .. 5 .. ".") --> The number is 5.
数値から文字列への Coercion および文字列から数値への Coercion は、tostring
関数と tonumber
関数を使用して手動で行うこともできます。前者は数値を引数として受け入れて文字列に変換し、後者は文字列を引数として受け入れて数値に変換します(デフォルトの10進数とは異なるベースをオプションで2番目の引数に指定できます)。
Bitwise operations
Lua 5.3以降、2進数(ビットパターン)を操作するためのビット演算子が提供されています。これらの演算子は他の演算子ほど頻繁には使用されないため、不要な場合はこのセクションをスキップできます。
Lua のビット演算子は常に整数を操作し、必要に応じてオペランドを変換します。それらは整数も与えます。
ビット単位のAND演算(演算子&
を使用)は、同じ長さの2つのバイナリ表現のビットの各ペアに対して論理積を実行します。たとえば、5 & 3
は 1 と評価されます。これは、これらの数値の2進表現を調べることで説明できます(下付き文字は基数を示すために使用されます)。
5と3の両方のバイナリ表現の特定の位置にあるビットが1である場合(最後のビットの場合のように)、その位置にあるビットは1になります。それ以外の場合はすべて0になります。
ビット単位のOR演算(演算子|
を使用)は、ビット単位のAND演算と同じように機能し、論理積を実行する代わりに論理和を実行します。したがって、5 | 3
は7と評価されます。
ここで、2つのオペランドのバイナリ表現がその位置に0ビットを持っていた場合にのみ、最終結果の各位置のビットが0であることがわかります。
ビット単位のXOR演算(演算子~
を使用)は他の2つの演算と同じように機能しますが、特定の位置では、オペランドのビットの両方ではなく片方が1の場合、最後のビットは1になります。
これは前の例と同じですが、両方のオペランドの最後のビットが1であったため、結果の最後のビットが1ではなく0であることがわかります。
ビット単位のNOT演算(演算子~
を使用)は、一意のオペランドの各ビットに対して論理否定を実行します。つまり、0が1になり、1が0になります。したがって、~7
は -8 と評価されます。
ここで、最初のビットはオペランドが0であるため結果で1になり、他のビットはすべて1であるため0になります。
これらのビット演算子に加えて、Lua5.3は算術ビットシフトもサポートしています。演算子<<
を使用して左側に示されている左シフトは、すべてのビットを、第2オペランドに対応するビット数だけ左にシフトすることで構成されます。演算子>>
で示され、右に示されている右シフトも同じですが、ビットシフトは反対方向です。
Operator precedence
演算子の優先順位は、Lua でも数学で通常行われるのと同じように機能します。特定の演算子は他の演算子よりも先に評価され、括弧を使用して、操作を実行する順序を任意に変更できます。演算子が評価される優先度は、優先度の高いものから低いものへと、以下のリストにあります。これらの演算子のいくつかはまだ議論されていませんが、それらはすべてこの本のある時点でカバーされます。
べき乗:
^
単項演算子:
not
、#
、-
、~
レベル2数学演算子:
*
、/
、//
、%
レベル1の数学演算子:
+
、-
連結:
..
ビットシフト:
<<
、>>
ビットごとのAND:
&
ビットごとのXOR:
~
ビットごとのOR:
|
関係演算子:
<
、>
、<=
、>=
、~=
、==
ブール値と:
and
ブール値または:
or
Quiz
この章の内容を理解したことを確認するために回答できる質問がいくつかあります。これらの質問のいくつかに対する答えを見つけるには、この章に記載されていない知識が必要になる場合があることに注意してください。これは正常です。クイズは学習体験の一部であり、本の他の場所では入手できない情報を紹介することができます。
1 何が出力されますか?print(type(type(5.2)))
2 0 or 8
この式は何を返しますか?
選択肢 | |
---|---|
true |
|
false |
|
0 |
|
8 |
3 どの文字列が有効ですか?
選択肢 | |
---|---|
"test's test" |
|
'test\'s test' |
|
"test"s test" |
|
'test"s test' |
|
"test\'s test" |
|
'test's test' |
4 どの式が"1223"
の文字列を与えますか?
選択肢 | |
---|---|
"122" + 3 |
|
"122" .. 3 |
|
"12" + "23" |
|
12 .. 23 |
5 正誤問題 not 5^3 == 5
true or false?
選択肢 | |
---|---|
true |
|
false |
Statements
ステートメントは、実行可能なコードの一部であり、ステートメントで使用する命令と式が含まれています。一部のステートメントには、たとえば特定の条件下で実行される可能性のあるコードも含まれます。式とは異なり、式に直接入れることができ、実行されます。Lua にはいくつかの命令がありますが、これらの命令を他の命令や複雑な式と組み合わせると、ユーザーに十分な制御と柔軟性が与えられます。
Assignment
プログラマーは、後で使用できるように、値をメモリーに格納できる必要があることがよくあります。これは変数を使用して行われます。変数は、コンピューターのメモリに格納されている値への参照です。それらは、メモリに保存した後、後で番号にアクセスするために使用できます。割り当ては、変数に値を割り当てるために使用される命令です。これは、値を格納する必要がある変数の名前、等号、および変数に格納する必要がある値で構成されます。
variable = 43 print(variable) --> 43
上記のコードで示されているように、変数の値にアクセスするには、変数の名前を値にアクセスする必要があります。
The assignment operator
Lua では、他のほとんどのプログラミング言語と同様に、等号(=
)は、右側のオペランドの式の値を左側のオペランドで指定された変数に割り当てる2項代入演算子として機能します
Assignment of variables
次の例は、変数の割り当てに等号を使用する方法を示しています。
fruit = "apple" -- assign a string to a variable count = 5 -- assign a numeric value to a variable
Strings and Numeric Values
リテラル文字列は、変数名と区別するために引用符で囲む必要があることに注意してください。
apples = 5 favourite = "apples" -- without quotes, apples would be interpreted as a variable name
変数名は数字で始めることができないため、数値を引用符で囲む必要はなく、変数名として誤って解釈することはできないことに注意してください。
apples = 6 -- no quotes are necessary around a numeric parameter pears = "5" -- quotes will cause the value to be considered a string
Multiple Assignments
Lua プログラミング言語は複数の割り当てをサポートしています。
apples, favorite = 5, "apples" -- assigns apples = 5, favorite = "apples"
Identifiers
Lua では、識別子は名前とも呼ばれます。文字、数字、アンダースコアで構成され、数字で始まらない任意のテキストを使用できます。これらは、テーブルに関する章で説明する変数とテーブルフィールドに名前を付けるために使用されます
いくつかの有効な名前は次のとおりです。
name
hello
_
_tomatoes
me41
__
_thisIs_StillaValid23name
いくつかの無効な名前は次のとおりです。
2hello
:数字で始まるth$i
:文字、数字、またはアンダースコアではない文字が含まれているhel!o
:文字、数字、またはアンダースコアではない文字が含まれている563text
:数字で始まる82_something
:数字で始まる
また、次のキーワードのLUAによって予約されており、名称として使用することはできません:and
、end
、in
、repeat
、break
、false
、local
、return
、do
、for
、nil
、then
、else
、function
、not
、true
、elseif
、if
、or
、until
、while
。
変数またはテーブルフィールドに名前を付けるときは、その有効な名前を選択する必要があります。したがって、文字またはアンダースコアで始まり、文字、アンダースコア、および数字のみを含める必要があります。Luaでは大文字と小文字が区別されることに注意してください。これは、Hello
とhello
が2つの異なる名前であることを意味します。
Scope
変数&usg=ALkJrhgPIty13LsluvhQl0GnViEF63s0ow)のスコープ&usg=ALkJrhgPIty13LsluvhQl0GnViEF63s0ow)は、その変数が意味を持つプログラムのコードの領域です。これまでに見た変数の例はすべてグローバル変数の例であり、プログラムのどこからでもアクセスできる変数です。一方、ローカル変数は、それらが定義されたプログラムの領域から、およびプログラムのその領域内にあるプログラムの領域でのみ使用できます。これらはグローバル変数とまったく同じ方法で作成されますが、接頭辞としてlocal
キーワードを付ける必要があります。
do
ステートメントは、それらを記述するために使用されます。このdo
ステートメントは、新しいコードブロック、つまり新しいスコープを作成する以外の目的がないステートメントです。end
キーワードで終わります:
local variable = 13 -- This defines a local variable that can be accessed from anywhere in the script since it was defined in the main region. do -- This statement creates a new block and also a new scope. variable = variable + 5 -- This adds 5 to the variable, which now equals 18. local variable = 17 -- This creates a variable with the same name as the previous variable, but this one is local to the scope created by the do statement. variable = variable - 1 -- This subtracts 1 from the local variable, which now equals 16. print(variable) --> 16 end print(variable) --> 18
スコープが終了すると、スコープ内のすべての変数が削除されます。コードの領域では、含まれているコードの領域で定義された変数を使用できますが、同じ名前のローカル変数を定義して変数を「上書き」すると、コードの他の領域で定義された変数の代わりにそのローカル変数が使用されます。これが、print関数の最初の呼び出しが16を出力し、do
ステートメントによって作成されたスコープ外の2番目の呼び出しが18を出力する理由です。
実際には、ローカル変数のみを使用する必要があります。ローカル変数は、グローバル変数のように現在の関数の環境に格納されるのではなく、レジスタに格納されるため、グローバル変数よりも高速に定義およびアクセスできるためです。レジスターは、Lua がローカル変数を格納してそれらにすばやくアクセスするために使用する領域であり、通常は最大200個のローカル変数しか含めることができません。すべてのコンピューターの重要なコンポーネントであるプロセッサーにもレジスターがありますが、これらは Lua のレジスターとは関係ありません。各関数(メインスレッド、プログラムのコア、関数でもある)にも独自の環境があります。これは、変数名にインデックスを使用し、これらの変数の値をこれらのインデックスに対応する値に格納するテーブルです。
Forms of assignment
Augmented assignment
複合代入とも呼ばれる拡張代入は、変数に前の値を基準にした値を与えるタイプの代入です。たとえば、現在の値をインクリメントする、aの値を8ずつインクリメントするコードa += 8
相当するものは、C&usg=ALkJrhiCKRK36t_KSDoPF3Pn4cHeTdDEkg)、JavaScript、Ruby&usg=ALkJrhjzUGQdMGYRxbbFJVnabYKtQcLMTw)、Python&usg=ALkJrhhE0iY1VFHE7kYCrOh5VrK0LUblFQ)に存在しますが、Luaには存在しません。つまり、a = a + 8
と記述する必要があります。
Chained assignment
連鎖割り当ては、多くの変数に単一の値を与える割り当ての一種です。たとえば、このコードa = b = c = d = 0
は、CおよびPythonでa、b、c、およびdの値を0に設定します。Luaでは、このコードはエラーを発生させるため、前の例は次のように記述する必要があります。
d = 0 c = d -- or c = 0 b = c -- or b = 0 a = b -- or a = 0
Parallel assignment
並列割り当ては、同時割り当ておよび複数割り当てとも呼ばれ、異なる変数に異なる値(同じ値にすることもできます)を同時に割り当てるタイプの割り当てです。連鎖割り当てや拡張割り当てとは異なり、Luaでは並列割り当てを使用できます。
前のセクションの例は、並列割り当てを使用するように書き直すことができます。
a, b, c, d = 0, 0, 0, 0
値よりも多くの変数を指定すると、一部の変数には値が割り当てられません。変数よりも多くの値を指定すると、余分な値は無視されます。より技術的には、値のリストは、割り当てが行われる前に変数のリストの長さに調整されます。つまり、余分な値が削除され、最後に余分なnil値が追加されて、のリストと同じ長さになります。変数値リストの最後に関数呼び出しがある場合、関数呼び出しが括弧で囲まれていない限り、返される値はそのリストの最後に追加されます。
さらに、ほとんどのプログラミング言語とは異なり、Luaはpermutation (順列)を介して変数の値の再割り当てを可能にします。例えば:
first_variable, second_variable = 54, 87 first_variable, second_variable = second_variable, first_variable print(first_variable, second_variable) --> 87 54
これが機能するのは、割り当てステートメントが何かを割り当てる前にすべての変数と値を評価するためです。割り当ては、実際に同時であるかのように実行されます。つまり、新しい値が割り当てられる前に、変数とその変数の値でインデックス付けされたテーブルフィールドに同時に値を割り当てることができます。つまり、次のコードは、dictionary[1]
を12に設定するのではなくdictionary[2]
に設定します。
dictionary = {} index = 2 index, dictionary[index] = index - 1, 12
Conditional statement
条件文は、式が真であるかどうかをチェックし、真である場合は特定のコードを実行する命令です。式が真でない場合は、そのコードをスキップするだけで、プログラムが続行されます。Luaでは、唯一の条件文がif
命令を使用します。Falseとnilは両方ともfalseと見なされ、その他はすべてtrueと見なされます。
local number = 6 if number < 10 then print("The number " .. number .. " is smaller than ten.") end -- Other code can be here and it will execute regardless of whether the code in the conditional statement executed.
上記のコードでは、変数番号には、代入ステートメントを使用して番号6が割り当てられています。次に、条件ステートメントは、変数番号に格納されている値が10より小さいかどうかをチェックします。これは、ここに当てはまります。そうである場合は、「"The number 6 is smaller than ten." 6は10より小さい」と出力されます。
else
キーワードを使用して式が真でない場合にのみ特定のコードを実行し、elseif
条件ステートメントをチェーン化することもできます。
local number = 15 if number < 10 then print("The number is smaller than ten.") elseif number < 100 then print("The number is bigger than or equal to ten, but smaller than one hundred.") elseif number ~= 1000 and number < 3000 then print("The number is bigger than or equal to one hundred, smaller than three thousands and is not exactly one thousand.") else print("The number is either 1000 or bigger than 2999.") end
else
ブロックは常に最後のものでなければならないことに注意してください。elseif
ブロックの後にelse
ブロックを置くことはできません。elseif
ブロックは、それらを先行ブロックのいずれも実行されなかった場合にのみ意味があります。
2つの値を比較するために使用される演算子は、上記のコードで使用されているものもあり、関係演算子と呼ばれます。関係がtrueの場合、ブール値true
を返します。それ以外の場合は、ブール値 false
を返します。
等しい | 等しくない | より大きい | 未満 | 以上 | 以下 | |
---|---|---|---|---|---|---|
数学表記 | = | ≠ | > | < | ≥ | ≤ |
Luaオペレーター | == | ~= | > | < | >= | <= |
equal to | not equal to | greater than | less than | greater than or equal to | less than or equal to | |
---|---|---|---|---|---|---|
Mathematical notation | = | ≠ | > | < | ≥ | ≤ |
Lua operator | == | ~= | > | < | >= | <= |
上記のコードは、and
キーワードを使用して、条件式で多くのブール式を組み合わせる方法も示しています。
Loops
多くの場合、プログラマーは特定のコードまたは同様のコードを何度も実行するか、ユーザー入力に応じて特定のコードを何度も実行する必要があります。ループは、1回指定されますが、連続して複数回実行される可能性のある一連のステートメントです。
Condition-controlled loops
条件制御ループは、条件によって制御されるループです。これらは条件ステートメントに非常に似ていますが、条件がtrueの場合にコードを実行し、それ以外の場合はスキップする代わりに、条件がtrueの間、または条件がfalseになるまでコードを実行し続けます。 Luaには、と条件制御ループの2つのステートメントがあります。while
ループと repeat
ループです。このようなループはコードを実行し、条件が真であるかどうかを確認します。 trueの場合、コードを再度実行し、条件がfalseになるまで繰り返します。条件がfalseの場合、コードの繰り返しを停止し、プログラムフローが続行されます。コードの各実行は、 iteration(反復)と呼ばれます。while
とrepeat
ループの違いは、repeat
ループはループの最後に条件をチェックすることです。while
ループは、ループの開始時にそれをチェックします。これは、最初の反復でのみ違いがあります。コードが最初に実行されたときに条件がfalseであっても、repeat
ループは常に少なくとも1回はコードを実行します。これは、条件が実際に true である場合にのみコードを最初に実行するwhile
ループには当てはまりません。
local number = 0 while number < 10 do print(number) number = number + 1 -- Increase the value of the number by one. end
上記のコードは、0、1、2、3のように、9まで出力します。10回目の反復後、数値は10以上になるため、ループの実行は停止します。ループは永久に実行されることを意味する場合があり、その場合、ループは無限ループと呼ばれます。たとえば、レンダラー、つまり画面上に物を描画するソフトウェアプロセスは、ユーザーに表示される画像を更新するために画面を再描画するために絶えずループすることがよくあります。これはビデオゲームでよくあることで、ユーザーに表示されるものを最新の状態に保つために、ゲームビューを常に更新する必要があります。ただし、ループを永久に実行する必要がある場合はまれであり、そのようなループはエラーの結果であることがよくあります。無限ループは多くのコンピュータリソースを消費する可能性がありますが、したがって、ユーザーから予期しない入力を受け取った場合でも、ループが常に終了するようにすることが重要です。
local number = 0 repeat print(number) number = number + 1 until number >= 10
上記のコードは、上記のwhile
ループを使用したコードとまったく同じことを行います。主な違いは`while
キーワードとdo
キーワードの間に条件が配置されるwhile
ループとは異なり、条件はループの最後、untilキーワードの後に配置されることです。repeat
ループは、end
キーワードによってブロックが閉じられていないLuaで唯一のステートメントです。
Count-controlled loops
変数をインクリメントすると、その値が段階的に、特に1ステップづつ増加します。前のセクションの2つのループは、変数の数字をインクリメントし、それを使用してコードを特定の回数実行しました。この種のループは非常に一般的であるため、Luaを含むほとんどの言語には組み込みのループ制御構造があります。この制御構造はカウント制御ループと呼ばれ、Luaおよびほとんどの言語では、for
ステートメントによって定義されます。このようなループで使用される変数は、ループカウンターと呼ばれます。
for number = 0, 9, 1 do print(number) end
上記のコードは、前のセクションで示した2つのループとまったく同じことを行いますが、数値変数はループのローカルであるため、ループ内からのみアクセスできます。変数名と等号記号に続く最初の数字は初期化です。ループカウンタが初期化される値です。 2番目の番号は、ループが停止する番号です。変数をインクリメントし、変数がこの数に達するまでコードを繰り返します。最後に、3番目の数値は増分です。これは、各反復でループカウンターが増加する値です。増分が指定されていない場合、Luaでは1と見なされます。したがって、以下のコードは1、1.1、1.2、1.3、1.4、1.5を出力します。
for n = 1, 2, 0.1 do print(n) if n >= 1.5 then break -- Terminate the loop instantly and do not repeat. end end
上記のコードが2までではなく、1.5までしか上がらない理由は、ループを即座に終了するbreak
ステートメントのためです。このステートメントは、while
ループやrepeat
ループを含む任意のループで使用できます。ここでは>=
演算子が使用されていることに注意してください。ただし、理論的には==
演算子も同様に機能します。これは、10進数の精度エラーが原因です。Luaは、倍精度浮動小数点形式で数値を表します、実際の値の近似値をメモリに格納します。場合によっては、近似の値が数値と正確に一致しますが、場合によっては、概算にすぎません。通常、これらの近似値は、違いが生じないように数値に十分に近いものになりますが、このシステムでは、等式演算子==
を使用するとエラーが発生する可能性があります。これが、等式演算子の使用を避けて10進数で作業するのが一般的に安全である理由です。この特定のケースでは、等式演算子が使用されていた場合、コードは機能しませんでした[ 1](1.9まで上昇し続けました)が、>=
演算子では機能します。
Blocks
ブロックは、順番に実行されるステートメントのリストです。これらのステートメントには、命令を含まない空のステートメントを含めることができます。空のステートメントを使用して、セミコロンでブロックを開始したり、2つのセミコロンを順番に書き込んだりできます。
関数の呼び出しと割り当ては括弧で始まる場合があり、あいまいさを招く可能性があります。このフラグメントの例です:
a = b + c (print or io.write)('done')
このコードは、次の2つの方法で解釈できます。
a = b + c(print or io.write)('done') a = b + c; (print or io.write)('done')
現在のパーサーは、常に最初の方法でそのような構文を認識し、最初の括弧を呼び出しの引数の開始として解釈します。このあいまいさを回避するために、次のように常に括弧で始まるセミコロンステートメントを前に付けることをお勧めします。
;(print or io.write)('done')
Chunks
Luaのコンパイル単位はチャンクと呼ばれます。チャンクは、ホストプログラム内のファイルまたは文字列に格納できます。チャンクを実行するために、Luaは最初にチャンクを仮想マシンの命令にプリコンパイルし、次にコンパイルされたコードを仮想マシンのインタープリターで実行します。チャンクは、Luaに付属のコンパイルプログラムluac
、または指定された関数のバイナリ表現を含む文字列を返すstring.dump
関数を使用して、バイナリ形式(バイトコード)にプリコンパイルすることもできます。
このload
関数を使用して、チャンクをロードできます。load
関数に指定された最初のパラメーターが文字列の場合、チャンクはその文字列です。この場合、文字列はLuaコードまたはLuaバイトコードのいずれかです。最初のパラメーターが関数の場合、load
チャンクを取得するためにその関数を繰り返し呼び出します。各チャンクは、前の文字列と連結される文字列です。次に、何も返されないか、空の文字列が返されたときに、チャンクが完了したと見なされます。
load
関数は構文エラーがない場合は関数としてコンパイルされたチャンクを返します。それ以外の場合は、nil
とエラーメッセージが返されます。
load
関数の2番目のパラメーターは、チャンクのソースを設定するために使用されます。すべてのチャンクは、適切なエラーメッセージとデバッグ情報を提供できるように、ソースのコピーをチャンク内に保持します。デフォルトでは、それらのソースのコピーはload
に与えられたコードになります(コードが与えられた場合、代わりに関数が与えられた場合、それは「=(load)」になります)。このパラメーターを使用して変更できます。これは、元のソースを取り戻せないようにコードをコンパイルするときに最も役立ちます。次に、バイナリ表現に含まれているソースを削除する必要があります。そうしないと、元のコードを取得できるためです。
load
関数の3番目のパラメーターは、生成された関数の環境を設定するために使用でき、4番目のパラメーターは、チャンクをテキストにするかバイナリにするかを制御します。文字列「b」(バイナリチャンクのみ)、「t」(テキストチャンクのみ)、または「bt」(バイナリとテキストの両方)の場合があります。デフォルトは「bt」です。
load
とまったく同じように機能するloadfile
関数もありますが、この関数はファイルからコードを取得するものです。 最初のパラメーターは、コードを取得するファイルの名前です。 バイナリ表現に格納されているソースを変更するパラメータはありません。load
関数の3番目と4番目のパラメータは、この関数の2番目と3番目のパラメータに対応しています。 loadfile
関数を使用して、標準入力からコードをロードすることもできます。これは、ファイル名が指定されていない場合に実行されます。
このdofile
関数はloadfile
関数に似ていますが、コードを関数としてファイルにロードする代わりに、ソースコードファイルに含まれているコードをLuaチャンクとしてすぐに実行します。その唯一のパラメーターは、コンテンツを実行するファイルの名前を指定するために使用されます。引数が指定されていない場合は、標準入力の内容が実行されます。チャンクが値を返す場合、それらはdofile
関数の呼び出しによって提供されます。dofile
はプロテクトモードでは実行されないため、dofile
で実行されたチャンク内のすべてのエラーが伝播します。
Functions
スタックとそのスタックで実行できる操作の図。
スタックは、後入れ先出しの原則に従って動作する、アイテムを追加(プッシュ)または削除(ポップ)できるアイテムのリストです。つまり、最後に追加されたアイテムが最初に削除されます。このようなリストがスタックと呼ばれるのはこのためです。スタックでは、最初にその上にあるアイテムを削除せずにアイテムを削除することはできません。したがって、すべての操作はスタックの最上位(Top)で行われます。アイテムは、他のアイテムの後に追加された場合は上にあり、他のアイテムの前に追加された場合は下にあります。
関数(サブルーチン、プロシージャ、ルーチン、またはサブプログラムとも呼ばれます)は、特定のタスクを実行する一連の命令であり、その一連の命令を実行する必要があるときはいつでも、プログラムの他の場所から呼び出すことができます。関数は、入力として値を受け取り、入力を操作したり、入力に基づいてタスクを実行したりした後、出力を返すこともできます。関数は、他の関数内を含め、プログラム内のどこからでも定義できます。また、関数にアクセスできるプログラムの任意の部分から呼び出すこともできます。関数は、数値や文字列と同様に値であるため、変数に格納できます。変数に共通するすべてのプロパティがあります。これらの特性により、関数はとてもつかいやすくなります。
関数は他の関数から呼び出すことができるため、Luaインタープリター(Luaコードを読み取って実行するプログラム)は、関数が終了したとき(それ以上実行するコードがないとき)に、現在実行している関数と呼ばれる関数を認識できる必要があります。それによって正しい関数の実行に戻ることができます。これは、コールスタックと呼ばれるスタックを使用して行われます。コールスタック内の各アイテムは、現在実行されている関数で、スタック内のすぐ上にあるアイテムがなくなるまで呼び出す関数です。関数が終了すると、インタープリターはスタックのポップ操作を使用してリスト内の最後の関数を削除し、その後、前の関数に戻ります。
関数には、組み込み関数とユーザー定義関数の2種類があります。組み込み関数はLuaで提供される関数であり、すでに知っているprint
関数などの関数が含まれています。print関数のように直接アクセスできるものもあれば、乱数を返すmath.random
関数のようにライブラリを介してアクセスする必要があるものもあります。ユーザー定義関数は、ユーザーが定義した関数です。ユーザー定義関数は、関数コンストラクターを使用して定義されます。
local func = function(first_parameter, second_parameter, third_parameter) -- function body (a function's body is the code it contains) end
上記のコードは、3つのパラメーターを持つ関数を作成し、それを変数funcに格納します。次のコードは上記のコードとまったく同じですが、関数を定義するためにシンタックスシュガーを使用しています。
local function func(first_parameter, second_parameter, third_parameter) -- function body end
なお、第2の形式を使用する場合は、内部から関数を参照することができますが、第1の形式を使用する場合は参照できません。これは、local function foo() end
が local foo = function() end
ではなくlocal foo; foo = function() end
に変換されるためです。これは、fooが1番目の形式ではなく2番目の形式の関数の環境の一部であることを意味します。これは、なぜ2番目の形式が関数自体を参照できるかを説明しています。
どちらの場合も、local
キーワードを省略して関数をグローバル変数に格納することができます。パラメータは変数のように機能し、関数が値を受け取ることを可能にします。関数が呼び出されると、引数が与えられます。その後、関数はそれらをパラメーターとして受け取ります。パラメータは、関数の先頭で定義されたローカル変数のようなものであり、関数呼び出しで指定された引数の順序に応じて順番に割り当てられます。引数が欠落している場合、パラメーターの値はnil
になります。次の例の関数は、2つの数値を加算し、結果を出力します。したがって、コードの実行時に5が出力されます。
local function add(first_number, second_number) print(first_number + second_number) end add(2, 3)
関数呼び出しは、ほとんどの場合、フォームname(arguments)
の下にあります。ただし、引数が1つだけで、それがテーブルまたは文字列であり、変数に含まれていない場合(つまり、関数呼び出しで直接作成され、リテラルとして表される場合)、括弧は省略できます。
print "Hello, world!" print {4, 5}
前の例のコードの2行目は、テーブルのメモリアドレスを出力します。print
関数が自動的に値を文字列に変換すると、複合型(関数、テーブル、ユーザーデータ、スレッド)がそれらのメモリアドレスに変更されます。ただし、ブール値、数値、および nil 値は、対応する文字列に変換されます。
パラメータと引数という用語は、同じ意味で使われることがよくあります。この本では、適切な意味で、パラメータと引数という用語は、それぞれ、パラメータとして割り当てられて関数に渡される値と対応する引数として値が割り当てられるものの名前を意味します。
Returning values
関数は入力を受け取り、それを操作し、出力を返すことができます。入力(パラメーター)を受け取り、それを操作する方法(関数本体)は既に知っています。また、returnステートメントを使用して、任意のタイプの1つまたは複数の値を返すことによって出力することもできます。これが、関数呼び出しがステートメントと式の両方である理由です。それらは実行できますが、評価することもできます。
local function add(first_number, second_number) return first_number + second_number end print(add(5, 6))
上記の関数のコードは、最初にadd
関数を定義しています。次に、5と6を値として呼び出します。add
関数はそれらを加算して結果を返し、出力されます。これが、上記のコードが11を出力する理由です。これらの値に評価される式をコンマで区切ることにより、関数が多くの値を返すことも可能です。
Errors
エラーには、構文エラー、静的セマンティックエラー、セマンティックエラーの3種類があります。構文エラーは、コードが明らかに無効な場合に発生します。たとえば、次のコードはLuaによって無効として検出されます。
print(5 ++ 4 return)
上記のコードは意味がありません。それから意味を引き出すことは不可能です。同様に、英語では、「cat(猫) dog(犬) tree(木)」は意味がないため、構文的に有効ではありません。文を作成するための規則に従っていません。
静的セマンティックエラーが起こるのはコードは意味を持っているが、まだ成立しない場合です。例えば、文字列に対して数字を足そうとすると静的セマンティックエラーになります。 これは文字列に数字を加算できないためです。
print("hello" + 5)
上記のコードはLuaの構文規則には従いますが、数値を含む文字列を追加することは不可能であるため、意味が成立しません(文字列が数値を表す場合を除き、その場合は強制的に1つになります)。これは英語で「I are big」という文と比較することができます。英語で文章を作成するための規則に収まっていますが、「I」は単数形で「are」は複数形であるため、それでもやはりおかしいわけです。
最後に、セマンティックエラーは、コードの一部の意味がその作成者が考えているものではない場合に発生するエラーです。これらは見つけるのが非常に難しいため、最悪のエラーです。 Luaは、構文エラーまたは静的セマンティックエラー(これはエラーのスローと呼ばれます)がある場合は常に通知しますが、セマンティックエラーがある場合は、どのような考えでコードが意味づけされていたかわからないため、通知できません。これらのエラーは、思っているよりも頻繁に発生し、エラーを見つけて修正することに、多くのプログラマーがたくさんの時間を費やしています。
エラーを見つけて修正するプロセスは、デバッグと呼ばれます。ほとんどの場合、プログラマーは実際にエラーを修正するよりもエラーを見つけることに多くの時間を費やします。これは、すべてのタイプのエラーに当てはまります。問題が何であるかがわかれば、通常は簡単に修正できますが、プログラマーがコードのどこに問題があるのかを見つけられずに、何時間もコードを見直す場合もあります。
Protected calls
エラーをスローすることは、インタープリター(コードを読み取って実行するプログラム)によって手動で行われたか自動で行われたかにかかわらず、コードに問題があることを示すアクションです。指定されたコードが無効な場合、Luaによって自動的に実行されますが、error
関数を使用して手動で実行できます。
local variable = 500 if variable % 5 ~= 0 then error("It must be possible to divide the value of the variable by 5 without obtaining a decimal number.") end
error
関数には、エラーがスローされるスタックレベルを示す2番目の引数もありますが、これについては本書では取り上げません。このassert
関数はerror
関数と同じことを行いますが、最初の引数が nil または false と評価され、エラーをスローするスタックレベルを指定するために使用できる引数がない場合にのみエラーをスローします。assert
関数は、スクリプトの開始時に役立ちます。たとえば、スクリプトが機能するために必要なライブラリが使用可能かどうかを確認する場合などです
エラーがスローされるたびにプログラム内のコードの実行が停止するため、なぜ自発的にエラーをスローしたいのか理解するのは難しいかもしれませんが、多くの場合、関数が正しく使用されていないときやプログラムが適切な環境で実行されていないときにエラーをスローし、コードをデバッグしなければならない人が、何が悪いのか気付かずにコードを長時間見つめることなく、速く間違いを見つけるのに役立ちます。
エラーがコードを停止するのを防ぎ、代わりにユーザーにエラーメッセージを表示して、開発者にバグを報告できるようにすることが役立つ場合があります。これは例外処理(またはエラー処理)と呼ばれ、エラーをキャッチして伝播を防ぎ、例外ハンドラーを実行して処理することで実行されます。さまざまなプログラミング言語でこの方法は大きく異なります。Luaでは、プロテクトされた呼び出しを使用して行われます[1]。プロテクトモードで呼び出された関数は、エラーが発生してもプログラムを停止しないため、これらはプロテクト呼び出しと呼ばれます。プロテクトモードで関数を呼び出すために使用できる関数は2つあります。
関数 | 説明 |
---|---|
pcall(function, ...) |
プロテクトモードで関数を呼び出し、ステータスコード(エラーがスローされたかどうかによって値が異なるブール値)と関数によって返される値、または関数がエラーによって停止された場合はエラーメッセージを返します。引数は、プロテクトモードで呼び出す必要のある関数である最初の引数の後にpcall 関数に渡すことで関数に指定できます。 |
xpcall(function, handler, ...) |
pcallと同じことを行いますが、関数エラーが発生すると、pcallが返すのと同じ値を返す代わりに、それらをパラメーターとしてハンドラー関数を呼び出します。次に、ハンドラ関数を使用して、たとえば、エラーメッセージを表示できます。pcall 関数に関しては、xpcall 関数に対して引数として与えることで関数に引数を渡すことができます。 |
Stack overflow
呼び出しスタック、つまり呼び出された順序で呼び出されたすべての関数を含むスタックについては、前述しました。Luaを含むほとんどの言語でのその呼び出しスタックには、最大サイズがあります。この最大サイズは非常に大きいため、ほとんどの場合心配する必要はありませんが、自分自身を呼び出す関数(これは再帰性と呼ばれ、このような関数は再帰関数と呼ばれます)は、自分自身を呼び出すことを妨げるものがない場合、この制限に達するまで無期限に再帰を続ける可能性があります。これはスタックオーバーフローと呼ばれます。スタックがオーバーフローすると、コードの実行が停止し、エラーがスローされます。
Variadic functions
可変引数関数は、vararg関数とも呼ばれ、可変数の引数を受け入れる関数です。可変個引数関数は、パラメーターリストの最後にある3つのドット( "...")で示されます。パラメータリストのパラメータに収まらない引数は、破棄されるのではなく、vararg式を介して関数で使用できるようになります。これも3つのドットで示されます。 vararg式の値は、値のリスト(テーブルではない)であり、次の式を使用して、より簡単に操作できるようにテーブルに配置できます。
{...}
Lua 5.0では、vararg式を介して使用できる代わりに、「arg」という特別なパラメーターで追加の引数を使用できました。次の関数は、受け取ったすべての引数に最初の引数を追加し、次にそれらすべてを合計して結果を出力する関数の例です。
function add_one(increment, ...) local result = 0 for _, number in next, {...} do result = result + number + increment end end
上記のコードは可変個引数関数のデモンストレーションにすぎないため、理解する必要はありません。
このselect
関数は、テーブルを使用せずに引数リストを操作するのに役立ちます。引数の数が不定であるため、それ自体がvariadic関数です。最初の引数として指定された番号を使用して、引数の後のすべての引数を返します(指定された番号が負の場合、最後からインデックスを付けます。つまり、-1が最後の引数です)。また、最初の引数が文字列 "#"の場合、最初の引数を除いて、受け取った引数の数も返します。引数リスト内の特定の数より前のすべての引数を破棄すると、より元々は、引数として送信されるnil値と、引数として送信されない値を区別するのに役立ちます。#
は最初の引数として、値が無いことから nil 値が指定されるとselect
は区別します。引数リスト(および戻りリストも)はタプル(tuples:いくつかの部分からなるデータの構造)のインスタンスであり、テーブルに関する章で説明します。このselect
関数はすべてのタプルで機能します。
print((function(...) return select('#', ...) == 1 and "nil" or "no value" end)()) --> no value print((function(...) return select('#', ...) == 1 and "nil" or "no value" end)(nil)) --> nil print((function(...) return select('#', ...) == 1 and "nil" or "no value" end)(variable_that_is_not_defined)) --> nil -- As this code shows, the function is able to detect whether the value nil was passed as an argument or whether there was simply no value passed. -- In normal circumstances, both are considered as nil, and this is the only way to distinguish them.
- ↑詳細については、Ierusalimschy、Roberto(2003)を参照してください。 "Error Handling in Application Code". Programming in Lua (first ed.). Lua.org. ISBN 8590379817. http://www.lua.org/pil/24.3.1.html. Retrieved June 20, 2014.
- ↑ For more information, see: Ierusalimschy, Roberto (2003). "Error Handling in Application Code". Programming in Lua (first ed.). Lua.org. ISBN 8590379817. http://www.lua.org/pil/24.3.1.html. Retrieved June 20, 2014.
Standard libraries
Luaは「バッテリーが付属していない」と言われている言語です。これは、そのライブラリがいくつかのことを行うために必要な最小限に保たれていることを意味します。Luaはコミュニティに依存して、より具体的なタスクを実行するために使用できるライブラリを作成しています。Luaには10のライブラリがあります。Luaのリファレンスマニュアルは、すべてのライブラリのドキュメントを提供しています[1]。ここで簡単に説明されています[2]。基本ライブラリとパッケージライブラリを除くすべてのライブラリは、テーブルのフィールドとして関数と値を提供します。
Basic library
基本ライブラリはLuaにコア機能を提供します。そのすべての関数と値はグローバル環境で直接利用可能であり、デフォルトでグローバル環境で直接利用可能なすべての関数と値は基本ライブラリの一部です。
Assertion
アサーションは、開発者が true であると想定する述語です。これらは、プログラムの実行の特定の瞬間に特定の条件が真であることを保証するためにプログラムで使用されます。アサーションは、プログラムが正しく機能することを確認するためのunit testsで使用されますが、プログラムコードでも使用されます。この場合、アサーションが false の場合、プログラムが正しい環境を確認するため、またはプログラムコードでエラーが発生していないことを確認し、適切なエラーメッセージを生成して、予期したとおりに何かが発生しなかったときにコードのバグを見つけやすくします。 Luaでは、条件とメッセージ(デフォルトでは「アサーションに失敗しました!」)をパラメーターとして受け入れるassert
関数を使用してアサーションが作成されます。条件が false と評価された場合、assert
はメッセージとともにエラーをスローします。true と評価された場合は、assert
はすべての引数を返します。
Garbage collection
ガベージコレクションは、Lua や他の多くの言語で実装されている自動メモリ管理の一形態です。プログラムがデータを変数に格納する必要がある場合、プログラムはオペレーティングシステムに、変数の値を格納するためにコンピュータのメモリにスペースを割り当てるように要求します。次に、スペースが不要になると(通常、変数がスコープから外れたため)、別のプログラムがスペースを使用できるように、スペースの割り当てを解除するようにオペレーティングシステムに指示します。 Lua では、実際のプロセスははるかに複雑ですが、基本的な考え方は同じです。プログラムは、変数の値が不要になったときにオペレーティングシステムに通知する必要があります。低水準言語では、割り当ては言語によって処理されますが、割り当て解除は、プログラマーが値を必要としなくなったときに言語が認識できないためではありません。値を参照する変数がスコープから外れたり削除されたりした場合でも、スクリプト内の別の変数またはフィールドがそれを参照している可能性があり、割り当てを解除すると問題が発生します。高水準言語では、割り当て解除は、Luaが使用するシステムであるガベージコレクションなど、さまざまな自動メモリ管理システムによって処理される場合があります。ガベージコレクターは、Lua によって割り当てられたすべての値を定期的に検索して、どこにも参照されていない値を探します。プログラムがアクセスできなくなった値を収集し(参照がないため)、これらの値を安全に割り当て解除できることがわかっているため、割り当てを解除します。これはすべて透過的かつ自動的に行われるため、プログラマーは通常、それについて何もする必要はありません。しかし、時々、開発者は、ガベージコレクターに指示を与えることができます。
Weak references
弱参照は、ガベージコレクターによって無視される参照です。これらの参照は、開発者が mode
メタメソッドを使用してガベージコレクターに示します。テーブルの mode
メタメソッドは文字列である必要があります。その文字列に文字「k」が含まれている場合、テーブルのフィールドのすべてのキーは弱く、文字「v」が含まれている場合、テーブルのフィールドのすべての値は弱くなります。オブジェクトの配列に弱い値がある場合、ガベージコレクターは、その配列および他の弱い参照でのみ参照されている限り、その配列で参照されている場合でもこれらのオブジェクトを収集します。この動作は便利な場合もありますが、ほとんど使用されません。
Manipulating the garbage collector
ガベージコレクターは、基本ライブラリの一部であり、ガベージコレクターへのインターフェイスとして機能する collectgarbage
関数で操作できます。その最初の引数は、実行するアクションをガベージコレクターに示す文字列です。2番目の引数は、一部のアクションで使用されます。この collectgarbage
関数を使用して、ガベージコレクターを停止し、手動で収集サイクルを実行し、Luaが使用するメモリをカウントできます。
Coroutines
Coroutinesは、サブルーチンを一般化して、特定の場所で実行を一時停止および再開するための複数のエントリポイントを許可するコンピュータプログラムコンポーネントです。コルーチンは、協調タスク、例外、イベントループ、イテレータ、無限リスト、パイプ&usg=ALkJrhhdyPCiI1O0UXvWU1Q1dAkZUaEmTg)など、より使い慣れたプログラムコンポーネントの実装に最適です。
Coroutines are computer program components that generalize subroutines to allow multiple entry points for suspending and resuming execution at certain locations. Coroutines are well-suited for implementing more familiar program components such as cooperative tasks, exceptions, event loop, iterators, infinite lists and pipes).
コルーチンは、Luaのコルーチンライブラリを使用して作成および操作できるコンポーネントであり、コルーチンを内部から生成する関数またはコルーチンを外部から再開する関数を呼び出すことにより、特定の場所で関数の実行を生成および再開できるようにします 。
例:
メインスレッドの関数は、
coroutine.create
関数からコルーチンを作成し、coroutine.resume
で再開します。コルーチンには、番号3が渡されます。コルーチンの関数が実行され、引数として
coroutine.resume
に渡された数値を取得します。関数は、実行の特定の時点に到達し
coroutine.yield
、引数として、受け取った引数の合計(3)と2(したがって、3 + 2 = 5)を呼び出します。coroutine.resume
への呼び出しはcoroutine.yield
に渡されたため、5を返し、メインスレッドは現在再び実行されており、その数値を変数に格納します。 コードを実行した後、コルーチンを再開し、coroutine.resume
の呼び出しから受け取った値の2倍をcoroutine.resume
に渡します(つまり、5×2 = 10を渡します)。コルーチンは、
coroutine.yield
の呼び出しの結果として、coroutine.resume
に渡された値を取得し、さらにコードを実行した後に終了します。 これは、coroutine.yield
の呼び出しの結果と、最初にパラメーターとして指定された値(つまり、10-3 = 7)との差を返します。メインスレッドは、
coroutine.resume
の呼び出しの結果としてコルーチンによって返される値を取得し、続行します。
この例をコードに入れると、次のようになります。
local co = coroutine.create(function(initial_value) local value_obtained = coroutine.yield(initial_value + 2) -- 3+2=5 return value_obtained - initial_value -- 10-3=7 end) local _, initial_result = coroutine.resume(co, 3) -- initial_result: 5 local _, final_result = coroutine.resume(co, initial_result * 2) -- 5*2=10 print(final_result) --> 7
coroutine.create
関数は、関数からコルーチンを作成します。 コルーチンは「スレッド」タイプの値です。 coroutine.resume
は、コルーチンの実行を開始または継続します。 コルーチンは、エラーが発生した場合、または実行するものが何も残っていない場合(この場合、実行が終了した場合)にデッドと呼ばれます。 コルーチンがデッドの場合、再開することはできません。 coroutine.resume
関数は、コルーチンの実行が成功した場合はtrue
を返し、コルーチンが終了した場合は返されたすべての値とともに、終了しなかった場合は coroutine.yield
に渡されます。 実行が成功しなかった場合は、エラーメッセージとともに「false」が返されます。 coroutine.resume
は実行中のコルーチンを返し、そのコルーチンがメインスレッドの場合はtrue
を返し、それ以外の場合は false
を返します。
このcoroutine.status
関数は、コルーチンのステータスを文字列として返します。
- コルーチンが実行中の場合は
running
。つまり、coroutine.status
を呼び出したコルーチンである必要があります。 - コルーチンがyieldの呼び出しで一時停止されている場合、またはコルーチンがまだ実行を開始していない場合は、
suspended
- コルーチンがアクティブであるが実行されていない場合は
normal
、つまり別のコルーチンを再開したことを意味します - コルーチンの実行が終了した場合、またはエラーが発生した場合は
dead
coroutine.wrap
関数は、呼び出されるたびにコルーチンを再開する関数を返します。 この関数に指定された追加の引数は、 coroutine.resume
への追加の引数として機能し、コルーチンによって返される値、またはcoroutine.yield
に渡される値は、関数の呼び出しによって返されます。 coroutine.wrap
関数は、coroutine.resume
とは異なり、プロテクトモードでコルーチンを呼び出さず、エラーを伝播します。
コルーチンには多くのユースケースがありますが、それらを説明することはこの本の範囲外です。
String matching
文字列を操作する場合、特定のパターンに従う部分文字列を文字列で検索できると便利なことがよくあります。 Luaには、これを行うための関数と、関数が文字列で検索できるパターンを表現するための表記法を提供する文字列操作ライブラリがあります。 Luaが提供する表記法は、プログラミングの世界のほとんどの言語やツールで使用されるパターンを表現するための正規表現と非常によく似ています。ただし、それはより制限されており、構文がわずかに異なります。
文字列ライブラリのfind
関数は、文字列内のパターンの最初の一致を探します。文字列内でパターンが見つかった場合、そのパターンが開始および終了する文字列内のインデックス(最初の文字から始まる文字列内の文字の位置を表す整数)を返します。パターンの出現が見つからない場合は、何も返しません。受け入れる最初のパラメーターは文字列で、2番目はパターン、3番目はfind
関数が検索を開始する文字位置を示す整数です。最後に、4番目の引数としてtrue
値を指定することにより、パターンなしで単純な一致を実行するようにfind
関数に指示できます。次に、最初の文字列で指定された2番目の文字列の出現を検索します。単純一致を実行する場合は、3番目の引数を指定する必要があります。このサンプルコードは、文中の「lazy」という単語を検索し、その単語で見つかった出現箇所の開始位置と終了位置を出力します。
local start_position, end_position = string.find("The quick brown fox jumps over the lazy dog.", "lazy", 1, true) print("The word \"lazy\" was found starting at position " .. start_position .. " and ending at position " .. end_position .. ".")
このコードでは「lazy」という単語は、位置36で始まり、位置39で終わることがわかりました。これは次と同等です。
local sentence = "The quick brown fox jumps over the lazy dog." local start_position, end_position = sentence:find("lazy", 1, true) print("The word \"lazy\" was found starting at position " .. start_position .. " and ending at position " .. end_position .. ".")
これは、文字列の index
メタメソッドが文字列ライブラリの関数を含むテーブルに設定され、 string.a(b, ...)
をb:a(...)
に置き換えることができるために機能します 。
文字の位置を示すインデックスを受け入れる、またはそのようなインデックスを返す文字列ライブラリ内の関数は、最初の文字が位置1にあると見なします。最後の文字が位置-1で、文字列の末尾から逆方向にインデックスを付けます。
パターンは、文字列が一致するかどうかを示す特定の表記法に従う文字列です。この目的のために、パターンには文字クラス、つまり文字のセットを表す組み合わせが含まれています。
文字の組み合わせ | 説明 |
---|---|
. | すべての文字 |
%a | 文字(大文字と小文字) |
%c | 制御文字 |
%d | 数字 |
%g | 印刷可能な文字(スペース文字を除く) |
%l | 小文字 |
%p | 句読文字 |
%s | スペース文字 |
%u | 大文字 |
%w | 英数字(数字と文字) |
%x | 16進数 |
特殊ではないすべての文字はそれ自体を表し、特殊文字(英数字ではないすべての文字)は、パーセント記号を接頭辞として付けることでエスケープできます。 キャラクタークラスを組み合わせて、セットに入れることで、より大きなキャラクタークラスを作成できます。 セットは、角括弧で囲まれた文字クラスとして示されます(つまり、 [%xp]
は、すべての16進文字と文字「p」のセットです)。 文字の範囲は、範囲の終了文字をハイフンで昇順で区切ることで確認できます(つまり、 [0-9%s]
は0から9までのすべての数字とスペース文字を表します)。キャレット( ^
)文字がセットの先頭(開始角括弧の直後)に配置されている場合、セットには、キャレットがセットの先頭に配置されていなかった場合に含まれていた文字を除くすべての文字が含まれます。
パーセント記号 %
の前にある文字で表されるすべてのクラスの補数は、パーセント記号の後に対応する大文字が続くものとして示されます(つまり、 %S
はスペース文字を除くすべての文字を表します)。
パターンは、文字列がそれに一致するためにパターン内でどのシーケンスを見つける必要があるかを表すパターンアイテムのシーケンスです。 パターンアイテムは、文字クラスにすることができます。この場合、クラス内の任意の文字に1回一致し、文字クラスの後に「*
」文字が続きます。この場合、クラス内の文字の0回以上の繰り返しに一致します(これら 繰り返し項目は常に可能な限り長いシーケンスに一致します)、文字クラスの後に追加( 「+
」)文字が続きます。この場合、クラス内の文字の1つ以上の繰り返しに一致します(これらの繰り返し項目も常に可能な限り長いシーケンスに一致します)、文字クラスの後にマイナス( 「-
」)文字が続きます。この場合、クラス内の文字の0回以上の繰り返しに一致しますが、最も短いシーケンスまたは文字クラスとそれに続く疑問符に一致します。この場合、クラス内の文字の1つまたは出現なしに一致します。
以前にキャプチャされた部分文字列と同等の部分文字列を一致させることができます。%1
は最初にキャプチャされた部分文字列と一致し、 %2
は2番目の部分文字列と一致し、以下同様に%9
まで続きます。 キャプチャについては、以下で説明します。 リファレンスマニュアルで説明されているように、パターンによって提供される他の2つの機能があります。
%bxy
、ここでxとyは2つの異なる文字です。 このような項目は、xで始まり、yで終わり、xとyのバランスが取れている文字列に一致します。 つまり、文字列を左から右に読み、xの場合は + 1、yの場合は-1を数えると、最後のyは、数が0に達する最初のyになります。たとえば、項目%b()
は バランスの取れた括弧で式を照合します。
%f[set]
、フロンティアパターン。 このような項目は、次の文字がセットに属し、前の文字がセットに属さないように、任意の位置で空の文字列と一致します。 セットセットは、前述のように解釈されます。 件名の最初と最後は、文字「 '\0'」であるかのように処理されます。—Lua authors, Lua 5.2 Reference Manual
パターンはパターン項目のシーケンスであり、オプションでキャレット^
が前に付き、パターンが文字列の先頭でのみ一致できることを示し、オプションでドル記号$
が続きます。これは、パターンが文字列の最後でのみ一致できることを示します 。 これらの記号は、文字列の最初または最後に一致を固定すると言われています。 これらの2つの文字は、パターンの最初または最後にある場合にのみ特別な意味を持ちます。
サブパターンは、キャプチャを示すためにパターン内の括弧で囲むことができます。一致が成功すると、キャプチャに一致する文字列の部分文字列は、将来使用するために保存されます。たとえば、gmatch
によって返されます。常に左括弧の位置から番号が付けられます。 2つの空の括弧は、現在の文字列位置(数値であり、文字列の一部ではありません)をキャプチャする空のキャプチャを示します。
このgmatch
関数を使用して、文字列内のパターンの出現を反復処理できます。find
関数とは異なり、検索を開始する初期位置を指定したり、単純なマッチングを実行したりすることはできません。このgmatch
関数は、呼び出されると、文字列内の指定されたパターンから次のキャプチャを返すイテレータを返します。パターンにキャプチャが指定されていない場合は、代わりに一致全体が示されます。次の例は、文中の単語を反復処理して1つずつプリントする方法を示しています。
local sentence = "The quick brown fox jumps over the lazy dog." for word in sentence:gmatch('%a+') do print(word) end
この例では、一致全体は、イテレータであるwordによって返される唯一の値によって与えられます。
gsub
関数を使用して、文字列内のパターンのすべての出現箇所を別のものに置き換えることができます。 最初の2つの引数は文字列とパターンで、3番目はオカレンスを置き換える文字列で、4番目は置き換える必要のあるオカレンスの最大数です。 3番目の引数は、文字列ではなく、テーブルまたは関数にすることもできます。
3番目の引数が文字列の場合、それは置換文字列と呼ばれ、文字列内のパターンの出現を置換します。 パターンによって保存されたキャプチャは、置換文字列に埋め込むことができます。 それらは、キャプチャの数を表す数字が続くパーセント記号によって示されます。 一致自体は %0
で表すことができます。 置換文字列のパーセント記号は、 %%
としてエスケープする必要があります。
3番目の引数がテーブルの場合、最初のキャプチャはそのテーブルにインデックスを付けるためのキーとして使用され、置換文字列はテーブル内のそのキーに対応する値です。 関数の場合、その関数は一致するたびに呼び出され、すべてのキャプチャが引数として渡されます。 どちらの場合も、キャプチャがない場合は、代わりに一致全体が使用されます。 関数またはテーブルの値が false
またはnil
の場合、置換は行われません。
Lua 5.2リファレンスマニュアルから直接引用したいくつかの例を次に示します。
```lua x = string.gsub("hello world", "(%w+)", "%1 %1") --> x="hello hello world world"
x = string.gsub("hello world", "%w+", "%0 %0", 1) --> x="hello hello world"
x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1") --> x="world hello Lua from"
x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv) --> x="home = /home/roberto, user = roberto"
x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s) return load(s)() end) --> x="4+5 = 9"
local t = {name="lua", version="5.2"} x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t) --> x="lua-5.2.tar.gz" ```
—Lua authors, Lua 5.2 Reference Manual
Lua は、パターンマッチング以外の文字列操作機能を提供します。これらには、文字の順序を逆にして文字列を返す reverse
関数、文字列に相当する小文字を返すlower
関数、文字列に相当する大文字を返す upper
関数が含まれます。文字列の長さを返す len
関数と、引数として指定された2つの文字位置で開始および終了する文字列の部分文字列を返すsub
関数。その他についてはLua 5.2リファレンスマニュアルに記載があります。
- ↑ Ierusalimschy, Roberto; Celes, Waldemar; Henrique de Figueiredo, Luiz. Lua 5.2 Reference Manual. http://www.lua.org/manual/5.2. Retrieved 30 November 2013.
- ↑ Functions that were already described elsewhere will not be described in this chapter.
Appendix:Software testing
ソフトウェアテストという用語は、コンピュータソフトウェアのバグやプログラミングの間違いを発見するために使用されるいくつかの方法とプロセスを指します。 ソフトウェアテストは静的に実行できます。静的テストと呼ばれ、コンピュータソフトウェアを実行せずに実行されます。動的な場合は、動的テストと呼ばれ、テスト対象のコンピュータプログラムの実行中に実行されます。
Type checking
プログラミング言語では、type systemは、type と呼ばれるプロパティを、コンピュータープログラムで構成されるさまざまな構成要素(変数、式、関数、モジュールなど)に割り当てるルールの集まりです。 型システムの主な目的は、コンピュータープログラムのさまざまな部分の間のインターフェイスを定義し、それらの部分が一貫した方法で接続されていることを確認することによって、コンピュータープログラムのバグを減らすことです。このチェックは、静的(コンパイル時)、動的(実行時)、または静的チェックと動的チェックの組み合わせとして実行できます。 型システムには、特定のコンパイラの最適化の有効化、複数のディスパッチの許可、ドキュメントの形式の提供など、他の目的もあります。
ウィキペディアからの抜粋が示すように、型チェックは実行時またはコンパイル時に実行できます。 コンパイル時に実行される場合、コンパイラはソースコードをコンパイルするときに、プログラムの型安全性を検証し、プログラムが特定の型安全性プロパティを満たしていることを保証します。通常、静的型チェッカーは、変数の値が常に 同じ型であり、関数に渡される引数は正しい型になります。
静的なアプローチにより、開発サイクルの早い段階でバグを発見できます。対照的に、動的アプローチは、プログラムの実行時にプログラムが型制約に従っていることを確認することで構成されます。 これは、動的型チェッカーがより多くの制約を検証できる必要があることを意味しますが、ほとんどの動的型付き言語には多くの型制約がありません。 Luaは動的に型付けされた言語です。Luaでは、値には型がありますが、変数にはありません。 つまり、変数の値は、プログラムの実行のある時点では数値になり、別の時点では文字列になる可能性があります。
Luaの型システムは、他のほとんどの言語と比較して非常に単純です。 演算子が使用されている場合(たとえば、少なくとも1つが数値ではなく、1に強制できない2つの値を追加しようとすると、型エラーが発生します)、および標準ライブラリの関数が呼び出された場合(関数 標準ライブラリのは、正しい型を持たない引数を拒否し、適切なエラーを発生させます)。
Luaには関数パラメーターの型を指定する機能がないため、type
関数は、関数に渡される引数が適切な型であることを確認するのに役立ちます。これは、プログラムの実行中にユーザーから提供された引数が渡される関数(たとえば、事前定義されたLua関数を呼び出すための対話型環境)で最も役立ちます。関数に型チェックのコードを追加すると、関数がより冗長になり、メンテナンスのオーバーヘッドが増えるためです。
White-box testing
ホワイトボックステストという用語は、ソフトウェアの内部動作に関する知識を使用して、その機能を検証するためのテストケースを作成する方法を指します。これはソフトウェアテストの3つのレベルに関連していますが、Luaプログラムは通常、統合とシステムテストが行われるより大きなアプリケーションの一部であるため、Luaプログラムにとって最も興味深いのはユニットレベルです。
Luaでのユニットテストに利用できるフレームワークは複数あります。ユニットレベルでのテストは、通常、関数に特定の引数を渡し、関数が予期しない値を返したときに警告を提供するテストケースを作成することで構成されるため、ライブラリに最も適しています。これには、新しい機能のテストケースを作成する必要がありますが、テストに合格しなくなるような方法で関数の動作を変更したときに、コードに導入されたエラーに気づきやすくなるという利点があります。
Luaには複数のユニットテストフレームワークがあります。 そのうちの1つは、バストされ、標準のLua仮想マシンと LuaJIT をサポートし、MoonScript と Terra でも使用できます。前者はLuaにコンパイルされる言語で、後者は Lua と相互運用可能な低レベル言語です。 Lua の別のユニットテストフレームワークである Luaunit は、完全にLuaで記述されており、依存関係はありません。 Shake は、当初は Kepler Project の一部であった、より単純なテストフレームワークであり、assert
および print
関数を使用しますが、現在は積極的に開発されていません。
Further reading
Lua に関する情報を見つけるための優れたリソースである lua-users wiki は、ソフトウェアテストに関連する資料を提供します。これらのページの一部は、他のページやまたはさまざまなタスクに役立つプロジェクトへのリンクで構成されています。
Glossary
これは、Lua のコンテキストでのプログラミングに関連する用語を含む用語集です。理解できない単語の意味を見つけるために、その使用をお勧めします。
abstract class
抽象クラスは、インスタンスを直接作成できないクラスです。抽象クラスはabstract typesです。
abstract data type
抽象データ型は、データ構造の同様の動作をするクラスを表すモデルです。 抽象データ型は、実装やコンピューターのメモリへのデータの格納方法ではなく、それらに対して実行できる操作と、これらの操作の数学的制約によって定義されます。
An abstract data type is a model to represent a class of data structures that have similar behavior. Abstract data types are defined by the operations that can be performed on them and by mathematical constraints of these operations rather than by the implementation and the way the data is stored in the memory of the computer.
abstract type
An abstract type is a type of data of which instances cannot be created directly.
actual parameter
See argument.
additive inverse
The additive inverse of a number is the number that, when added to that number, yields zero. For example, the additive inverse of 42 is -42.
arithmetic negation
Arithmetic negation is the operation that produces the additive inverse of a number.
arithmetic operation
An arithmetic operation is an operation whose operands are numbers.
arity
The arity of an operation or of a function is the number of operands or arguments the operation or function accepts.
argument
array
An array is a data structure consisting of a collection of values, each identified by at least one array index or key.
associative array
An associative array is an abstract data type composed of a collection of pairs of keys and values, such that each possible key appears at most once in the collection.
augmented assignment
Augmented assignment is a type of assignment that gives a variable a value that is relative to its prior value.
binary operation
A binary operation is an operation of which the arity is two.
boolean
See logical data.
boolean negation
See logical negation.
chained assignment
Chained assignment is a type of assignment that gives a single value to many variables. Example:
a = b = c = d = 0
.chunk
A chunk is a sequence of statements.
compound assignment
See augmented assignment.
concatenation
String concatenation is the operation of joining two strings of characters. For example, the concatenation of "snow" and "ball" is "snowball".
concrete class
A concrete class is a class of which instances can be created directly. Concrete classes are concrete types.
concrete type
A concrete type is a type of which instances can be created directly.
condition
A condition is a predicate that is used in a conditional statement or as an operand to the conditional operator. Conditions, in Lua, are considered as true when their expression evaluates to a value other than
nil
orfalse
, and are considered as false otherwise.conditional operator
A conditional operator is an operator that returns a value if a condition is true and another if it isn't.
conditional statement
A conditional statement) is a statement that executes a piece of code if a condition is true.
data structure
A data structure is a particular way of storing and organizing data in the memory of a computer. It is the implementation of an abstract data type.
data type
A data type is a model for representing the storage of data in the memory of a computer.
dictionary
See associative array.
exclusive disjunction
Venn diagram of {\displaystyle \scriptstyle a\veebar b}The exclusive disjunction operation is a binary operation that produces the value
true
when one of its operands is true but the other is not. The exclusive disjunction of a and b is expressed mathematically as {\displaystyle \scriptstyle a\veebar b}. There is no operator corresponding to exclusive disjunction in Lua, but {\displaystyle \scriptstyle a\veebar b} can be represented as(a or b) and not (a and b)
.formal parameter
See parameter.
function
A function is a sequence of statements (instructions) that perform a specific task. Functions can be used in a program wherever that particular task needs to be performed. Functions are usually defined in the program that will use them, but are sometimes defined in libraries that can be used by other programs.
hash map
See hash table.
hash table
A hash table is an implementation as a data structure of the associative array. A hash table uses a hash function to compute an index into an array of buckets or slots, from which the value corresponding to the index can be found.
inline if
See conditional operator.
integer
An integer is a number that can be written without a fractional or decimal component. Integers are implemented in Lua in the same way as other numbers.
length operation
The length operation is the operation that produces the number of values in an array.
literal
A literal is a notation for representing a fixed value in source code. All values can be represented as literals in Lua except threads and userdata.
logical complement
The logical complement of a boolean value is the boolean value that is not that value. This means the logical complement of
true
isfalse
and vice versa.logical conjunction
Venn diagram of {\displaystyle \scriptstyle a\land b}The logical conjunction operation is a binary operation that produces the value
true
when both of its operands are true andfalse
in all other cases. It is implemented as theand
operator in Lua and it returns its first operand if it isfalse
ornil
and the second operand otherwise. The logical conjunction of a and b is expressed mathematically as {\displaystyle \scriptstyle a\land b}.logical data
The logical data type, which is generally called the boolean type, is the type of the values
false
andtrue
.logical disjunction
Venn diagram of {\displaystyle \scriptstyle a\lor b}The logical disjunction operation is a binary operation that produces the value
false
when both of its operands are false andtrue
in all other cases. It is implemented as theor
operator in Lua and it returns the first operand if it is neitherfalse
nornil
and the second otherwise. The logical disjunction of a and b is expressed mathematically as {\displaystyle \scriptstyle a\lor b}.logical negation
Logical negation, implemented in Lua by the
not
operator, is the operation that produces the logical complement of a boolean value.map
See associative array.
method
A method) is a function that is a member of an object and generally operates on that object.
modulo
See modulo operation.
modulo operation
The modulo operation, implemented in Lua by the
%
operator, is the operation that produces the remainder of the division of a number by another.modulus
See modulo operation.
multiple assignment
See parallel assignment.
-
The type nil is the type of the value
nil
, whose main property is to be different from any other value; it usually represents the absence of a useful value. not operator
See logical negation.
number
The number type represents real (double-precision floating-point) numbers. It is possible to build Lua interpreters that use other internal representations for numbers, such as single-precision float or long integers.
operator
An operator) is a token that generates a value from one or many operands.
parallel assignment
Parallel assignment is a type of assignment that simultaneously assigns values to different variables.
parameter
A parameter) is a variable in a function definition to which the argument that corresponds to it in a call to that function is assigned.
predicate
A predicate is an expression that evaluates to a piece of logical data.
procedure
See function.
relational operator
A relational operator is an operator that is used to compare values.
routine
See function.
sign change
See arithmetic negation.
simultaneous assignment
See parallel assignment.
string
The type string represents arrays of characters. Lua is 8-bit clean: strings can contain any 8-bit character, including embedded zeros.
string literal
A string literal is the representation of a string value within the source code of a computer program. With respect to syntax, a string literal is an expression that evaluates to a string.
subprogram
See function.
subroutine
See function.
symbol
See token.
symbol table
A symbol table is an implementation as a data structure of the associative array. They are commonly implemented as hash tables.
token
A token is an atomic piece of data, such as a word in a human language or such as a keyword in a programming language, for which a meaning may be inferred during parsing.
variable
A variable) is a label associated to a location in the memory. The data in that location can be changed and the variable will point to the new data.
variadic function
A variadic function is a function of indefinite arity.
Minetest Modding Book
この本についてのこと
これは Minetest というゲーム(とゲームエンジン)でのゲームのつくり方というか、ゲームの構造を学ぶための全くのビギナー向けの本の日本語訳です。 Minetest とは簡単に言うと、ある程度までのスペックのコンピューター資源で問題なく動作するマインクラフトのようななにかです。このなにかについて、作り始めた人物へのインタビューがありますので、興味があればどうぞ読んでみてください。
この本自体は Lua 言語の知識を要しますが、プログラムについての知識が無くても学習できるように丁寧にアドバイスしていると思います。 Lua 言語について補足的に学習するための参考としては、こちらをご覧ください。
Front Cover
© 2014-20 | Helpful? Consider donating to support rubenwardy.
Minetest Modding Book
by rubenwardy
with editing by Shara
Introduction
Minetest は Lua スクリプトを使用して modding サポートを提供します。この本は、基本から始めて、独自の mods を作成する方法を教えることを目的としています。各章は API の特定の部分に焦点を当てており、すぐに独自の mods を作成できるようになります。
サイトからこの本を読むこともできますし、htmlファイルをダウンロードして読むこともできます。
Feedback and Contributions
間違いに気づきましたか、それともフィードバックを送りたいですか?ぜひ連絡してください。
- GitLabのIsuueを作成します。
- フォーラムトピックに投稿します。
- 私に連絡してください
- コントリビュートしたいですか?READMEをお読みください。
1 - Getting Started
Introduction
mods を作るためには mod のフォルダーの基本構造を理解する必要があります。
What are Games and Mods?
Minetest の力は、独自のボクセルグラフィックス、ボクセルアルゴリズム、または派手なネットワークコードを作成しなくても、ゲームを簡単に開発できることです。
Minetest では、ゲームは、ゲームのコンテンツと動作を提供するために連携して機能するモジュールのコレクションです。一般に mod として知られているモジュールは、スクリプトとリソースのコレクションです。 1 つの mod だけを使用してゲームを作成することは可能ですが、ゲームの一部を他の部分とは独立して調整および交換するのが容易でないため、これが行われることはめったにありません。
ゲームの外に Mod を配布することも可能です。その場合、それらはより伝統的な意味での MOD 、つまり Mod です。これらの mod は、ゲームの機能を調整または拡張します。
ゲームに含まれる mod とサードパーティの mod はどちらも同じ API を使用します。
この本の内容は Minetest API の主要部分をカバーし、ゲーム開発者と modders に適用できます。
Where are mods stored?
各 mod には、Lua コード、テクスチャ、モデル、およびサウンドが配置される独自のディレクトリがあります。 Minetest は、さまざまなロケーションを mod の存在をチェックします。これらの場所は、一般に mod load paths と呼ばれます。
特定のワールド/セーブゲームについて、 3 つの mod の場所が順番にチェックされます。:
- Game mods これらは、world が実行しているゲームを形成する mods です。例:
minetest/games/minetest_game/mods/
、/usr/share/minetest/games/minetest/
- Global mods 、ほとんどの場合 mods がインストールされる場所。疑わしい場合は、ここに配置してください。例:
minetest/mods/
- World mods 、特定のワールドに固有の mods を保存する場所。例:
minetest/worlds/world/worldmods/
Minetest は、上記の順序で場所を確認します。以前に見つかったものと同じ名前の mod が見つかった場合、前の mod の代わりに後の mod がロードされます。これは、global mod のロケーションに同じ名前の mod を配置することで、game mod をオーバーライド(上書き)できることを意味します。
各 mod ロードパスの実際の場所は、使用しているオペレーティングシステムと、Minetest のインストール方法によって異なります。
- Windows:
- ポータブルビルドの場合、例:あなたが .zip ファイルから、抽出したディレクトリに移動します。
games
、mods
およびworlds
ディレクトリを調べてください。 - インストールされているビルド、つまり setup.exe からの場合、
C:\\Minetest
またはC:\\Games\Minetest
を調べます。 - GNU / Linux:
- システム全体のインストールについては、
~/.minetest
を調べてください。これ~
は、ユーザーのホームディレクトリを意味し、ドット(.
)で始まるファイルとディレクトリは非表示になっていることに注意してください。 - ポータブルインストールの場合は、ビルドディレクトリを確認してください。
- Flatpakのインストールについては、
~/.var/app/net.minetest.Minetest/.minetest/mods/
をご覧ください 。 - mac OS
-
~/Library/Application Support/minetest/
を見てください。~
は、ユーザーのホームを意味することに注意してください。すなわち:/Users/USERNAME/
Mod Directory
mod name は mod を参照するために使用されます。各 mod には一意の(他に同名が存在しない)名前を付ける必要があります。 mod 名には、文字、数字、およびアンダースコアを含めることができます。 mod の名前から、 mod の機能を理解できることが好ましく、 mod のコンポーネントを含むディレクトリは、 mod 名と同じ名前である必要があります。 mod 名が使用可能かどうかを確認するには、content.minetest.netで検索してみてください。
mymod ├── init.lua (required) - Runs when the game loads. ├── mod.conf (recommended) - Contains description and dependencies. ├── textures (optional) │ └── ... any textures or images ├── sounds (optional) │ └── ... any sounds └── ... any other files or directories
ゲームのロード時に実行するには、mod で init.lua ファイルのみが必要です。
ただし、 mod.conf が推奨されています。また、 mod の機能によっては、他のコンポーネントが必要になる場合があります。
Dependencies
依存関係(dependencies)は、ロードしようとする mod がそれ自体の前に別の mod をロードする必要がある場合に発生します。 1 つの mod で、別の mod のコード、アイテム、またはその他のリソースを使用できるようにする必要がある場合があるのです。
依存関係には、 2 種類があります。ハード依存関係とオプション依存関係です。どちらも、最初に mod をロードする必要があります。依存している mod が利用できない場合、ハード依存関係があると mod のロードに失敗しますが、オプション依存関係の場合は、有効になる機能が少なくなる可能性があります。
オプション依存関係は、オプションで別の mod をサポートする場合に役立ちます。ユーザーが両方の mod を同時に使用したい場合は、追加のコンテンツを有効にすることができます。
依存関係は mod.conf にリストされている必要があります。
mod.conf
このファイルは、mod の名前、説明、その他の情報を含む mod メタデータに使用されます。例:
name = mymod description = Adds foo, bar, and bo. depends = modone, modtwo optional_depends = modthree
depends.txt
Minetest の 0.4.x バージョンとの互換性のために、mod.conf で依存関係を指定するだけでなく、すべての依存関係をリストする depends.txt ファイルを提供する必要があります。
modone modtwo modthree?
各 mod 名は 1 行に 1 つづつあり、名前の後に疑問符が付いた mod 名はオプション依存関係です。
Mod Packs
mod は Mod Packs にグループ化でき、複数の mod をパッケージ化して一緒に移動できます。プレーヤーに複数の Mod を提供したいが、それぞれを個別にダウンロードさせたくない場合に便利です。
modpack1 ├── modpack.lua (required) - signals that this is a mod pack ├── mod1 │ └── ... mod files └── mymod (optional) └── ... mod files
modpack は geme ではないことに注意してください。game には独自の組織構造があり、これについてはゲームの章で説明します。
Example
これらすべてをまとめた例を次に示します。
Mod Folder
mymod ├── textures │ └── mymod_node.png files ├── depends.txt ├── init.lua └── mod.conf
depends.txt
default
init.lua
print("This file will be run at load time!") minetest.register_node("mymod:node", { description = "This is a node", tiles = {"mymod_node.png"}, groups = {cracky = 1} })
mod.conf
name = mymod descriptions = Adds a node depends = default
この mod の名前は「 mymod 」です。 init.lua 、 mod.conf 、 depends.txt の 3 つのテキストファイルがあります。 スクリプトはメッセージを出力してからノードを登録します。これについては次の章で説明します。 単一の依存関係、 default mod があります。これは通常、 Minetest Game にあります。 また、textures/ にはノードのテクスチャがあります。
2 - Lua Scripting
Introduction
この章では、Lua でのスクリプト作成、必要なツールについて説明し、おそらく役立つと思われるいくつかのテクニックについて説明します。
Code Editors
Lua でスクリプトを作成するには、コードのハイライト表示を備えたコードエディターで十分です。コードのハイライト表示は、意味に応じて、単語や文字にさまざまな色を付けて表示させます。これにより、間違いを見つけることができます。
function ctf.post(team,msg) if not ctf.team(team) then return false end if not ctf.team(team).log then ctf.team(team).log = {} end table.insert(ctf.team(team).log,1,msg) ctf.save() return true end
たとえば、上記のスニペットのキーワードは、 if 、then 、end 、return などでハイライト表示されています。 table.insert は、デフォルトで Lua に付属している関数です。 これは、 Lua に適した一般的なエディターのリストです。もちろん、他のエディターも利用できます。
Coding in Lua
Program Flow
プログラムは、次々に実行される一連のコマンドです。これらのコマンドを「ステートメント」と呼びます。プログラムフローは、これらのステートメントが実行される方法です。さまざまなタイプのフローを使用すると、コマンドのセットをスキップまたはジャンプできます。フローには主に 3 つのタイプがあります。
- シーケンス (Sequence):スキップせずに、ステートメントを次々に実行するだけです。
- 選択 (Selection):条件に応じてシーケンスをスキップします。
- 反復 (Iteration):繰り返し、ループします。条件が満たされるまで、同じステートメントを実行し続けます。
local a = 2 -- Set 'a' to 2 local b = 2 -- Set 'b' to 2 local result = a + b -- Set 'result' to a + b, which is 4 a = a + 10 print("Sum is "..result)
おっと、そこで何が起こりましたか?
a、b、および result は 変数 です。ローカル変数は、local キーワードを使用して宣言され、初期値が与えられます。ローカルは スコープ(scope) と呼ばれる非常に重要な概念の一部であるため、少し説明します。
=
は アサイン 、ですから result = a + b
は「 result 」 に 「 a + b 」を割り当てます。「 result 」変数で見られるように、変数名は数学とは異なり、1文字より長くなる可能性があります。 Lua では大文字と小文字が区別されることにも注意してください。 A は a とは異なる変数です。
Variable Types
変数は次のタイプのいずれかのみであり、割り当て後にタイプを変更できます。変数が nil または nil 以外の単一の型のみであることを確認することをお勧めします。
タイプ | 説明 | 例 |
---|---|---|
Nil | 初期化されていません。変数は空で、値がありません | local A 、 D = nil |
Number | 整数または10進数。 | local A = 4 |
String | テキストの一部 | local D = "one two three" |
Boolean | True or False | local is_true = false 、 local E = (1 == 1) |
Table | Lists | 以下に説明します |
Function | 実行できます。入力が必要な場合があり、値を返す場合があります | local result = func(1, 2, 3) |
Arithmetic Operators
網羅的なリストではありません。考えられるすべての演算子が含まれているわけではありません。
シンボル | 目的 | 例 |
---|---|---|
A + B | 加算 | 2 + 2 = 4 |
A-B | 減算 | 2-10 = -8 |
A * B | 乗算 | 2 * 2 = 4 |
A / B | 分割 | 100/50 = 2 |
A ^ B | 累乗 | 2 ^ 2 = 22 = 4 |
A .. B | 文字列を結合する | "foo".."bar" = "foobar" |
Selection
最も基本的な選択は if ステートメントです。次のようになります。
local random_number = math.random(1, 100) -- Between 1 and 100. if random_number > 50 then print("Woohoo!") else print("No!") end
この例では、1から100までの乱数が生成されます。次に、「 Woohoo! 」と出力されます。その数が50より大きい場合は、「 No! 」と出力されます。
local random_number = math.random(1, 100) -- Between 1 and 100. if random_number > 50 then print("Woohoo!") else print("No!") end
「>」から他に何が離れますか?
Logical Operators
シンボル | 目的 | 例 |
---|---|---|
A == B | 等しい | 1 == 1(true)、1 == 2(false) |
A 〜 = B | 等しくない | 1〜 = 1(false)、1〜 = 2(true) |
A > B | 大なり記号 | 5> 2(true)、1> 2(false)、1> 1(false) |
A < B | 未満 | 1 <3(true)、3 <1(false)、1 <1(false) |
A >= B | 以上かイコール | 5> = 5(true)、5> = 3(true)、5> = 6(false) |
A <= B | 以下かイコール | 3 <= 6(true)、3 <= 3(true) |
A and B | And(両方ともtrueでなければなりません) | (2> 1)and(1 == 1)(true)、(2> 3)and(1 == 1)(false) |
A or B | または。一方または両方がtureでなければなりません。 | (2> 1)or(1 == 2)(true)、(2> 4)or(1 == 3)(false) |
not A | not true | not(1 == 2)(true)、not(1 == 1)(false) |
これにはすべての可能な演算子が含まれているわけではなく、次のように演算子を組み合わせることができます。
if not A and B then print("Yay!") end
A が false で B が true の場合「 Yay! 」をプリントします 。
論理演算子と算術演算子はまったく同じように機能します。どちらも入力を受け入れ、保存可能な値を返します。
local A = 5 local is_equal = (A == 5) if is_equal then print("Is equal!") end
Programming
プログラミングとは、項目のリストを並べ替えるなどの問題を解決し、それをコンピューターが理解できる手順に変換するアクションです。
プログラミングの論理的なプロセスを教えることは、この本の範囲を超えています。ただし、次の Web サイトは、これを開発するのに非常に役立ちます。
- Codecademyは、「コーディング」を学ぶための最良のリソースの1つであり、インタラクティブなチュートリアル体験を提供します。
- Scratchは、プログラミングに必要な問題解決手法を学び、絶対的な基礎から始めるときに優れたリソースです。 Scratch は、子供たちにプログラミング方法を教えるように設計されており、本格的なプログラミング言語ではありません。
Local and Global Scope
変数がローカルであるかグローバルであるかによって、変数の書き込み、または読み取りが可能な場所が決まります。ローカル変数には、変数が定義されている場所からのみアクセスできます。ここではいくつかの例を示します。
-- Accessible from within this script file local one = 1 function myfunc() -- Accessible from within this function local two = one + one if two == one then -- Accessible from within this if statement local three = one + two end end
一方、グローバル変数には、スクリプトファイルのどこからでも、他の mod からもアクセスできます。
my_global_variable = "blah" function one() my_global_variable = "three" end print(my_global_variable) -- Output: "blah" one() print(my_global_variable) -- Output: "three"
Locals should be used as much as possible
Lua はデフォルトでグローバルです(他のほとんどのプログラミング言語とは異なります)。 ローカル変数は、そのように識別する必要があります(つまりローカル変数を使う場合は明示的に宣言します)。
function one() foo = "bar" end function two() print(dump(foo)) -- Output: "bar" end one() two()
dump() は、任意の変数を文字列に変換できる関数であり、プログラマーはそれが何であるかを確認できます。foo 変数は、文字列であることを示す引用符を含めて、「 bar 」として出力されます。
これはずさんなコーディングであり、Minetest は実際にこれについて警告します:
Assignment to undeclared global 'foo' inside function at init.lua:2
これを修正するには、「ローカル」を使用します。
function one() local foo = "bar" end function two() print(dump(foo)) -- Output: nil end one() two()
nil
は初期化されていないことを意味することに注意してください。変数に値がまだ割り当てられていないか、存在しないか、初期化されていません(つまり、 nil
に設定されています)。
関数についても同じことが言えます。関数は特別なタイプの変数であり、他の mod が同じ名前の関数を持つ可能性があるため、ローカルにする必要があります。
local function foo(bar) return bar * 2 end
API テーブルを使用して、次のように他の mod が関数を呼び出せるようにする必要があります。
mymod = {} function mymod.foo(bar) return "foo" .. bar end -- In another mod, or script: mymod.foo("foobar")
Including other Lua Scripts
mod に他のLuaスクリプトを含めるための推奨される方法は、dofile を使用することです。
dofile(minetest.get_modpath("modname") .. "/script.lua")
スクリプトは値を返すことができます。これは、プライベートローカル変数を共有するのに役立ちます。
-- script.lua return "Hello world!" -- init.lua local ret = dofile(minetest.get_modpath("modname") .. "/script.lua") print(ret) -- Hello world!
後の章では、mod のコードを分割する方法について詳しく説明します。ここではシンプルなアプローチとして、 nodes.lua 、crafts.lua 、craftitems.lua などのさまざまなタイプのものに対して別々のファイルを用意します。
3 - Nodes, Items, and Crafting
Introduction
新しいノードとクラフトアイテムの登録、およびクラフトレシピの作成は、多くの mod の基本的要件です。
- What are Nodes and Items?
- Registering Items
- Registering a basic node
- Actions and Callbacks
- Crafting
- Groups
- Tools, Capabilities, and Dig Types
What are Nodes and Items?
ノード、クラフトアイテム、ツールはすべてアイテムです。アイテムは、通常のゲームプレイでは不可能な場合でも、インベントリ内で見つけることができるものです。
ノードは、世界に配置されているか、または発見することができるアイテムです。世界のすべての位置空間は、たった1つのノードで占められている必要があります-一見空白の位置空間は通常、エアノードです。
クラフトアイテムは配置できず、インベントリにあるか、ワールド内でドロップされたアイテムとしてのみ発見できます。
ツールには着用する機能があり、通常はデフォルトではない掘削する機能があります。将来的には、クラフトアイテムとツールの区別がかなり人工的なものであるため、クラフトアイテムとツールが1つのタイプのアイテムに統合される可能性があります。
Registering Items
アイテム定義は、アイテム名と定義テーブルで構成されます。定義テーブルには、アイテムの動作に影響を与える属性が含まれています。
minetest.register_craftitem("modname:itemname", { description = "My Special Item", inventory_image = "modname_itemname.png" })
Item Names and Aliases
すべてのアイテムには、参照するために使用されるアイテム名があります。次の形式である必要があります。
modname:itemname
modname は、アイテムが登録されている mod の名前であり、 itemname はアイテム自体の名前です。アイテム名は、アイテムが何であるかに関連している必要があり、まだ登録できません。
アイテムには、その名前を指すエイリアスを含めることもできます。エイリアスは、擬似的なアイテム名です。エンジンはエイリアスの出現をアイテム名であるように扱います。これには、主に 2 つの一般的な用途があります。
- 削除されたアイテムの名前を別の名前に変更します。 corrective コードなしでアイテムが mod から削除された場合、world とノードインベントリに不明なノードが存在するようになる可能性があります。削除ノードを取得できる場合は、取得できないノードへのエイリアシングを回避することが重要です。
- ショートカットを追加します。
/giveme dirt
より簡単です/giveme default:dirt
。
エイリアスの登録は非常に簡単です。引数の順序を覚える良い方法はfrom → to
、fromがエイリアスで、toがターゲットであるということです。
minetest.register_alias("dirt", "default:dirt")
mod はアイテム名を直接処理する前にエイリアスを解決する必要があります。エンジンはこれを行いません。ただし、これはすごく簡単です。
itemname = minetest.registered_aliases[itemname] or itemname
Textures
テクスチャ(のファイル)は、modname_itemname.png
という命名形式で textures/
フォルダに配置する必要があります。
JPEG テクスチャはサポートされていますが、透明度はサポートされておらず、一般に低解像度では品質が低くなります。多くの場合、 PNG 形式を使用することをお勧めします。
Minetest のテクスチャは通常 16x16 ピクセルです。これらは任意の解像度にすることができますが、2 のオーダー、たとえば 16 、32 、64 、または 128 にすることをお勧めします。これは、古いデバイスでは他の解像度が正しくサポートされておらず、パフォーマンスが低下する可能性があるためです。
Registering a basic node
minetest.register_node("mymod:diamond", { description = "Alien Diamond", tiles = {"mymod_diamond.png"}, is_ground_content = true, groups = {cracky=3, stone=1} })
tiles
プロパティは、ノードが使用するテクスチャ名のテーブルです。テクスチャが 1 つしかない場合、このテクスチャは6面すべての面で使用されます。側面ごとに異なるテクスチャを与えるには、 6 つのテクスチャの名前を次の順序で指定します。
up (+Y), down (-Y), right (+X), left (-X), back (+Z), front (-Z). (+Y, -Y, +X, -X, +Z, -Z)
3D コンピュータグラフィックスの慣例と同様に、 Minetest では +Y が上向きであることを忘れないでください。
minetest.register_node("mymod:diamond", { description = "Alien Diamond", tiles = { "mymod_diamond_up.png", -- y+ "mymod_diamond_down.png", -- y- "mymod_diamond_right.png", -- x+ "mymod_diamond_left.png", -- x- "mymod_diamond_back.png", -- z+ "mymod_diamond_front.png", -- z- }, is_ground_content = true, groups = {cracky = 3}, drop = "mymod:diamond_fragments" -- ^ Rather than dropping diamond, drop mymod:diamond_fragments })
_ground_content 属性を使用すると、石の上に洞窟を生成できます。これは、マップの生成中に地下に配置される可能性のあるノードにとって不可欠です。エリア内の他のすべてのノードが生成された後、洞窟は world から切り取られます。
Actions and Callbacks
Minetest は、コールバックベースの modding デザインを多用しています。コールバックをアイテム定義テーブルに配置して、さまざまな異なるユーザーイベントに応答できるようにすることができます。
on_use
デフォルトでは、プレーヤーがアイテムを左クリックすると、 use コールバックがトリガーされます。 use コールバックがあると、アイテムがノードの掘削に使用されるのを防ぐことができます。 use コールバックの一般的な使用法の1つは、food(食品)です。
minetest.register_craftitem("mymod:mudpie", { description = "Alien Mud Pie", inventory_image = "myfood_mudpie.png", on_use = minetest.item_eat(20), })
minetest.item_eat 関数に提供される数値は、この food (食品)が消費されたときに回復したヒットポイントの数です。プレイヤーが持っている各ハートアイコンは、 2 つのヒットポイントの価値があります。プレーヤーは通常、最大 10 個のハートを持つことができます。これは、 20 ヒットポイントに相当します。ヒットポイントは整数( whole number )である必要はありません。小数にすることができます。
minetest.item_eat() は、関数を返す関数であり、on_use コールバックとして設定します。これは、上記のコードがほぼ次のようになっていることを意味します。
minetest.register_craftitem("mymod:mudpie", { description = "Alien Mud Pie", inventory_image = "myfood_mudpie.png", on_use = function(...) return minetest.do_item_eat(20, nil, ...) end, })
関数を返すことだけで item_eat がどのように機能するかを理解することで、カスタムサウンドの再生させるなど、より複雑な動作を行うように変更することができます。
Crafting
type
プロパティによって示される、利用可能ないくつかのタイプのクラフトレシピがあります。
- shaped - 材料は正しい位置になければなりません。
- shapeless - 材料がどこにあるかは関係ありません。適切な量があるだけです。
- cooking - 使用するかまど( furnacae )のレシピ。
- fuel - かまどで燃焼できるアイテムを定義します。
- tool_repair - ツールで修復できるアイテムを定義します。
クラフトレシピはアイテムではないため、アイテム名を使用して一意に識別しません。
Shaped
Shoped したレシピは、材料が機能するためには正しい Shope またはパターンである必要があります。以下の例では、クラフトが機能するために、フラグメントは椅子のようなパターンである必要があります。
minetest.register_craft({ type = "shaped", output = "mymod:diamond_chair 99", recipe = { {"mymod:diamond_fragments", "", ""}, {"mymod:diamond_fragments", "mymod:diamond_fragments", ""}, {"mymod:diamond_fragments", "mymod:diamond_fragments", ""} } })
注意すべき点の1つは、右側の空白の列です。これは、図形の右側に空の列が必要であることを意味します。そうでない場合、これは機能しません。この空の列が必要ない場合は、次のように空の文字列を省略できます。
minetest.register_craft({ output = "mymod:diamond_chair 99", recipe = { {"mymod:diamond_fragments", "" }, {"mymod:diamond_fragments", "mymod:diamond_fragments"}, {"mymod:diamond_fragments", "mymod:diamond_fragments"} } })
Shaped はデフォルトのクラフトタイプであるため、タイプフィールドは実際に shaped クラフトには必要ありません。
Shapeless
Shapeless レシピは、材料がどこに配置されていても、そこにあるだけで問題にならない場合に使用されるレシピの一種です。
minetest.register_craft({ type = "shapeless", output = "mymod:diamond 3", recipe = { "mymod:diamond_fragments", "mymod:diamond_fragments", "mymod:diamond_fragments", }, })
Cooking and Fuel
「クッキング」タイプのレシピは、クラフティンググリッドでは作成されませんが、かまどでされるか、または mods で見つかる可能性あります。
minetest.register_craft({ type = "cooking", output = "mymod:diamond_fragments", recipe = "default:coalblock", cooktime = 10, })
コードの唯一の本当の違いは、レシピがテーブル内({中括弧の間})にあるのと比較して、単一のアイテムにすぎないことです。また、アイテムの cooking にかかる時間を定義するオプションの「 cooktime 」パラメーターもあります。これが設定されていない場合、デフォルトで 3 になります。 上記のレシピは、石炭ブロックが入力スロットにあり、その下に何らかの形の燃料がある場合に機能します。10秒後にダイヤモンドの破片ができます!
このタイプは、mods から炉やその他の調理器具で何を燃やすことができるかを定義するため、cooking タイプの付属品です。
minetest.register_craft({ type = "fuel", recipe = "mymod:diamond", burntime = 300, })
他のレシピのような出力はありませんが、燃料として持続する時間を秒単位で定義する燃焼時間( burn time )があります。だから、ダイヤモンドは 300 秒間の燃料として良いです!
Groups
アイテムは多くのグループのメンバーになることができ、グループは多くのメンバーを持つことができます。グループは groups
定義テーブルのプロパティを使用して定義され、関連付けられた値があります。
groups = {cracky = 3, wood = 1}
グループを使用する理由はいくつかあります。まず、グループは、掘削タイプや可燃性( flammability )などのプロパティを説明するために使用されます。次に、アイテム名の代わりにグループをクラフトレシピで使用して、グループ内の任意のアイテムを使用できるようにすることができます。
minetest.register_craft({ type = "shapeless", output = "mymod:diamond_thing 3", recipe = {"group:wood", "mymod:diamond"} })
Tools, Capabilities, and Dig Types
Dig types は、さまざまなツールで掘削したときのノードの強度を定義するために使用されるグループです。関連付けられた値が高い dig タイプのグループは、ノードの切断がより簡単かつ迅速であることを意味します。複数の dig タイプを組み合わせて、複数のタイプのツールをより効率的に使用できるようにすることができます。Dig type のないノードは、どのツールでも掘削できません。
グループ | 最高のツール | 説明 |
---|---|---|
crumbly | spade | 土、砂 |
cracky | pickaxe (つるはし) | タフな(しかしもろい)石のようなもの |
snappy | どれか | 細かい工具を使用して切断できます。 例:葉、小植物、ワイヤー、金属板 |
choppy | axe (斧) | 鋭い力で切ることができます。例:木、木の板 |
fleshy | sword (剣) | 動物やプレイヤーのような生き物。 これは、打撃時の血液への影響を示唆している可能性があります。 |
explody | ? | 特に爆発しやすい |
oddly_breakable_by_hand | どれか | 松明など-非常に素早く掘ります |
すべてのツールにはツール機能があります。機能には、サポートされている Dig type タイプのリストと、掘削時間( dig times )や摩耗量など、各タイプに関連するプロパティが含まれます。ツールは、タイプごとに最大サポート硬度を持つこともできます。これにより、弱いツールが硬いノードを掘るのを防ぐことができます。ツールがすべての Dig type を機能に含めることは非常に一般的であり、あまり適切でないものは非常に非効率的な特性を持っています。プレイヤーが現在使用しているアイテムに明示的なツール機能がない場合は、代わりに現在のハンドの機能が使用されます。
minetest.register_tool("mymod:tool", { description = "My Tool", inventory_image = "mymod_tool.png", tool_capabilities = { full_punch_interval = 1.5, max_drop_level = 1, groupcaps = { crumbly = { maxlevel = 2, uses = 20, times = { [1]=1.60, [2]=1.20, [3]=0.80 } }, }, damage_groups = {fleshy=2}, }, })
Groupcaps は、ノードを掘るためにサポートされている掘りタイプのリストです。ダメージグループは、ツールがオブジェクトにダメージを与える方法を制御するためのものです。これについては、オブジェクト、プレーヤー、エンティティの章で後述します。
4 - Creating Textures
Introduction
テクスチャを作成して最適化できることは、 Minetest 用に開発するときに非常に役立つスキルです。ピクセルアートテクスチャの操作に関連する多くのテクニックがあり、これらのテクニックを理解すると、作成するテクスチャの品質が大幅に向上します。
優れたピクセルアートを作成するための詳細なアプローチはこの本の範囲外であり、代わりに最も関連性のある基本的なテクニックのみを取り上げます。ピクセルアートをより詳細にカバーする、利用可能な多くの優れたオンラインチュートリアルがあります。
Techniques
Using the Pencil
鉛筆ツールは、ほとんどのエディターで使用できます。最小サイズに設定すると、画像の他の部分を変更せずに、一度に1つのピクセルを編集できます。ピクセルを1つずつ操作することで、意図しないぼかしを発生させることなく、クリアでシャープなテクスチャを作成できます。また、高レベルの精度と制御を提供します。
Tiling
ノードに使用されるテクスチャは、通常、タイル状に設計する必要があります。これは、同じテクスチャを持つ複数のノードを一緒に配置すると、エッジが正しく整列することを意味します。
エッジを正しく一致させないと、結果は見た目がはるかに悪くなります。
Transparency
透明度は、ほぼすべてのクラフトアイテムとガラスなどの一部のノードのテクスチャを作成するときに重要です。すべてのエディタが透明度をサポートしているわけではないため、作成するテクスチャに適したエディタを選択してください。
Editors
MS Paint
MS ペイントは、基本的なテクスチャデザインに役立つシンプルなエディタです。ただし、透過性はサポートされていません。これは通常、ノードの側面のテクスチャを作成する場合は問題になりませんが、テクスチャに透明度が必要な場合は、別のエディタを選択する必要があります。
GIMP
GIMP は Minetest コミュニティで一般的に使用されています。その機能の多くがすぐには明らかにならないため、学習曲線はかなり高くなります。
GIMP を使用する場合、鉛筆ツールはツールボックスから選択できます。
消しゴムツールの [ハードエッジ] チェックボックスを選択することもお勧めします。
5 - Node Drawtypes
Node Drawtypes
Introduction
ノードを描画する方法は、 drawtype と呼ばれます。利用可能な drawtype はたくさんあります。 drawtype の動作は、ノードタイプ定義にプロパティを提供することで制御できます。これらのプロパティは、このノードのすべてのインスタンスで修正されています。と呼ばれるものを使用して、ノードごとにいくつかのプロパティを制御することができます param2
。
前の章では、ノードとアイテムの概念が紹介されましたが、ノードの完全な定義は示されていませんでした。 Minetest の世界は、位置の 3D グリッドです。各位置はノードと呼ばれ、ノードタイプ(名前)と 2 つのパラメーター( param1 と param2 )で構成されます。この関数minetest.register_node
は、実際にはノードを登録しないという点で少し誤解を招きます。新しい type のノードを登録します。
ノードパラメータは、ノードが個別にレンダリングされる方法を制御するために使用されます。 param1
は、ノードのライティングを格納するために使用され、 param2
の意味は、ノードタイプ定義の paramtype2
プロパティによって異なります。
- Cubic Nodes: Normal and Allfaces
- Glasslike Nodes
- Airlike Nodes
- Lighting and Sunlight Propagation
- Liquid Nodes
- Node Boxes
- Mesh Nodes
- Signlike Nodes
- Plantlike Nodes
- Firelike Nodes
- More Drawtypes
Cubic Nodes: Normal and Allfaces
Normal Drawtype
通常の drawtype は、通常、立方体ノードをレンダリングするために使用されます。通常のノードの側がソリッド側に接している場合、その側はレンダリングされないため、パフォーマンスが大幅に向上します。
対照的に、 allfaces drawtype は、ソリッドノードに対して上向きの場合でも内側をレンダリングします。これは、リーフノードなど、側面が部分的に透明なノードに適しています。 allfaces_optional drawtype を使用して、ユーザーが遅い描画をオプトアウトできるようにすることができます。その場合、通常のノードのように動作します。
minetest.register_node("mymod:diamond", { description = "Alien Diamond", tiles = {"mymod_diamond.png"}, groups = {cracky = 3}, }) minetest.register_node("default:leaves", { description = "Leaves", drawtype = "allfaces_optional", tiles = {"default_leaves.png"} })
注:通常の drawtype はデフォルトの drawtype であるため、明示的に指定する必要はありません。
Glasslike Nodes
ガラス状ノードと通常ノードの違いは、ガラス状ノードを通常ノードの隣に配置しても、通常ノードの側面が非表示にならないことです。ガラスのようなノードは透明になる傾向があるため、これは便利です。通常の drawtype を使用すると、 world を透視することができます。
Glasslike's Edges
minetest.register_node("default:obsidian_glass", { description = "Obsidian Glass", drawtype = "glasslike", tiles = {"default_obsidian_glass.png"}, paramtype = "light", is_ground_content = false, sunlight_propagates = true, sounds = default.node_sound_glass_defaults(), groups = {cracky=3,oddly_breakable_by_hand=3}, })
Glasslike_Framed
ノードのエッジが個々のノードではなく、3D 効果で全体を一周します。
Glasslike_Framed's Edges
glasslike_framed_optional drawtype を使用して、ユーザーがフレーム付きの外観にオプトインできるようにすることができます。
minetest.register_node("default:glass", { description = "Glass", drawtype = "glasslike_framed", tiles = {"default_glass.png", "default_glass_detail.png"}, inventory_image = minetest.inventorycube("default_glass.png"), paramtype = "light", sunlight_propagates = true, -- Sunlight can shine through block groups = {cracky = 3, oddly_breakable_by_hand = 3}, sounds = default.node_sound_glass_defaults() })
Airlike Nodes
これらのノードはレンダリングされないため、テクスチャはありません。
minetest.register_node("myair:air", { description = "MyAir (you hacker you!)", drawtype = "airlike", paramtype = "light", sunlight_propagates = true, walkable = false, -- Would make the player collide with the air node pointable = false, -- You can't select the node diggable = false, -- You can't dig the node buildable_to = true, -- Nodes can be replace this node. -- (you can place a node and remove the air node -- that used to be there) air_equivalent = true, drop = "", groups = {not_in_creative_inventory=1} })
Lighting and Sunlight Propagation
ノードのライティングは param1 に保存されます。ノードの側面をシェーディングする方法を理解するために、隣接ノードのライト値が使用されます。このため、ソリッドノードは光を遮断するため、光の値がありません。
デフォルトでは、ノードタイプでは、どのノードインスタンスにもライトを保存できません。通常、ガラスや空気などの一部のノードが光を通過できることが望ましいです。これを行うには、定義する必要のある 2 つのプロパティがあります。
paramtype = "light", sunlight_propagates = true,
最初の行は、param1が実際に光レベルを保存することを意味します。 2 行目は、太陽光が値を減少させることなくこのノードを通過する必要があることを意味します。
Liquid Nodes
Liquid Drawtype
液体の種類ごとに、 2 つのノード定義が必要です。1つは液体ソース用で、もう1つは流れる液体用です。
-- Some properties have been removed as they are beyond -- the scope of this chapter. minetest.register_node("default:water_source", { drawtype = "liquid", paramtype = "light", inventory_image = minetest.inventorycube("default_water.png"), -- ^ this is required to stop the inventory image from being animated tiles = { { name = "default_water_source_animated.png", animation = { type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 2.0 } } }, special_tiles = { -- New-style water source material (mostly unused) { name = "default_water_source_animated.png", animation = {type = "vertical_frames", aspect_w = 16, aspect_h = 16, length = 2.0}, backface_culling = false, } }, -- -- Behavior -- walkable = false, -- The player falls through pointable = false, -- The player can't highlight it diggable = false, -- The player can't dig it buildable_to = true, -- Nodes can be replace this node alpha = 160, -- -- Liquid Properties -- drowning = 1, liquidtype = "source", liquid_alternative_flowing = "default:water_flowing", -- ^ when the liquid is flowing liquid_alternative_source = "default:water_source", -- ^ when the liquid is a source liquid_viscosity = WATER_VISC, -- ^ how fast liquid_range = 8, -- ^ how far post_effect_color = {a=64, r=100, g=100, b=200}, -- ^ colour of screen when the player is submerged })
フローノードの定義は似ていますが、名前とアニメーションが異なります。完全な例については、 minetest_game のデフォルト mod の default:water_flowing を参照してください。
Node Boxes
Nodebox drawtype
ノードボックスを使用すると、立方体ではなく、必要な数の直方体で作成されたノードを作成できます。
minetest.register_node("stairs:stair_stone", { drawtype = "nodebox", paramtype = "light", node_box = { type = "fixed", fixed = { {-0.5, -0.5, -0.5, 0.5, 0, 0.5}, {-0.5, 0, 0, 0.5, 0.5, 0.5}, }, } })
最も重要な部分はノードボックステーブルです。
{-0.5, -0.5, -0.5, 0.5, 0, 0.5}, {-0.5, 0, 0, 0.5, 0.5, 0.5}
各行は直方体であり、結合されて1つのノードになります。最初の 3 つの数字は、左下隅の-0.5から0.5までの座標であり、最後の 3 つの数字は反対側の角です。それらは X, Y, Z の形式であり、Yは上です。
NodeBoxEditor)を使用して、エッジをドラッグすることでノードボックスを作成できます。これは、手動で行うよりも視覚的です。
Wallmounted Node Boxes
トーチのように床、壁、または天井に配置するときに、異なる node box が必要になる場合があります。
minetest.register_node("default:sign_wall", { drawtype = "nodebox", node_box = { type = "wallmounted", -- Ceiling wall_top = { {-0.4375, 0.4375, -0.3125, 0.4375, 0.5, 0.3125} }, -- Floor wall_bottom = { {-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125} }, -- Wall wall_side = { {-0.5, -0.3125, -0.4375, -0.4375, 0.3125, 0.4375} } }, })
Mesh Nodes
node box は一般的に作成が簡単ですが、直方体のみで構成できるという制限があります。node box は最適化されていません。完全に非表示になっている場合でも、内面はレンダリングされます。
面はメッシュ上の平らな面です。内面は、 2 つの異なるノードボックスの面が重なるときに発生し、ノードボックスモデルの一部が非表示になりますが、レンダリングされたままになります。
メッシュノードは次のように登録できます。
minetest.register_node("mymod:meshy", { drawtype = "mesh", -- Holds the texture for each "material" tiles = { "mymod_meshy.png" }, -- Path to the mesh mesh = "mymod_meshy.b3d", })
メッシュが models
ディレクトリで使用可能であることを確認してください。ほとんどの場合、メッシュは mod のフォルダーにあるはずですが、依存している別の mod によって提供されるメッシュを共有することは問題ありません。たとえば、より多くのタイプの家具を追加する mod は、基本的な furniture mod によって提供されるモデルを共有したい場合があります。
Signlike Nodes
Signlike nodes はフラットノードであり、他のノードの側面に取り付けることができます。
この drawtype の名前にもかかわらず、サインは実際にはサインライクを使用する傾向はありませんが、代わりに nodebox
drawtype を使用して 3D 効果を提供します。しかし、 signlike
drawtype は、一般的にはしごで使用されています。
minetest.register_node("default:ladder_wood", { drawtype = "signlike", tiles = {"default_ladder_wood.png"}, -- Required: store the rotation in param2 paramtype2 = "wallmounted", selection_box = { type = "wallmounted", }, })
Plantlike Nodes
Plantlike Drawtype
植物のようなノードは、 X のようなパターンでタイルを描画します。
minetest.register_node("default:papyrus", { drawtype = "plantlike", -- Only one texture used tiles = {"default_papyrus.png"}, selection_box = { type = "fixed", fixed = {-6 / 16, -0.5, -6 / 16, 6 / 16, 0.5, 6 / 16}, }, })
Firelike Nodes
Firelike は、壁や天井に「しがみつく」ように設計されていることを除けば、Plantlike に似ています。
minetest.register_node("mymod:clingere", { drawtype = "firelike", -- Only one texture used tiles = { "mymod:clinger" }, })
More Drawtypes
これは包括的なリストではありません。次のような他のタイプがあります。
- Fencelike
- Plantlike rooted - 水の中の植物のため
- Raillike - カートトラックのため
- Torchlike - 2D 壁 / 床 / 天井ノード用。 Minetest Game のトーチは、実際にはメッシュノードの 2 つの異なるノード定義を使用します (default:torch and default:torch_wall) 。
いつものように、完全なリストについては Lua APIのドキュメントをお読みください。
6 - ItemStacks and Inventories
ItemStacks and Inventories
Introduction
この章では、プレーヤーインベントリ、ノードインベントリ、またはデタッチインベントリなどの、インベントリの使用方法と操作方法を学習します。
- What are ItemStacks and Inventories?
- ItemStacks
- Inventory Locations
- Lists
- Modifying Inventories and ItemStacks
- Wear
- Lua Tables
What are ItemStacks and Inventories?
ItemStack は、インベントリ内の単一セルの背後にあるデータです。
inventory は inventory lists コレクションです、それぞれが ItemStacks の 2D グリッドです。 inventory lists は、インベントリのコンテキストでは単に lists と呼ばれます。インベントリのポイントは、プレーヤーとノードに最大で1つのインベントリしかない場合に、複数のグリッドを許可することです。
ItemStacks
ItemStack には、名前( name )、カウント( count )、摩耗( wear )、メタデータ( metadata )の 4 つのコンポーネントがあります。
アイテム名は、登録済みアイテムのアイテム名、エイリアス、または不明なアイテム名の場合があります。不明なアイテムは、ユーザーが mod をアンインストールする場合、または mod がエイリアスの登録などの予防措置なしにアイテムを削除する場合によく見られます。
print(stack:get_name()) stack:set_name("default:dirt") if not stack:is_known() then print("Is an unknown item!") end
カウントは常に 0 以上になります。通常のゲームプレイでは、カウントはアイテムの最大スタックサイズを超えないようにする必要があります - stack_max
。ただし、管理コマンド( admin commands )とバグのある mod を使用すると、スタックが最大サイズを超える場合があります。
print(stack:get_stack_max())
ItemStack は空にすることができ、その場合、カウントは0になります。
print(stack:get_count()) stack:set_count(10)
ItemStack は、 ItemStack 関数を使用して複数の方法で構築できます。
ItemStack() -- name="", count=0 ItemStack("default:pick_stone") -- count=1 ItemStack("default:stone 30") ItemStack({ name = "default:wood", count = 10 })
アイテムメタデータは、アイテムに関するデータの無制限の Key-Value ストアです。 Key-Value は、名前(キーと呼ばれる)を使用してデータ(値と呼ばれる)にアクセスすることを意味します。一部のキーには特別な意味があります。たとえば、description
スタックごとのアイテムの説明に使用されます。これについては、メタデータとストレージの章で詳しく説明します。
Inventory Locations
インベントリロケーションは、インベントリがどこに、どのように保存されるかです。インベントリロケーションには、プレーヤー、ノード、およびデタッチの 3 つのタイプがあります。インベントリは 1 つのロケーションに直接関連付けられています。インベントリが更新されると、すぐに更新されます。
ノードインベントリは、チェストなどの特定のノードの位置に関連しています。ノードはノードメタデータに保存されているため、ロードする必要があります。
local inv = minetest.get_inventory({ type="node", pos={x=1, y=2, z=3} })
上記は、一般にInvRefと呼ばれるインベントリ参照を取得します。ノードインベントリ参照は、ノードインベントリを操作するために使用されます。 参照とは、データが実際にはそのオブジェクト内に格納されていないことを意味しますが、オブジェクトは代わりにデータをインプレースで直接更新します。
ノードインベントリ参照の場所は、次のように見つけることができます。
local location = inv:get_location()
プレーヤーのインベントリは、同様に、またはプレーヤーの参照を使用して取得できます。プレイヤーはインベントリにアクセスするためにオンラインである必要があります。
local inv = minetest.get_inventory({ type="player", name="player1" }) -- or local inv = player:get_inventory()
デタッチされたインベントリは、プレーヤーまたはノードから独立しているインベントリです。切り離されたノードインベントリも、再起動しても保存されません。
local inv = minetest.get_inventory({ type="detached", name="inventory_name" })
他のタイプのインベントリとは異なり、アクセスする前に、まずデタッチされたインベントリを作成する必要があります。
minetest.create_detached_inventory("inventory_name")
create_detached_inventory 関数は 3 つの引数を受け入れますが、最初の引数(インベントリ名)のみが必要です。 2 番目の引数は、コールバックのテーブルを取ります。これは、プレーヤーがインベントリと対話する方法を制御するために使用できます。
-- Input only detached inventory minetest.create_detached_inventory("inventory_name", { allow_move = function(inv, from_list, from_index, to_list, to_index, count, player) return count -- allow moving end, allow_put = function(inv, listname, index, stack, player) return stack:get_count() -- allow putting end, allow_take = function(inv, listname, index, stack, player) return -1 -- don't allow taking end, on_put = function(inv, listname, index, stack, player) minetest.chat_send_all(player:get_player_name() .. " gave " .. stack:to_string() .. " to the donation chest from " .. minetest.pos_to_string(player:get_pos())) end, })
パーミッションコールバック(つまり、 allow_
で始まるもの)は、転送するアイテムの数を返します。転送を完全に防ぐために -1 が使用されます。
アクションコールバック( on
で始まる)には戻り値がありません。
Lists
インベントリリストは、複数のグリッドを1つの場所に保存できるようにするために使用される概念です。これは、メインインベントリやクラフトスロットなど、すべてのゲームに共通のリストが多数あるため、プレイヤーにとって特に便利です。
Size and Width
リストには、グリッド内のセルの総数であるサイズと、エンジン内でのみ使用される幅があります。ウィンドウの背後にあるコードが使用する幅を決定するため、ウィンドウにインベントリを描画する場合、リストの幅は使用されません。
if inv:set_size("main", 32) then inv:set_width("main", 8) print("size: " .. inv:get_size("main")) print("width: " .. inv:get_width("main")) else print("Error! Invalid itemname or size to set_size()") end
set_size
ではリスト名またはサイズが無効な場合は失敗し、 false を返します。たとえば、新しいサイズが小さすぎて、ノードインベントリ内の現在のすべてのアイテムを収めることができない場合があります。
Checking Contents
is_empty
ではリストにアイテムが含まれているかどうかを確認するために使用できます。
if inv:is_empty("main") then print("The list is empty!") end
contains_item
ではリストに特定のアイテムが含まれているかどうかを確認するために使用できます。
if inv:contains_item("main", "default:stone") then print("I've found some stone!") end
Modifying Inventories and ItemStacks
Adding to a List
add_item
ではリストにアイテムを追加します(この場合 "main"
)。以下の例では、最大スタックサイズも尊重されます。
local stack = ItemStack("default:stone 99") local leftover = inv:add_item("main", stack) if leftover:get_count() > 0 then print("Inventory is full! " .. leftover:get_count() .. " items weren't added") end
Taking Items
リストからアイテムを削除するには:
local taken = inv:remove_item("main", stack) print("Took " .. taken:get_count())
Manipulating Stacks
最初に取得することで、個々のスタックを変更できます :
local stack = inv:get_stack(listname, 0)
次に、プロパティを設定するか、以下を尊重するメソッドを使用して、それらを変更します stack_size
:
local stack = ItemStack("default:stone 50") local to_add = ItemStack("default:stone 100") local leftover = stack:add_item(to_add) local taken = stack:take_item(19) print("Could not add" .. leftover:get_count() .. " of the items.") -- ^ will be 51 print("Have " .. stack:get_count() .. " items") -- ^ will be 80 -- min(50+100, stack_max) - 19 = 80 -- where stack_max = 99
add_item
では ItemStack にアイテムを追加し、追加できなかったアイテムを返します。
take_item
ではアイテムの数まで要しますが、それより少なくなる場合があり、取得したスタックを返します。
最後に、アイテムスタックを設定します。
inv:set_stack(listname, 0, stack)
Wear
ツールには摩耗があります。摩耗はプログレスバーを示し、完全に摩耗するとツールが壊れます。摩耗は 65535 のうちの数です。高いほど、ツールは摩耗します。
摩耗は add_wear()
、 get_wear()
と set_wear(wear)
を使用して操作することができます。
local stack = ItemStack("default:pick_mese") local max_uses = 10 -- This is done automatically when you use a tool that digs things -- It increases the wear of an item by one use. stack:add_wear(65535 / (max_uses - 1))
ノードを掘るとき、ツールの摩耗の量は、掘られるノードに依存する可能性があります。したがって、 max_uses は、何を掘っているかによって異なります。
Lua Tables
ItemStacks と Inventory は、テーブルとの間で変換できます。これは、コピーおよび一括操作に役立ちます。
-- Entire inventory local data = inv1:get_lists() inv2:set_lists(data) -- One list local listdata = inv1:get_list("main") inv2:set_list("main", listdata)
get_lists()
によって返されるリストのテーブルは次の形式になります。
{ list_one = { ItemStack, ItemStack, ItemStack, ItemStack, -- inv:get_size("list_one") elements }, list_two = { ItemStack, ItemStack, ItemStack, ItemStack, -- inv:get_size("list_two") elements } }
get_list()
は ItemStack のリストとして単一のリストを返します。
注意すべき重要な点の1つは、上記のsetメソッドはリストのサイズを変更しないということです。これは、リストを空のテーブルに設定することでリストをクリアでき、サイズが減少しないことを意味します。
inv:set_list("main", {})
7 - Basic Map Operations
Introduction
この章では、マップ上で基本的なアクションを実行する方法を学習します。
Map Structure
Minetest マップは MapBlock に分割され、各 MapBlock はサイズ16の立方体です。プレイヤーがマップ内を移動すると、MapBlock が作成、ロード、およびアンロードされます。まだロードされていないマップの領域は、無視できるノード、つまり通過できない選択できないプレースホルダーノードでいっぱいです。空のスペースは、通り抜けることができる目に見えないノードである空気ノードで満たされています。
ロードされたマップブロックは、アクティブブロックと呼ばれることがよくあります。アクティブブロックは、 mods またはプレーヤーからの読み取りまたは書き込みが可能で、アクティブエンティティがあります。エンジンは、液体物理学の実行など、マップ上での操作も実行します。
MapBlocks は、ワールドデータベースからロードするか、生成することができます。 MapBlocks は、デフォルトで最大値 31000 に設定されているマップ生成制限( mapgen_limit
)まで生成されます。ただし、既存の MapBlock は、生成制限の範囲外でワールドデータベースからロードできます。
Reading
Reading Nodes
位置が決まったら、地図から読みだすことができます。
local node = minetest.get_node({ x = 1, y = 3, z = 4 }) print(dump(node)) --> { name=.., param1=.., param2=.. }
位置が小数の場合、それを含むノードに丸められます。この関数は常にノード情報を含むテーブルを返します。
name
- ノード名。エリアがアンロードされるときに無視されます。param1
- ノード定義を参照してください。これは一般的に軽いでしょう。param2
- ノード定義を参照してください。
ブロックが非アクティブの場合、関数は含まれているブロックをロードせず、代わりに name
が ignore
のテーブルを返すことに注意してください。代わりに minetest.get_node_or_nil
を使用すると、ignore
という名前のテーブルではなく nil
が返されます。 ただし、それでもブロックは読み込まれません。 ブロックに実際に ignore
が含まれている場合でも、これは ignore
を返す可能性があります。 これは、マップ生成制限で定義されているように、マップの端( mapgen_limit
)の近くで発生します。
Finding Nodes
Minetestは、一般的なマップアクションを高速化するための多数のヘルパー関数を提供します。これらの中で最も一般的に使用されるのは、ノードを見つけるためです。
たとえば、メセの近くでよりよく育つ特定の種類の植物を作りたいとしましょう。近くのメセノードを検索し、それに応じて成長率を調整する必要があります。
local grow_speed = 1 local node_pos = minetest.find_node_near(pos, 5, { "default:mese" }) if node_pos then minetest.chat_send_all("Node found at: " .. dump(node_pos)) grow_speed = 2 end
たとえば、近くに mese が多いほど成長率が上がるとしましょう。次に、エリア内の複数のノードを見つけることができる関数を使用する必要があります。
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 }) local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 }) local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" }) local grow_speed = 1 + #pos_list
上記のコードは、面積に基づいてチェックするのに対し find_node_near
、範囲に基づいてチェックするため、私たちが望むことを完全には実行しません。これを修正するには、残念ながら、手動で範囲を確認する必要があります。
local pos1 = vector.subtract(pos, { x = 5, y = 5, z = 5 }) local pos2 = vector.add(pos, { x = 5, y = 5, z = 5 }) local pos_list = minetest.find_nodes_in_area(pos1, pos2, { "default:mese" }) local grow_speed = 1 for i=1, #pos_list do local delta = vector.subtract(pos_list[i], pos) if delta.x*delta.x + delta.y*delta.y <= 5*5 then grow_speed = grow_speed + 1 end end
これで、範囲内の mese ノードに基づいてコードが grow_speed
に正しく増加します。実際の距離を取得するために位置を二乗するのではなく、位置からの距離の二乗を比較した方法に注意してください。これは、コンピューターが平方根の計算コストを高くするのを可能な限り回避する必要があるためです。
find_nodes_with_meta
、 find_nodes_in_area_under_air
など、上記の 2 つの関数にはさらに多くのバリエーションがあり、これらは同様に機能し、他の状況で役立ちます。
Writing
Writing Nodes
set_node
マップへの書き込みに使用できます。 set_node を呼び出すたびに、ライティングが再計算されます。つまり、多数のノードでは set_node がかなり遅くなります。
minetest.set_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" }) local node = minetest.get_node({ x = 1, y = 3, z = 4 }) print(node.name) --> default:mese
set_node は、関連するメタデータまたはインベントリをその位置から削除します。これは、すべての状況で望ましいわけではありません。特に、 1 つの概念ノードを表すために複数のノード定義を使用している場合はそうです。 この例は、ファーネスノードです。概念的には 1 つのノードと考えていますが、実際には 2 つです。
次のように、メタデータやインベントリを削除せずにノードを設定できます。
minetest.swap_node({ x = 1, y = 3, z = 4 }, { name = "default:mese" })
Removing Nodes
ノードは常に存在する必要があります。ノードを削除するには、位置を air
に設定します。
次の 2 行は両方ともノードを削除し、両方とも同一です。
minetest.remove_node(pos) minetest.set_node(pos, { name = "air" })
実際、remove_node は、名前が air の set_node を呼び出します。
Loading Blocks
minetest.emerge_area
を使用してマップブロックをロードできます。 出現領域は非同期です。つまり、ブロックはすぐには読み込まれません。代わりに、それらは将来すぐにロードされ、コールバックは毎回呼び出されます。
-- Load a 20x20x20 area local halfsize = { x = 10, y = 10, z = 10 } local pos1 = vector.subtract(pos, halfsize) local pos2 = vector.add (pos, halfsize) local context = {} -- persist data between callback calls minetest.emerge_area(pos1, pos2, emerge_callback, context)
Minetest は、ブロックをロードするたびに、進行状況情報とともに emerge_callback
を呼び出します。
local function emerge_callback(pos, action, num_calls_remaining, context) -- On first call, record number of blocks if not context.total_blocks then context.total_blocks = num_calls_remaining + 1 context.loaded_blocks = 0 end -- Increment number of blocks loaded context.loaded_blocks = context.loaded_blocks + 1 -- Send progress message if context.total_blocks == context.loaded_blocks then minetest.chat_send_all("Finished loading blocks!") end local perc = 100 * context.loaded_blocks / context.total_blocks local msg = string.format("Loading blocks %d/%d (%.2f%%)", context.loaded_blocks, context.total_blocks, perc) minetest.chat_send_all(msg) end end
これはブロックをロードする唯一の方法ではありません。LVM を使用すると、包含ブロック( the encompassed blocks )が同期的にロードされます。
Deleting Blocks
delete_blocks を使用して、マップブロックの範囲を削除できます。
-- Delete a 20x20x20 area local halfsize = { x = 10, y = 10, z = 10 } local pos1 = vector.subtract(pos, halfsize) local pos2 = vector.add (pos, halfsize) minetest.delete_area(pos1, pos2)
これにより、そのエリア内のすべてのマップブロックが包括的に削除されます。 これは、一部のノードがエリア境界とオーバーラップするマップブロック上にあるため、エリア外で削除されることを意味します。
Introduction
特定のノードで関数を定期的に実行することは、一般的なタスクです。 Minetest は、これを行う 2 つの方法を提供します。アクティブブロック修飾子( ABM )とノードタイマーです。
ABM は、ロードされたすべての MapBlock をスキャンして、基準に一致するノードを探します。草など、世界で頻繁に見られるノードに最適です。CPU のオーバーヘッドは高くなりますが、メモリとストレージのオーバーヘッドは低くなります。
かまどやマシンなど、一般的でないノードやすでにメタデータを使用しているノードの場合は、代わりにノードタイマーを使用する必要があります。 ノードタイマーは、各 MapBlock で保留中のタイマーを追跡し、期限切れになったときに実行することで機能します。 つまり、タイマーは、一致するものを見つけるためにロードされたすべてのノードを検索する必要はありませんが、代わりに、保留中のタイマーを追跡するためにわずかに多くのメモリとストレージを必要とします。
Node Timers
ノードタイマーは、単一のノードに直接関連付けられています。 NodeTimerRef オブジェクトを取得することで、ノードタイマーを管理できます。
local timer = minetest.get_node_timer(pos) timer:start(10.5) -- in seconds
ステータスを確認したり、タイマーを停止したりすることもできます。
if timer:is_started() then print("The timer is running, and has " .. timer:get_timeout() .. "s remaining!") print(timer:get_elapsed() .. "s has elapsed.") end timer:stop()
ノードタイマーがアップすると、ノードの定義テーブルの on_timer
メソッドが呼び出されます。このメソッドは、ノードの位置という1つのパラメーターのみを取ります。
minetest.register_node("autodoors:door_open", { on_timer = function(pos) minetest.set_node(pos, { name = "autodoors:door" }) return false end })
on_timer
で true を返すと、タイマーが同じ間隔で再度実行されます。
タイマーの制限に気付いたかもしれません。最適化の理由から、ノードタイプごとに1つのタイプのタイマーのみを実行でき、ノードごとに1つのタイマーのみを実行できます。
Active Block Modifiers
この章では、エイリアングラスは水の近くに現れる可能性のあるタイプのグラスです。
minetest.register_node("aliens:grass", { description = "Alien Grass", light_source = 3, -- The node radiates light. Min 0, max 14 tiles = {"aliens_grass.png"}, groups = {choppy=1}, on_use = minetest.item_eat(20) }) minetest.register_abm({ nodenames = {"default:dirt_with_grass"}, neighbors = {"default:water_source", "default:water_flowing"}, interval = 10.0, -- Run every 10 seconds chance = 50, -- Select every 1 in 50 nodes action = function(pos, node, active_object_count, active_object_count_wider) local pos = {x = pos.x, y = pos.y + 1, z = pos.z} minetest.set_node(pos, {name = "aliens:grass"}) end })
この ABM は 10 秒ごとに実行され、一致するノードごとに、 50 分の1の確率で実行されます。 ABM がノード上で実行されている場合、エイリアングラスノードがその上に配置されます。 これにより、以前にその位置にあったノードが削除されることに注意してください。 これを防ぐには、 minetest.get_node を使用して、草のためのスペースがあることを確認するチェックを含める必要があります。
ネイバーの指定はオプションです。 複数のネイバーを指定する場合、要件を満たすために存在する必要があるのはそのうちの1つだけです。
チャンスの指定もオプションです。 チャンスを指定しない場合、ABM は他の条件が満たされたときに常に実行されます。
Your Turn
- midas touch: 5秒ごとに100回に1回の確率で水を金のブロックに変えます。
- Decay : 水が隣にあるとき、木を土に変えます。
- Burnin' : すべてのエアノードに火をつけます。(ヒント:「 air 」および「 fire:basic_flame 」)。警告:ゲームがクラッシュします。
9 - Storage and Metadata
Introduction
この章では、データを保存する方法を学習します。
Metadata
What is Metadata?
Minetest では、メタデータはカスタムデータを何かに添付するために使用される Key-Value ストアです。メタデータを使用して、ノード、プレーヤー、または ItemStack に対する情報を格納できます。
各タイプのメタデータは、まったく同じAPIを使用します。メタデータは値を文字列として格納しますが、他のプリミティブ型を変換して格納する方法はいくつかあります。
メタデータの一部のキーには、特別な意味がある場合があります。 たとえば、ノードメタデータの infotext
は、十字線を使用してノードにカーソルを合わせたときに表示されるツールチップを格納するために使用されます。 他の mod との競合を避けるために、キーには標準の名前空間規則 modname:keyname
を使用する必要があります。 例外は owner
として保存される所有者名などの従来のデータです。
メタデータはデータに関するデータです。ノードのタイプやスタックの数などのデータ自体はメタデータではありません。
Obtaining a Metadata Object
ノードの位置がわかっている場合は、そのメタデータを取得できます。
local meta = minetest.get_meta({ x = 1, y = 2, z = 3 })
Player および ItemStack メタデータは、 get_meta()
を使用して取得されます。
local pmeta = player:get_meta() local imeta = stack:get_meta()
Reading and Writing
ほとんどの場合、 get <type>()
メソッドと set <type>()
メソッドを使用してメタの読み取りと書き込みを行います。メタデータは文字列を格納するため、文字列メソッドは直接値を設定して取得します。
print(meta:get_string("foo")) --> "" meta:set_string("foo", "bar") print(meta:get_string("foo")) --> "bar"
キーが存在しない場合、入力されたすべてのゲッターはニュートラルなデフォルト値を返します。 " "
や 0
など。 get()
を使用して、文字列または nil を返すことができます。
メタデータは参照であるため、変更はすべてソースに自動的に更新されます。 ただし、 ItemStack は参照ではないため、インベントリ内の itemstack を更新する必要があります。
型指定されていないゲッターとセッターは、文字列との間で変換されます :
print(meta:get_int("count")) --> 0 meta:set_int("count", 3) print(meta:get_int("count")) --> 3 print(meta:get_string("count")) --> "3"
Special Keys
infotext
はノードメタデータで使用され、十字線をノード上に置いたときにツールチップを表示します。 これは、ノードの所有権またはステータスを表示するときに役立ちます。
description
は ItemStack メタデータで使用され、インベントリ内のスタックにカーソルを合わせたときに説明を上書きします。 minetest.colorize()
でエンコードすることで色を使用できます。
owner
は、アイテムまたはノードを所有するプレーヤーのユーザー名を保存するために使用される共通のキーです。
Storing Tables
テーブルは、保存する前に文字列に変換する必要があります。 Minetest は、これを行うために Lua と JSON の 2 つの形式を提供します。
Lua メソッドははるかに高速で、Lua がテーブルに使用する形式と一致する傾向がありますが、JSON はより標準的な形式であり、構造が優れており、別のプログラムと情報を交換する必要がある場合に適しています。
local data = { username = "player1", score = 1234 } meta:set_string("foo", minetest.serialize(data)) data = minetest.deserialize(minetest:get_string("foo"))
Private Metadata
ノードメタデータのエントリはプライベートとしてマークでき、クライアントに送信されません。 プライベートとしてマークされていないエントリは、クライアントに送信されます。
meta:set_string("secret", "asd34dn") meta:mark_as_private("secret")
Lua Tables
to_table
と from_table
を使用して Lua テーブルとの間で変換できます :
local tmp = meta:to_table() tmp.foo = "bar" meta:from_table(tmp)
Mod Storage
Mod ストレージは、技術的にはメタデータではありませんが、メタデータとまったく同じ API を使用します。 Mod ストレージは Mod ごとに、ロード時にのみどの Mod がそれを要求しているかを知るために取得できます。
local storage = minetest.get_mod_storage()
メタデータと同じようにストレージを操作できるようになりました :
storage:set_string("foo", "bar")
Databases
mod がサーバーで使用される可能性が高く、大量のデータを保存する場合は、データベースの保存方法を提供することをお勧めします。 データの保存方法と使用場所を分離して、これをオプションにする必要があります。
local backend if use_database then backend = dofile(minetest.get_modpath("mymod") .. "/backend_sqlite.lua") else backend = dofile(minetest.get_modpath("mymod") .. "/backend_storage.lua") end backend.get_foo("a") backend.set_foo("a", { score = 3 })
backend_storage.lua ファイルには、 mod ストレージの実装が含まれている必要があります :
local storage = minetest.get_mod_storage() local backend = {} function backend.set_foo(key, value) storage:set_string(key, minetest.serialize(value)) end function backend.get_foo(key, value) return minetest.deserialize(storage:get_string(key)) end return backend
backend_sqlite も同様のことを行いますが、mod ストレージの代わりに Lualsqlite3 ライブラリを使用します。
SQLite などのデータベースを使用するには、安全でない環境を使用する必要があります。安全でない環境とは、ユーザーが明示的にホワイトリストに登録した Mod のみが利用できるテーブルであり、悪意のある Mod が利用できる場合に悪用される可能性のある Lua API の制限の少ないコピーが含まれています。安全でない環境については、セキュリティ 章で詳しく説明します。
local ie = minetest.request_insecure_environment() assert(ie, "Please add mymod to secure.trusted_mods in the settings") local _sql = ie.require("lsqlite3") -- Prevent other mods from using the global sqlite3 library if sqlite3 then sqlite3 = nil end
SQL または、lsqlite3 library の使い方を説明するのはこの本の範疇を超えています。
Deciding Which to Use
使用するメソッドのタイプは、データの内容、フォーマット方法、およびデータの大きさによって異なります。ガイドラインとして、小さいデータは最大 10K 、中程度のデータは最大 10MB 、大きいデータはそれを超える任意のサイズです。
ノードメタデータは、ノード関連のデータを保存する必要がある場合に適しています。中程度のデータを非公開にすると、かなり効率的に保存できます。
アイテムのメタデータは、クライアントへの送信を回避することができないため、少量のデータ以外のものを格納するために使用しないでください。スタックが移動されるか、Lua からアクセスされるたびに、データもコピーされます。
Mod ストレージは中程度のデータには適していますが、大きなデータの書き込みは非効率的である可能性があります。保存のたびにすべてのデータを書き出す必要がないように、大きなデータにはデータベースを使用することをお勧めします。
安全でない環境にアクセスするために mod をホワイトリストに登録する必要があるため、データベースはサーバーでのみ実行可能です。大規模なデータセットに最適です。
Your Turn
- 5回パンチすると消えるノードを作ります。(ノード定義の
on_punch
とminetest.set_node
を使います)
10 - Objects, Players, and Entities
Introduction
この章では、オブジェクトを操作する方法と、オブジェクトを定義する方法を学習します。
- What are Objects, Players, and Entities?
- Position and Velocity
- Object Properties
- Entities
- Attachments
- Your Turn
What are Objects, Players, and Entities?
プレーヤーとエンティティはどちらもオブジェクトのタイプです。オブジェクトは、ノードグリッドとは独立して移動できるものであり、速度やスケールなどのプロパティがあります。オブジェクトはアイテムではなく、独自の個別の登録システムがあります。
プレイヤーとエンティティの間にはいくつかの違いがあります。最大のものは、プレイヤーがプレイヤーによって制御されるのに対し、エンティティは mod によって制御されることです。これは、プレイヤーの速度を mod で設定できないことを意味します。プレイヤーはクライアント側であり、エンティティはサーバー側です。もう1つの違いは、プレーヤーによってマップブロックが読み込まれるのに対し、エンティティは保存されて非アクティブになることです。
この区別は、後で説明するように、エンティティが Lua エンティティと呼ばれるテーブルを使用して制御されるという事実によって混乱しています。
Position and Velocity
get_pos
とset_pos
は、エンティティの位置を取得および設定できるようにするために存在します。
local object = minetest.get_player_by_name("bob") local pos = object:get_pos() object:set_pos({ x = pos.x, y = pos.y + 1, z = pos.z })
set_pos
は、アニメーションなしですぐに位置を設定します。 オブジェクトを新しい位置にスムーズにアニメーション化する場合は、 move_to
を使用する必要があります。 残念ながら、これはエンティティに対してのみ機能します。
object:move_to({ x = pos.x, y = pos.y + 1, z = pos.z })
エンティティを処理するときに考慮すべき重要なことは、ネットワークの遅延です。 理想的な世界では、エンティティの動きに関するメッセージは、正しい順序で、送信方法と同様の間隔ですぐに届きます。 ただし、シングルプレイヤーでない限り、これは理想的な世界ではありません。 メッセージが届くまでしばらく時間がかかります。 位置メッセージが順不同で到着する可能性があり、現在の既知の位置より古い位置に移動するポイントがないため、一部の set_pos
呼び出しがスキップされます。 動きの間隔が同じでない場合があり、アニメーションに使用するのが困難になります。
これらすべての結果、クライアントはサーバーに対してさまざまなことを認識します。これは、注意する必要があることです。
Object Properties
オブジェクトプロパティは、オブジェクトをレンダリングして処理する方法をクライアントに指示するために使用されます。 定義上、プロパティはエンジンが使用するためのものであるため、カスタムプロパティを定義することはできません。
ノードとは異なり、オブジェクトは設定された外観ではなく動的な外観を持っています。プロパティを更新することで、オブジェクトの外観をいつでも変更できます。
object:set_properties({ visual = "mesh", mesh = "character.b3d", textures = {"character_texture.png"}, visual_size = {x=1, y=1}, })
更新されたプロパティは、範囲内のすべてのプレイヤーに送信されます。これは、プレイヤーごとにスキンが異なるなど、大量のバリエーションを非常に安価に入手するのに非常に便利です。
次のセクションに示すように、エンティティはその定義で提供される初期プロパティを持つことができます。ただし、デフォルトのプレーヤープロパティはエンジンで定義されているため、新しく参加したプレーヤーのプロパティを設定するには、 on_joinplayer
で set_properties()
を使用する必要があります。
Entities
エンティティには、アイテム定義テーブルに似た定義テーブルがあります。このテーブルには、コールバックメソッド、初期オブジェクトプロパティ、およびカスタムメンバーを含めることができます。
ただし、エンティティは1つの非常に重要な点でアイテムとは異なります。エンティティが出現すると(つまり、ロードまたは作成されると)、メタテーブルを使用して定義テーブルから継承するそのエンティティの新しいテーブルが作成されます。
この新しいテーブルは、一般に Lua エンティティテーブルと呼ばれます。
メタテーブルは、Lua 言語の重要な部分であるため、知っておく必要のある重要な Lua 機能です。
素人の言葉で言えば、メタテーブルを使用すると、特定の Lua 構文を使用するときにテーブルがどのように動作するかを制御できます。メタテーブルの最も一般的な使用法は、別のテーブルをプロトタイプとして使用する機能です。現在のテーブルに存在しない場合は、デフォルトで他のテーブルのプロパティとメソッドが使用されます。
テーブル A のメンバー X にアクセスするとします。テーブル A にそのメンバーがある場合、通常どおりに返されます。ただし、テーブルにそのメンバーがなくても、メタテーブルの可能性があるテーブル B がある場合は、テーブル B がそのメンバーを持っているかどうかを確認します。
local MyEntity = { initial_properties = { hp_max = 1, physical = true, collide_with_objects = false, collisionbox = {-0.3, -0.3, -0.3, 0.3, 0.3, 0.3}, visual = "wielditem", visual_size = {x = 0.4, y = 0.4}, textures = {""}, spritediv = {x = 1, y = 1}, initial_sprite_basepos = {x = 0, y = 0}, }, message = "Default message", } function MyEntity:set_message(msg) self.message = msg end
エンティティが出現すると、そのタイプテーブルからすべてをコピーすることにより、エンティティ用のテーブルが作成されます。このテーブルは、その特定のエンティティの変数を格納するために使用できます。
local entity = object:get_luaentity() local object = entity.object print("entity is at " .. minetest.pos_to_string(object:get_pos()))
エンティティで使用できるコールバックは多数あります。完全なリストはlua_api.txtにあります。
function MyEntity:on_step(dtime) local pos = self.object:get_pos() local pos_down = vector.subtract(pos, vector.new(0, 1, 0)) local delta if minetest.get_node(pos_down).name == "air" then delta = vector.new(0, -1, 0) elseif minetest.get_node(pos).name == "air" then delta = vector.new(0, 0, 1) else delta = vector.new(0, 1, 0) end delta = vector.multiply(delta, dtime) self.object:move_to(vector.add(pos, delta)) end function MyEntity:on_punch(hitter) minetest.chat_send_player(hitter:get_player_name(), self.message) end
ここで、このエンティティをスポーンして使用すると、エンティティが非アクティブになってから再びアクティブになると、メッセージが忘れられることに気付くでしょう。これは、メッセージが保存されていないためです。 Minetest では、エンティティテーブルにすべてを保存するのではなく、保存方法を制御できます。 Staticdata は、保存する必要のあるすべてのカスタム情報を含む文字列です。
function MyEntity:get_staticdata() return minetest.write_json({ message = self.message, }) end function MyEntity:on_activate(staticdata, dtime_s) if staticdata ~= "" and staticdata ~= nil then local data = minetest.parse_json(staticdata) or {} self:set_message(data.message) end end
Minetest は、いつでも何度でも get_staticdata()
を呼び出すことができます。これは、 Minetest が MapBlock が非アクティブになるのを待たずに保存するためです。これにより、データが失われる可能性があります。 MapBlock は約18秒ごとに保存されるため、 get_staticdata()
が呼び出される間隔も同様であることに注意してください。
一方、 on_activate()
は、 MapBlock がアクティブになるか、エンティティの生成によってエンティティがアクティブになったときにのみ呼び出されます。これは、 staticdata が空である可能性があることを意味します。
最後に、適切な名前の register_entity
を使用して型テーブルを登録する必要があります。
minetest.register_entity("mymod:entity", MyEntity)
エンティティは、次のような mod によって生成できます。
local pos = { x = 1, y = 2, z = 3 } local obj = minetest.add_entity(pos, "mymod:entity", nil)
3 番目のパラメーターは初期静的データです。メッセージを設定するには、エンティティテーブルメソッドを使用できます。
obj:get_luaentity():set_message("hello!")
give privilegeを持つプレイヤーは、 chat コマンドを使用してエンティティを生成できます。
/spawnentity mymod:entity
Attachments
アタッチされたオブジェクトは、親(アタッチされているオブジェクト)が移動すると移動します。アタッチされたオブジェクトは、親の子であると言われます。オブジェクトには無制限の数の子を含めることができますが、親は多くても 1 つです。
child:set_attach(parent, bone, position, rotation)
オブジェクトの get_pos()
は、オブジェクトがアタッチされているかどうかに関係なく、常にオブジェクトのグローバル位置を返します。 set_attach
は相対的な位置を取りますが、期待どおりではありません。アタッチメントの位置は、 10 倍に拡大された親の原点を基準にしています。
したがって、 0,5,0
は、親の原点の半分上のノードになります。
⚠ 度とラジアン |
---|
アタッチメントの回転は度で設定されますが、オブジェクトの回転は ラジアンです。必ず正しい角度測定に変換してください。 |
アニメーションのある 3D モデルの場合、ボーン引数を使用してエンティティをボーンにアタッチします。 3D アニメーションは、スケルトンに基づいています。モデル内のボーンのネットワークで、各ボーンに位置と回転を指定して、モデルを変更したり、腕を動かしたりできます。ボーンにアタッチすることは、キャラクターに何かを持たせたい場合に便利です。
obj:set_attach(player, "Arm_Right", -- default bone {x=0.2, y=6.5, z=3}, -- default position {x=-100, y=225, z=90}) -- default rotation
Your Turn
- ノードとエンティティを組み合わせて風車を作成します。
- 選択したmobを作成します(エンティティ API のみを使用し、他の Mod は使用しません)。
11 - Privileges
Introduction
特権は、しばしば略して priv と呼ばれ、プレーヤーに特定のアクションを実行する能力を与えます。サーバーの所有者は、各プレーヤーが持つ能力を制御するための特権を付与および取り消すことができます。
- When to use Privileges
- Declaring Privileges
- Checking for Privileges
- Getting and Setting Privileges
- Adding Privileges to basic_privs
When to use Privileges
特権はプレイヤーに何かをする能力を与えるべきです。クラスまたはステータスを示すための特権ではありません。
Good Privileges:
- interact
- shout
- noclip
- fly
- kick
- ban
- vote
- worldedit
- area_admin - admin functions of one mod is ok
Bad Privileges:
- moderator
- admin
- elf
- dwarf
Declaring Privileges
register_privilege
を使用して、新しい特権を宣言します。
minetest.register_privilege("vote", { description = "Can vote on issues", give_to_singleplayer = true })
give_to_singleplayer
は、指定されていない場合、デフォルトで true に設定されるため、上記の定義では実際には必要ありません。
Checking for Privileges
プレーヤーが必要なすべての特権を持っているかどうかをすばやく確認するには:
local has, missing = minetest.check_player_privs(player_or_name, { interact = true, vote = true })
この例では、プレーヤーが必要なすべての特権を持っている場合、 has
は true です。 has
が false の場合、 missing
には欠落している特権の Key-Value テーブルが含まれます。
local has, missing = minetest.check_player_privs(name, { interact = true, vote = true }) if has then print("Player has all privs!") else print("Player is missing privs: " .. dump(missing)) end
不足している権限を確認する必要がない場合は、 check_player_privs
を直接 if ステートメントに入れることができます。
if not minetest.check_player_privs(name, { interact=true }) then return false, "You need interact for this!" end
Getting and Setting Privileges
プレーヤーがオンラインであるかどうかに関係なく、プレーヤーの特権にアクセスまたは変更できます。
local privs = minetest.get_player_privs(name) print(dump(privs)) privs.vote = true minetest.set_player_privs(name, privs)
特権は常に Key-Value テーブルとして指定され、キーは特権名、値はブール値です。
{ fly = true, interact = true, shout = true }
Adding Privileges to basic_privs
basic_privs
特権を持つプレーヤーは、限られた特権のセットを付与および取り消すことができます。モデレーターにこの権限を付与して、「インタラクト」や「シャウト」を付与および取り消すことができるのが一般的ですが、「ギブ」や「サーバー」など、悪用される可能性の高い自分や他のプレーヤーに権限を付与することはできません。
basic_privs
に特権を追加し、モデレーターが他のプレーヤーに付与および取り消すことができる特権を調整するには、 basic_privs
設定を変更する必要があります。
デフォルトでは、 basic_privs
の値は次のとおりです。
basic_privs = interact, shout
vote
を追加するには、これを次のように更新します。
basic_privs = interact, shout, vote
これにより、 basic_privs
を持つプレイヤーは、vote
特権を付与および取り消すことができます。
12 - Chat and Commands
Introduction
Mod は、メッセージの送信、メッセージの傍受、チャットコマンドの登録など、プレーヤーのチャットと対話できます。
- Sending Messages to All Players
- Sending Messages to Specific Players
- Chat Commands
- Complex Subcommands
- Intercepting Messages
Sending Messages to All Players
ゲーム内のすべてのプレーヤーにメッセージを送信するには、 chat_send_all 関数を呼び出します。
minetest.chat_send_all("This is a chat message to all players")
これがゲーム内でどのように表示されるかの例を次に示します。
<player1> Look at this entrance This is a chat message to all players <player2> What about it?
メッセージは、ゲーム内のプレーヤーのチャットと区別するために別の行に表示されます。
Sending Messages to Specific Players
特定のプレーヤーにメッセージを送信するには、chat_send_player 関数を呼び出します。
minetest.chat_send_player("player1", "This is a chat message for player1")
このメッセージは、すべてのプレーヤーへのメッセージと同じ方法で表示されますが、指定されたプレーヤー(この場合は player1 )にのみ表示されます。
Chat Commands
/foo
などのチャットコマンドを登録するには、register_chatcommand
を使用します。
minetest.register_chatcommand("foo", { privs = { interact = true, }, func = function(name, param) return true, "You said " .. param .. "!" end, })
上記のスニペットでは、 interact
は必須のprivilegeとしてリストされています。つまり、 interact
特権を持つプレーヤーだけがコマンドを実行できます。
チャットコマンドは最大 2 つの値を返すことができます。 1 つは成功を示すブール値で、もう 1 つはユーザーに送信するメッセージです。
⚠ オフラインプレイヤーはコマンドを実行できます |
---|
Mod はオフラインプレーヤーに代わってコマンドを実行できるため、プレーヤーオブジェクトの代わりにプレーヤー名が渡されます。たとえば、IRCブリッジを使用すると、プレーヤーはゲームに参加せずにコマンドを実行できます。 |
したがって、プレーヤーがオンラインであると想定しないようにしてください。確認するにはこれをチェックします |
minetest.get_player_by_name |
プレーヤーを返します。 |
Complex Subcommands
多くの場合、次のような複雑なチャットコマンドを作成する必要があります。
/msg <to> <message>
/team join <teamname>
/team leave <teamname>
/team list
これは通常、 Lua パターンを使用して行われます。パターンは、ルールを使用してテキストからコンテンツを抽出する方法です。
local to, msg = string.match(param, "^([%a%d_-]+) (*+)$")
上記のコードは /msg <to> <message>
を実装しています。左から右に見ていきましょう。
^
は、文字列の先頭に一致することを意味します。()
は一致するグループです-ここにあるものと一致するものはすべて string.match から返されます。[]
は、このリスト内の文字を受け入れることを意味します。%a
は任意の文字を受け入れることを意味し、%d
は任意の数字を受け入れることを意味します。[%a%d_-]
は、任意の文字または数字、あるいは_
または-
を受け入れることを意味します。+
は、 1 回以上前のものと一致することを意味します。*
は、このコンテキストの任意の文字に一致することを意味します。$
は、文字列の末尾に一致することを意味します。
簡単に言えば、パターンは名前(文字/数字/-/のみの単語)、スペース、メッセージ( 1 つ以上の任意の文字)に一致します。名前とメッセージは括弧で囲まれているため、返されます。
これが、ほとんどの Mod が複雑なチャットコマンドを実装する方法です。 Lua パターンのより良いガイドは、おそらくlua-users.orgチュートリアルまたはPILドキュメントでしょう。
Chat Command Builderと呼ばれるパターンなしで複雑なチャットコマンドを作成するために使用できる、この本の著者によって書かれたライブラリもあります。
Intercepting Messages
メッセージを傍受するには、 register_on_chat_message を使用します。
minetest.register_on_chat_message(function(name, message) print(name .. " said " .. message) return false end)
false を返すことにより、チャットメッセージをデフォルトのハンドラーで送信できるようになります。 nil
は暗黙的に返され、 false のように扱われるため、実際には return false
の行を削除しても、同じように機能します。
⚠ 特権とチャットコマンド |
---|
プレーヤーがこのコールバックをトリガーするために、シャウト特権は必要ありません。これは、チャットコマンドが Lua に実装されており、 / で始まるチャットメッセージであるためです。 |
チャットコマンドである可能性があること、またはユーザーが「シャウト」を持っていない可能性があることを考慮に入れる必要があります。
minetest.register_on_chat_message(function(name, message) if message:sub(1, 1) == "/" then print(name .. " ran chat command") elseif minetest.check_player_privs(name, { shout = true }) then print(name .. " said " .. message) else print(name .. " tried to say " .. message .. " but doesn't have shout") end return false end)
13 - Chat Command Builder
Introduction
この章では、 ChatCmdBuilder を使用して、 /msg <name> <message>
、 /team join <teamname>
/team Leave <teamname>
などの複雑なチャットコマンドを作成する方法を説明します。
ChatCmdBuilder はこの本の著者によって作成されたライブラリであり、ほとんどの modder はチャットとコマンドで概説されている方法を使用する傾向があることに注意してください。
Why ChatCmdBuilder?
従来、mod は Lua パターンを使用してこれらの複雑なコマンドを実装していました。
local name = string.match(param, "^join ([%a%d_-]+)")
しかし、私は Lua パターンを書くのが面倒で読めないことに気づきました。このため、私はあなたのためにこれを行うためのライブラリを作成しました。
ChatCmdBuilder.new("sethp", function(cmd) cmd:sub(":target :hp:int", function(name, target, hp) local player = minetest.get_player_by_name(target) if player then player:set_hp(hp) return true, "Killed " .. target else return false, "Unable to find " .. target end end) end, { description = "Set hp of player", privs = { kick = true -- ^ probably better to register a custom priv } })
ChatCmdBuilder.new(name、setup_func、def)
は、 name
という新しいチャットコマンドを作成します。次に、渡された関数( setup_func
)を呼び出し、サブコマンドを作成します。各 cmd:sub(route、func)
はサブコマンドです。
サブコマンドは、入力パラメーターに対する特定の応答です。プレーヤーがチャットコマンドを実行すると、入力に一致する最初のサブコマンドが実行され、他のサブコマンドは実行されません。一致するサブコマンドがない場合、ユーザーには無効な構文が通知されます。たとえば、上記のコードスニペットでは、プレーヤーが /sethp username 12
の形式で何かを入力すると、 cmd:sub に渡された関数が呼び出されます。 /sethp 12 bleh
と入力すると、間違った入力メッセージが表示されます。
:name :hp:int
はルートです。 /teleport に渡されるパラメータのフォーマットを記述します。
Routes
ルートは、ターミナルと変数で構成されています。ターミナルは常にそこになければなりません。たとえば、 /team join :username :teamname
のjoin
です。スペースも端子としてカウントされます。
変数は、ユーザーの入力内容に応じて値を変更できます。たとえば、 :username
と :teamname
です。
変数は :name:type
として定義されます。 name
はヘルプドキュメントで使用されています。 type
は入力を照合するために使用されます。タイプが指定されていない場合、タイプは word
です。
有効なタイプは次のとおりです。
word
- default. Any string without spaces.int
- Any integer/whole number, no decimals.number
- Any number, including ints and decimals.pos
- 1,2,3 or 1.1,2,3.4567 or (1,2,3) or 1.2, 2 ,3.2text
- Any string. There can only ever be one text variable, no variables or terminals can come afterwards.
:name :hp:int
には、次の 2 つの変数があります。
name
- タイプが指定されていないため、word
のタイプ。スペースを含まない任意の文字列を受け入れます。hp
-int
のタイプ
Subcommand functions
最初の引数は発信者の名前です。次に、変数が順番に関数に渡されます。
cmd:sub(":target :hp:int", function(name, target, hp) -- subcommand function end)
Installing ChatCmdBuilder
ソースコードはGithubで探してダウンロードできます。
インストールには 2 つの方法があります。
- ChatCmdBuilder を mod としてインストールし、それに依存します。
- mod のchatcmdbuilder.lua として ChatCmdBuilder に init.lua ファイルを含め、それを dofile します。
Admin complex command
これを可能にするチャットコマンドを作成する例を次に示します。
/admin kill <username>>
- ユーザーを強制終了します/admin move <username> to <pos>
- テレポートユーザー/admin log <username>
- レポートログを表示/admin log <username> <message>
- レポートログへのログ
local admin_log local function load() admin_log = {} end local function save() -- todo end load() ChatCmdBuilder.new("admin", function(cmd) cmd:sub("kill :name", function(name, target) local player = minetest.get_player_by_name(target) if player then player:set_hp(0) return true, "Killed " .. target else return false, "Unable to find " .. target end end) cmd:sub("move :name to :pos:pos", function(name, target, pos) local player = minetest.get_player_by_name(target) if player then player:setpos(pos) return true, "Moved " .. target .. " to " .. minetest.pos_to_string(pos) else return false, "Unable to find " .. target end end) cmd:sub("log :username", function(name, target) local log = admin_log[target] if log then return true, table.concat(log, "\n") else return false, "No entries for " .. target end end) cmd:sub("log :username :message", function(name, target, message) local log = admin_log[target] or {} table.insert(log, message) admin_log[target] = log save() return true, "Logged" end) end, { description = "Admin tools", privs = { kick = true, ban = true } })
14 - Player Physics
Introduction
プレーヤーの物理は、物理オーバーライドを使用して変更できます。物理オーバーライドは、歩行速度、ジャンプ速度、および重力定数を設定できます。物理オーバーライドは、プレーヤーごとに設定され、乗数です。たとえば、重力の値が 2 の場合、重力は 2 倍強くなります。
Basic Example
呼び出し元を low G にする反重力コマンドを追加する方法の例を次に示します。
minetest.register_chatcommand("antigravity", { func = function(name, param) local player = minetest.get_player_by_name(name) player:set_physics_override({ gravity = 0.1, -- set gravity to 10% of its original value -- (0.1 * 9.81) }) end, })
Available Overrides
player:set_physics_override()
にはオーバーライドのテーブルが与えられます。
lua_api.txtによると、次のようになります。
- speed 速度: デフォルトの歩行速度値への乗数(デフォルト:1)
- jump ジャンプ: デフォルトのジャンプ値への乗数(デフォルト:1)
- gravity 重力: デフォルトの重力値への乗数(デフォルト:1)
- sneak: プレーヤーがこっそりできるかどうか(デフォルト:true)
Old Movement Behaviour
0.4.16 リリースより前のプレイヤーの動きには、スニークグリッチが含まれていました。これにより、特定のノードの配置から作成された「エレベーター」をスニーク(シフトを押す)やスペースを押して上昇するなど、さまざまな動きのグリッチが可能になります。この動作は意図したものではありませんが、多くのサーバーで使用されているため、オーバーライドで保持されています。
古い動きの動作を完全に復元するには、 2 つのオーバーライドが必要です。
- new_move: プレーヤーが新しい動きを使用するかどうか(デフォルト:true)
- sneak_glitch: プレーヤーが「スニークエレベーター」を使用できるかどうか(デフォルト:false)
Mod Incompatibility
プレイヤーの同じ物理値をオーバーライドする mod は、互いに互換性がない傾向があることに注意してください。オーバーライドを設定すると、以前に設定されたオーバーライドが上書きされます。これは、複数のオーバーライドがプレーヤーの速度を設定した場合、最後に実行されたものだけが有効になることを意味します。
Your Turn
- Sonic ソニック: プレーヤーがゲームに参加するときに、速度乗数を高い値(少なくとも6)に設定します。
- Super bounce スーパーバウンス: プレーヤーが 20 メートルジャンプできるようにジャンプ値を増やします( 1 メートルは 1 ノードです)。
- Space スペース: プレイヤーが高くなるにつれて重力を減少させます。
15 - GUIs (Formspecs)
Introduction
Screenshot of furnace formspec, labelled.
この章では、 formspec を作成してユーザーに表示する方法を学習します。 formspec は、フォームの仕様コードです。 Minetest では、フォームはプレーヤーインベントリなどのウィンドウであり、ラベル、ボタン、フィールドなどのさまざまな要素を含めることができます。
プレーヤーに情報を提供するだけでよい場合など、ユーザー入力を取得する必要がない場合は、formes ではなくヘッドアップディスプレイ(HUD)の要素の使用を検討する必要があることに注意してください。予期しないウィンドウはゲームプレイを混乱させる傾向があるためです。
- Real or Legacy Coordinates
- Anatomy of a Formspec
- Guessing Game
- Formspec Sources
Real or Legacy Coordinates
Minetest の古いバージョンでは、formspecs に一貫性がありませんでした。さまざまな要素が配置される方法は、予期しない方法で変化しました。要素の配置を予測して配置するのは困難でした。 Minetest 5.1.0 には、一貫した座標系を導入することでこれを修正することを目的とした実座標と呼ばれる機能が含まれています。実座標の使用を強くお勧めします。そのため、この章ではそれらを排他的に使用します。
Anatomy of a Formspec
Elements
Formspec は、通常とは異なる形式のドメイン固有言語です。これは、次の形式のいくつかの要素で構成されています。
type[param1;param2]
要素タイプが宣言され、パラメータは角括弧で囲まれています。次のように、複数の要素を結合したり、複数の行に配置したりできます。
foo[param1]bar[param1] bo[param1]
要素は、テキストボックスやボタンなどのアイテムであるか、サイズや背景などのメタデータにすることができます。考えられるすべての要素のリストについては、lua_api.txtを参照してください。
Header
formspec のヘッダーには、最初に表示する必要のある情報が含まれています。これには、 formspec のサイズ、位置、アンカー、およびゲーム全体のテーマを適用する必要があるかどうかが含まれます。
ヘッダーの要素は特定の順序で定義する必要があります。そうしないと、エラーが発生します。この順序は上記の段落で示され、いつものように、lua_api.txtに記載されています。
サイズは formspec スロットにあります。測定単位は約 64 ピクセルですが、画面密度とクライアントのスケーリング設定によって異なります。サイズが 2,2
の formspec は次のとおりです。
formspec_version[3] size[2,2]
formspec 言語バージョンを明示的に定義した方法に注目してください。これがないと、代わりにレガシーシステムが代わりに使用されます。これにより、一貫した要素の配置やその他の新しい機能を使用できなくなります。
位置要素とアンカー要素は、 formspec を画面に配置するために使用されます。位置は、 formspec が画面上のどこにあるかを設定し、デフォルトで中央( 0.5,0.5
)になります。アンカーは、 formspec 上の位置を設定し formspec を画面の端に揃えることができます。 formspec は、次のように画面の左側に配置できます。
formspec_version[3] size[2,2] real_coordinates[true] position[0,0.5] anchor[0,0.5]
これにより、アンカーが formspec ボックスの左中央の端に設定され、次にそのアンカーの位置が画面の左側に設定されます。
Guessing Game
The guessing game formspec.
学ぶための最良の方法は何かを作ることですので、推測ゲームを作りましょう。原理は単純です。 mod が数字を決定し、プレーヤーが数字を推測します。次に、 mod は、推測が実際の数値よりも高いか低いかを示します。
まず、formspec コードを作成する関数を作成しましょう。他の場所での再利用が容易になるため、これを行うことをお勧めします。
guessing = {} function guessing.get_formspec(name) -- TODO: display whether the last guess was higher or lower local text = "I'm thinking of a number... Make a guess!" local formspec = { "formspec_version[3]", "size[6,3.476]", "label[0.375,0.5;", minetest.formspec_escape(text), "]", "field[0.375,1.25;5.25,0.8;number;Number;]", "button[1.5,2.3;3,0.8;guess;Guess]" } -- table.concat is faster than string concatenation - `..` return table.concat(formspec, "") end
上記のコードでは、フィールド、ラベル、およびボタンを配置します。フィールドはテキスト入力を許可し、ボタンはフォームを送信するために使用されます。パディングと間隔を追加するために要素が慎重に配置されていることに気付くでしょう。これについては後で説明します。
次に、プレーヤーが formspec を表示できるようにします。これを行う主な方法は、 show_formspec
を使用することです。
function guessing.show_to(name) minetest.show_formspec(name, "guessing:game", guessing.get_formspec(name)) end minetest.register_chatcommand("game", { func = function(name) guessing.show_to(name) end, })
show_formspec
関数は、プレーヤー名、 formspec 名、および formspec 自体を受け入れます。 formspec 名は、有効な itemname 、つまり modname:itemname
の形式である必要があります。
Padding and Spacing
The guessing game formspec.
パディングは、 formspec のエッジとそのコンテンツの間、または関連のない要素の間のギャップであり、赤で示されています。間隔は、関連する要素間のギャップであり、青で示されています。
「 0.375 」のパディングと「 0.25 」の間隔を持つことはかなり標準的です。
Receiving Formspec Submissions
show_formspec
が呼び出されると、 formspec がクライアントに送信されて表示されます。 formspecs を使用するには、クライアントからサーバーに情報を返す必要があります。このためのメソッドは formspec フィールド送信と呼ばれ、 show_formspec
の場合、その送信はグローバルコールバックを使用して受信されます。
minetest.register_on_player_receive_fields(function(player, formname, fields) if formname ~= "guessing:game" then return end if fields.guess then local pname = player:get_player_name() minetest.chat_send_all(pname .. " guessed " .. fields.number) end end)
minetest.register_on_player_receive_fields
で指定された関数は、ユーザーがフォームを送信するたびに呼び出されます。ほとんどのコールバックは、関数に指定されたフォーム名を確認し、それが正しいフォームでない場合は終了する必要があります。ただし、一部のコールバックは、複数のフォームまたはすべてのフォームで機能する必要がある場合があります。
関数の fields
パラメータは、ユーザーが送信した値のテーブルであり、文字列でインデックスが付けられています。名前付き要素は、送信の原因となったイベントに関連する場合にのみ、独自の名前でフィールドに表示されます。たとえば、ボタン要素は、その特定のボタンが押された場合にのみフィールドに表示されます。
⚠ 悪意のあるクライアントはいつでも何でも送信できます |
---|
formspec の送信を信頼してはいけません。悪意のあるクライアントは、 formspec を表示したことがなくても、いつでも好きなものを送信できます。これは、特権をチェックし、それらがアクションの実行を許可されていることを確認する必要があることを意味します。 |
したがって、 formspec がクライアントに送信され、クライアントが情報を送り返します。次のステップは、何らかの方法でターゲット値を生成して記憶し、推測に基づいて formspec を更新することです。これを行う方法は、「コンテキスト」と呼ばれる概念を使用することです。
Contexts
多くの場合、 minetest.show_formspec で、クライアントに送信したくない情報をコールバックに提供する必要があります。これには、チャットコマンドが呼び出された対象や、ダイアログの内容が含まれる場合があります。この場合、覚えておく必要のある目標値。
コンテキストは、情報を格納するためのプレーヤーごとのテーブルであり、すべてのオンラインプレーヤーのコンテキストは、file-local 変数に格納されます。
local _contexts = {} local function get_context(name) local context = _contexts[name] or {} _contexts[name] = context return context end minetest.register_on_leaveplayer(function(player) _contexts[player:get_player_name()] = nil end)
次に、formspec を表示する前に、 show コードを変更してコンテキストを更新する必要があります。
function guessing.show_to(name) local context = get_context(name) context.target = context.target or math.random(1, 10) local fs = guessing.get_formspec(name, context) minetest.show_formspec(name, "guessing:game", fs) end
また、コンテキストを使用するように formspec 生成コードを変更する必要があります。
function guessing.get_formspec(name, context) local text if not context.guess then text = "I'm thinking of a number... Make a guess!" elseif context.guess == context.target then text = "Hurray, you got it!" elseif context.guess > context.target then text = "Too high!" else text = "Too low!" end
get_formspec
はコンテキストを読み取るだけで、まったく更新しないことをお勧めします。これにより、関数が単純になり、テストも簡単になります。
そして最後に、ハンドラーを更新して、推測でコンテキストを更新する必要があります。
if fields.guess then local name = player:get_player_name() local context = get_context(name) context.guess = tonumber(fields.number) guessing.show_to(name) end
Formspec Sources
formspec をクライアントに配信する方法は3つあります。
- show_formspec: 上記で使用した方法で、フィールドは
register_on_player_receive_fields
によって受信されます。 - ノードメタフォームスペック: ノードのメタデータにフォームスペックが含まれており、クライアントはプレーヤーが右クリックしたときそれをすぐに表示します。フィールドは、
on_receive_fields
と呼ばれるノード定義のメソッドによって受信されます。 - Player Inventory Formspecs: formspec はある時点でクライアントに送信され、プレーヤーが
i
を押すと表示されます。フィールドはregister_on_player_receive_fields
によって受信されます。
Node Meta Formspecs
minetest.show_formspec
は formspec を表示する唯一の方法ではありません。 ノードのメタデータに formspecs を追加することもできます。たとえば、これはチェストで使用され、開始時間を短縮できます。サーバーがプレーヤーにチェストのフォームスペックを送信するのを待つ必要はありません。
minetest.register_node("mymod:rightclick", { description = "Rightclick me!", tiles = {"mymod_rightclick.png"}, groups = {cracky = 1}, after_place_node = function(pos, placer) -- This function is run when the chest node is placed. -- The following code sets the formspec for chest. -- Meta is a way of storing data onto a node. local meta = minetest.get_meta(pos) meta:set_string("formspec", "formspec_version[3]" .. "size[5,5]" .. "label[1,1;This is shown on right click]" .. "field[1,2;2,1;x;x;]") end, on_receive_fields = function(pos, formname, fields, player) if fields.quit then return end print(fields.x) end })
このように設定された Formspecs は、同じコールバックをトリガーしません。 meta formspecs のフォーム入力を受け取るには、ノードを登録するときに on_receive_fields
エントリを含める必要があります。
このスタイルのコールバックは、フィールドで Enter キーを押すとトリガーされます。これは、 minetest.show_formspec
では不可能です。ただし、この種のフォームは、ノードを右クリックすることによってのみ表示できます。プログラムでトリガーすることはできません。
Player Inventory Formspecs
プレイヤーインベントリフォームスペックは、プレイヤーがiを押したときに表示されるものです。グローバルコールバックは、この formspec からイベントを受信するために使用され、フォーム名は ""`です。
複数の mod でプレーヤーのインベントリをカスタマイズできるようにするさまざまな mod がいくつかあります。公式に推奨される mod は SimpleFast Inventory(sfinv) で、 MinetestGame に含まれています。
Your Turn
- 推測ゲームを拡張して、各プレーヤーの最高スコアを追跡します。最高スコアは、推測の数です。
- ユーザーが formspec を開いてメッセージを残すことができる「受信ボックス」と呼ばれるノードを作成します。このノードは、配置者の名前を「所有者」としてメタに保存し、「 show_formspec 」を使用してさまざまな formspec をさまざまなプレーヤーに表示する必要があります。
16 - HUD
Introduction
ヘッドアップディスプレイ( HUD )要素を使用すると、テキスト、画像、およびその他のグラフィック要素を表示できます。
HUD はユーザー入力を受け入れません。そのためには、formspecを使用する必要があります。
- Positioning
- Text Elements
- Image Elements
- Changing an Element
- Storing IDs
- Other Elements
Positioning
Position and Offset
画面にはさまざまな物理的サイズと解像度があり、HUDはすべての画面タイプで適切に機能する必要があります。
これに対する Minetest の解決策は、パーセンテージ位置とオフセットの両方を使用して要素の位置を指定することです。パーセンテージの位置は画面サイズを基準にしているため、要素を画面の中央に配置するには、画面の半分のパーセンテージの位置を指定する必要があります。 (50%, 50%) 、および (0, 0) のオフセット。
次に、オフセットを使用して、パーセント位置を基準にして要素を移動します。
Alignment
位置合わせは、位置とオフセットの結果が要素上にある場所です。たとえば、 {x = -1.0, y = 0.0}
は、位置とオフセットの結果を要素の境界の左側に配置します。これは、テキスト要素を左、中央、または右に揃える場合に特に便利です。
上の図は、3つのウィンドウ(青)を示しています。各ウィンドウには、 1 つの HUD 要素(黄色)があり、毎回異なる配置になっています。矢印は、位置とオフセットの計算結果です。
Scoreboard
この章では、次のようにスコアパネルを配置および更新する方法を学習します。
上のスクリーンショットでは、すべての要素の位置が同じパーセンテージ (100%, 50%) ですが、オフセットが異なります。これにより、すべてをウィンドウの右側に固定できますが、サイズを変更することはできません。
Text Elements
プレーヤーオブジェクトのコピーを取得したら、 HUD 要素を作成できます。
local player = minetest.get_player_by_name("username") local idx = player:hud_add({ hud_elem_type = "text", position = {x = 0.5, y = 0.5}, offset = {x = 0, y = 0}, text = "Hello world!", alignment = {x = 0, y = 0}, -- center aligned scale = {x = 100, y = 100}, -- covered later })
hud_add
関数は要素 ID を返します - これは後で HUD 要素を変更または削除するために使用できます。
Parameters
要素のタイプは、定義テーブルの hud_elem_type
プロパティを使用して指定されます。他のプロパティの意味は、このタイプによって異なります。
scale
はテキストの最大境界です。これらの境界外のテキストはトリミングされます。例: {x=100, y=100}
number
はテキストの色であり、16進形式です。例:0xFF0000
Our Example
先に進んで、すべてのテキストをスコアパネルに配置しましょう。
-- Get the dig and place count from storage, or default to 0 local meta = player:get_meta() local digs_text = "Digs: " .. meta:get_int("score:digs") local places_text = "Places: " .. meta:get_int("score:places") player:hud_add({ hud_elem_type = "text", position = {x = 1, y = 0.5}, offset = {x = -120, y = -25}, text = "Stats", alignment = 0, scale = { x = 100, y = 30}, number = 0xFFFFFF, }) player:hud_add({ hud_elem_type = "text", position = {x = 1, y = 0.5}, offset = {x = -180, y = 0}, text = digs_text, alignment = -1, scale = { x = 50, y = 10}, number = 0xFFFFFF, }) player:hud_add({ hud_elem_type = "text", position = {x = 1, y = 0.5}, offset = {x = -70, y = 0}, text = places_text, alignment = -1, scale = { x = 50, y = 10}, number = 0xFFFFFF, })
これにより、次のようになります。
Image Elements
画像要素は、テキスト要素と非常によく似た方法で作成されます。
player:hud_add({ hud_elem_type = "image", position = {x = 1, y = 0.5}, offset = {x = -220, y = 0}, text = "score_background.png", scale = { x = 1, y = 1}, alignment = { x = 1, y = 0 }, })
これで、次のようになります。
Parameters
text
フィールドは画像名を提供するために使用されます。
座標が正の場合、それはスケールファクターであり、 1 は元の画像サイズ、2 は 2 倍のサイズというようになります。ただし、座標が負の場合は、画面サイズのパーセンテージです。たとえば、 x = -100
は幅の 100% です。
Scale
スケールの例として、スコアパネルのプログレスバーを作成しましょう。
local percent = tonumber(meta:get("score:score") or 0.2) player:hud_add({ hud_elem_type = "image", position = {x = 1, y = 0.5}, offset = {x = -215, y = 23}, text = "score_bar_empty.png", scale = { x = 1, y = 1}, alignment = { x = 1, y = 0 }, }) player:hud_add({ hud_elem_type = "image", position = {x = 1, y = 0.5}, offset = {x = -215, y = 23}, text = "score_bar_full.png", scale = { x = percent, y = 1}, alignment = { x = 1, y = 0 }, })
これで、最初の投稿のような HUD ができました。ただし、問題が 1 つありますが、統計が変更されても更新されません。
Changing an Element
hud_add
メソッドによって返された ID を使用して、 ID を更新したり、後で削除したりできます。
local idx = player:hud_add({ hud_elem_type = "text", text = "Hello world!", -- parameters removed for brevity }) player:hud_change(idx, "text", "New Text") player:hud_remove(idx)
hud_change
メソッドは、要素 ID、変更するプロパティ、および新しい値を受け取ります。上記の呼び出しにより、 text
プロパティが「 HelloWorld 」から「 Newtext 」に変更されます。
これは、 hud_add
の直後に hud_change
を実行することは、機能的には以下と同等であり、かなり非効率的な方法であることを意味します。
local idx = player:hud_add({ hud_elem_type = "text", text = "New Text", })
Storing IDs
score = {} local saved_huds = {} function score.update_hud(player) local player_name = player:get_player_name() -- Get the dig and place count from storage, or default to 0 local meta = player:get_meta() local digs_text = "Digs: " .. meta:get_int("score:digs") local places_text = "Places: " .. meta:get_int("score:places") local percent = tonumber(meta:get("score:score") or 0.2) local ids = saved_huds[player_name] if ids then player:hud_change(ids["places"], "text", places_text) player:hud_change(ids["digs"], "text", digs_text) player:hud_change(ids["bar_foreground"], "scale", { x = percent, y = 1 }) else ids = {} saved_huds[player_name] = ids -- create HUD elements and set ids into `ids` end end minetest.register_on_joinplayer(score.update_hud) minetest.register_on_leaveplayer(function(player) saved_huds[player:get_player_name()] = nil end)
Other Elements
HUD 要素の完全なリストについては、lua_api.txtをお読みください。
17 - SFINV: Inventory Formspec
Introduction
Simple Fast Inventory(SFINV)は、 Minetest Game にある mod で、プレーヤーのインベントリ formspec を作成するために使用されます。 SFINV には、表示されているページを追加または管理できる API が付属しています。
SFINV はデフォルトでページをタブとして表示しますが、 mod またはゲームが代わりに他の形式でページを表示することを決定する可能性があるため、ページはページと呼ばれます。たとえば、複数のページを 1 つの formspec に表示できます。
- Registering a Page
- Receiving events
- Conditionally showing to players
- on_enter and on_leave callbacks
- Adding to an existing page
Registering a Page
SFINV は、ページを作成するための適切な名前の sfinv.register_page
関数を提供します。ページの名前とその定義を使用して関数を呼び出すだけです。
sfinv.register_page("mymod:hello", { title = "Hello!", get = function(self, player, context) return sfinv.make_formspec(player, context, "label[0.1,0.1;Hello world!]", true) end })
make_formspec
関数は、 formspec を SFINV の formspec コードで囲みます。現在「 true 」に設定されている 4 番目のパラメータは、プレーヤーのインベントリを表示するかどうかを決定します。
物事をもっとエキサイティングにしましょう。これは、プレーヤー管理タブの formspec 生成部分のコードです。このタブでは、管理者がリストでプレーヤーを選択してボタンをクリックすることで、プレーヤーをキックまたは禁止することができます。
sfinv.register_page("myadmin:myadmin", { title = "Tab", get = function(self, player, context) local players = {} context.myadmin_players = players -- Using an array to build a formspec is considerably faster local formspec = { "textlist[0.1,0.1;7.8,3;playerlist;" } -- Add all players to the text list, and to the players list local is_first = true for _ , player in pairs(minetest.get_connected_players()) do local player_name = player:get_player_name() players[#players + 1] = player_name if not is_first then formspec[#formspec + 1] = "," end formspec[#formspec + 1] = minetest.formspec_escape(player_name) is_first = false end formspec[#formspec + 1] = "]" -- Add buttons formspec[#formspec + 1] = "button[0.1,3.3;2,1;kick;Kick]" formspec[#formspec + 1] = "button[2.1,3.3;2,1;ban;Kick + Ban]" -- Wrap the formspec in sfinv's layout -- (ie: adds the tabs and background) return sfinv.make_formspec(player, context, table.concat(formspec, ""), false) end, })
上記のコードについては何も新しいことはありません。すべての概念は、上記および前の章で説明されています。
Receiving events
sfinv 定義に on_player_receive_fields
関数を追加することで、 formspec イベントを受け取ることができます。
on_player_receive_fields = function(self, player, context, fields) -- TODO: implement this end,
on_player_receive_fields
は minetest.register_on_player_receive_fields
と同じように機能しますが、 formname
の代わりに context
が指定されている点が異なります。 SFINV は、ナビゲーションタブイベントなど、 SFINV 自体に関連するイベントを消費するため、このコールバックではそれらを受信しないことに注意してください。
それでは、 adminmod に on_player_receive_fields
を実装しましょう。
on_player_receive_fields = function(self, player, context, fields) -- text list event, check event type and set index if selection changed if fields.playerlist then local event = minetest.explode_textlist_event(fields.playerlist) if event.type == "CHG" then context.myadmin_selected_idx = event.index end -- Kick button was pressed elseif fields.kick then local player_name = context.myadmin_players[context.myadmin_selected_idx] if player_name then minetest.chat_send_player(player:get_player_name(), "Kicked " .. player_name) minetest.kick_player(player_name) end -- Ban button was pressed elseif fields.ban then local player_name = context.myadmin_players[context.myadmin_selected_idx] if player_name then minetest.chat_send_player(player:get_player_name(), "Banned " .. player_name) minetest.ban_player(player_name) minetest.kick_player(player_name, "Banned") end end end,
ただし、これにはかなり大きな問題があります。誰でもプレイヤーを蹴ったり禁止したりできます!キックまたはバンの特権を持つプレイヤーにのみこれを表示する方法が必要です。幸いなことに、 SFINV を使用するとこれを実行できます。
Conditionally showing to players
ページがいつ表示されるかを制御したい場合は、ページの定義に is_in_nav
関数を追加できます。
is_in_nav = function(self, player, context) local privs = minetest.get_player_privs(player:get_player_name()) return privs.kick or privs.ban end,
1つの priv のみをチェックする必要がある場合、または「 and 」を実行する場合は、 get_player_privs
の代わりに minetest.check_player_privs()
を使用する必要があります。
is_in_nav
は、プレーヤーのインベントリフォームスペックが生成されたときにのみ呼び出されることに注意してください。これは、プレーヤーがゲームに参加したとき、タブを切り替えたとき、または mod が SFINV の再生成を要求したときに発生します。
つまり、 is_in_nav
の結果を変更する可能性のあるイベントについて、 SFINV がインベントリフォームスペックを再生成するように手動で要求する必要があります。私たちの場合、キックまたは禁止がプレーヤーに付与または取り消されるたびに、それを行う必要があります。
local function on_grant_revoke(grantee, granter, priv) if priv ~= "kick" and priv ~= "ban" then return end local player = minetest.get_player_by_name(grantee) if not player then return end local context = sfinv.get_or_create_context(player) if context.page ~= "myadmin:myadmin" then return end sfinv.set_player_inventory_formspec(player, context) end minetest.register_on_priv_grant(on_grant_revoke) minetest.register_on_priv_revoke(on_grant_revoke)
on_enter and on_leave callbacks
プレーヤーは、タブが選択されたときにタブに入り、別のタブが選択されようとしているときにタブから離れます。カスタムテーマを使用すると、複数のページを選択できる可能性があります。
これらのイベントは、プレーヤーによってトリガーされない場合があることに注意してください。その時点では、プレーヤーは formspec を開いていない可能性があります。たとえば、プレーヤーがインベントリを開く前でもゲームに参加すると、ホームページに対して on_enter が呼び出されます。
プレーヤーを混乱させる可能性があるため、ページの変更をキャンセルすることはできません。
on_enter = function(self, player, context) end, on_leave = function(self, player, context) end,
Adding to an existing page
既存のページにコンテンツを追加するには、ページをオーバーライドして、返された formspec を変更する必要があります。
local old_func = sfinv.registered_pages["sfinv:crafting"].get sfinv.override_page("sfinv:crafting", { get = function(self, player, context, ...) local ret = old_func(self, player, context, ...) if type(ret) == "table" then ret.formspec = ret.formspec .. "label[0,0;Hello]" else -- Backwards compatibility ret = ret .. "label[0,0;Hello]" end return ret end })
18 - Biomes and Decorations
Introduction
興味深く多様なゲーム内環境を作成することを目指す場合、バイオームと装飾を登録する機能は不可欠です。この章では、バイオームを登録する方法、バイオームの分布を制御する方法、およびバイオームに装飾を配置する方法について説明します。
- What are Biomes?
- Biome Placement
- Registering a Biome
- What are Decorations?
- Registering a Simple Decoration
- Registering a Schematic Decoration
- Mapgen Aliases
What are Biomes?
Minetest バイオームは、特定のゲーム内環境です。バイオームを登録するときに、マップ生成中にバイオームに表示されるノードのタイプを判別できます。バイオーム間で異なる可能性のある最も一般的なタイプのノードには、次のものがあります。
- トップノード: これは、サーフェス上で最も一般的に見られるノードです。よく知られている例は、 MinetestGame の「 Dirt with Grass 」です。
- フィラーノード: これは、最上位ノードのすぐ下のレイヤーです。草のあるバイオームでは、それはしばしば汚れになります。
- ストーンノード: これは、地下で最もよく見られるノードです。
水ノード: これは通常液体であり、水域が予想される場所に表示されるノードになります。
Top node: This is the node most commonly found on the surface. A well-known example would be “Dirt with Grass” from Minetest Game.
- Filler node: This is the layer immediately beneath the top node. In biomes with grass, it will often be dirt.
- Stone node: This is the node you most commonly see underground.
- Water node: This is usually a liquid and will be the node that appears where you would expect bodies of water.
他のタイプのノードもバイオーム間で異なる可能性があり、同じゲーム内で非常に異なる環境を作成する機会を提供します。
Biome Placement
Heat and Humidity
バイオームを登録するだけでは十分ではありません。また、ゲーム内のどこで発生するかを決定する必要があります。これは、各バイオームに熱と湿度の値を割り当てることによって行われます。
これらの値について慎重に検討する必要があります。それらは、どのバイオームが互いに隣接できるかを決定します。決定が不十分だと、氷河と国境を接する暑い砂漠となることを意味するものや、避けたいと思われる他のありそうもない組み合わせが生じる可能性があります。
ゲームでは、マップの任意のポイントでの熱と湿度の値は通常 0 から 100 の間です。値は徐々に変化し、マップ内を移動するにつれて増加または減少します。任意の時点でのバイオームは、登録されたバイオームのどれがマップ上のその位置にあるものに最も近い熱と湿度の値を持っているかによって決定されます。
熱と湿度の変化は緩やかであるため、バイオームの環境に関する合理的な期待に基づいて、バイオームに熱と湿度の値を割り当てることをお勧めします。例えば:
- 砂漠は高温多湿である可能性があります。
- 雪に覆われた森は、熱が低く、湿度が中程度の場合があります。
- 沼地バイオームは一般的に湿度が高いでしょう。 * 実際には、これは、多様な範囲のバイオームがある限り、互いに隣接するバイオームが論理的な進行を形成することに気付く可能性が高いことを意味します。
Visualising Boundaries using Voronoi Diagrams
Voronoi diagram, showing the closest point.By Balu Ertl, CC BY-SA 4.0.
使用しているバイオーム間の関係を視覚化できれば、バイオームの熱と湿度の値を微調整するのは簡単です。これは、独自のバイオームのフルセットを作成する場合に最も重要ですが、既存のセットにバイオームを追加する場合にも役立ちます。
どのバイオームが境界を共有するかを視覚化する最も簡単な方法は、ボロノイ図を作成することです。これを使用して、任意の位置が2次元図のどの点に最も近いかを示すことができます。
ボロノイ図は、互いに隣接する必要のあるバイオームが存在しない場所と、互いに隣接するべきではないバイオームが存在する場所を明らかにすることができます。また、一般的なバイオームがゲーム内でどのように機能するかについての一般的な洞察を与えることもできます。図の外縁にある小さなバイオームやバイオームよりも、大きくて中央のバイオームの方が一般的です。
これは、熱と湿度の値に基づいて各バイオームのポイントをマークすることによって行われます。ここで、x軸は熱で、y軸は湿度です。次に、ダイアグラムはエリアに分割され、特定のエリア内のすべての位置が、ダイアグラム上の他のポイントよりもそのエリア内のポイントに近くなります。
各領域はバイオームを表します。 2つのエリアが境界を共有している場合、ゲーム内でそれらが表すバイオームを隣り合わせに配置できます。他の領域と共有される長さと比較した、2つの領域間で共有される境界の長さは、2つのバイオームが互いに隣接して見つかる可能性が高い頻度を示します。
Creating a Voronoi Diagram using Geogebra
手で描くだけでなく、Geogebraなどのプログラムを使ってボロノイ図を作成することもできます。
ツールバーのポイントツール(アイコンは「 A 」の付いたポイント)を選択し、チャートをクリックしてポイントを作成します。ポイントをドラッグしたり、左側のサイドバーで明示的に位置を設定したりできます。また、物事を明確にするために、各ポイントにラベルを付ける必要があります。
次に、左側のサイドバーの入力ボックスに次の関数を入力して、ボロノイを作成します。
Lua Voronoi({ A, B, C, D, E })
各ポイントが中括弧の内側にあり、コンマで区切られている場合。あなたは今すべきです。
やったぁ!これで、ドラッグ可能なすべてのポイントを含むボロノイ図が作成されます。
Registering a Biome
次のコードは、grasslandsbiome という名前の単純なバイオームを登録します。
minetest.register_biome({ name = "grasslands", node_top = "default:dirt_with_grass", depth_top = 1, node_filler = "default:dirt", depth_filler = 3, y_max = 1000, y_min = -3, heat_point = 50, humidity_point = 50, })
このバイオームには、表面に草のノードがある1層の土と、その下に 3 層の土のノードがあります。ストーンノードを指定していないため、 mapgen_stone
の mapgen エイリアス登録で定義されたノードがダートの下に存在します。
バイオームを登録する際には多くのオプションがあり、これらはいつものようにMinetest Lua APIリファレンスに記載されています。
作成するすべてのバイオームに対してすべてのオプションを定義する必要はありませんが、特定のオプションまたは適切な mapgen エイリアスのいずれかを定義しないと、マップ生成エラーが発生する場合があります。
What are Decorations?
デコレーションは、 mapgen のマップに配置できるノードまたはスケマチックのいずれかです。一般的な例としては、花、低木、樹木などがあります。他のより創造的な用途には、洞窟につららや石筍をぶら下げたり、地下の結晶を形成したり、小さな建物を配置したりすることもあります。
装飾は、特定のバイオーム、高さ、またはそれらを配置できるノードに制限できます。それらは、特定の植物、樹木、またはその他の特徴を確実に持つことによって、バイオームの環境を開発するためによく使用されます。
Registering a Simple Decoration
単純な装飾は、マップ生成中にマップ上に単一ノードの装飾を配置するために使用されます。デコレーションとして配置するノード、配置できる場所の詳細、および発生頻度を指定する必要があります。
例:
minetest.register_decoration({ deco_type = "simple", place_on = {"base:dirt_with_grass"}, sidelen = 16, fill_ratio = 0.1, biomes = {"grassy_plains"}, y_max = 200, y_min = 1, decoration = "plants:grass", })
この例では、 plants:grass
という名前のノードは、base:dirt_with_grass
ノードの上にある grassy_plains という名前のバイオームに、高さ y = 1
とy = 200
の間に配置されます。
fill_ratio 値は、装飾が表示される頻度を決定します。値を 1 まで大きくすると、多数の装飾が配置されます。代わりに、ノイズパラメータを使用して配置を決定することができます。
Registering a Schematic Decoration
スケマチックの装飾は単純な装飾と非常に似ていますが、単一のノードの配置ではなく、スケマチックの配置が含まれます。例えば: Schematic decorations are very similar to simple decoration, but involve the placement of a schematic instead of the placement of a single node. For example:
minetest.register_decoration({ deco_type = "schematic", place_on = {"base:desert_sand"}, sidelen = 16, fill_ratio = 0.0001, biomes = {"desert"}, y_max = 200, y_min = 1, schematic = minetest.get_modpath("plants") .. "/schematics/cactus.mts", flags = "place_center_x, place_center_z", rotation = "random", })
この例では、cactus.mts スケマチックが砂漠のバイオームに配置されています。スケマチックへのパスを指定する必要があります。この場合、このパスは mod 内の専用のスケマチックディレクトリに保存されます。
この例では、スケマチックの配置を中央に配置するフラグも設定し、回転はランダムに設定されています。スケマチックを装飾として配置するときのランダムな回転は、非対称のスケマチックを使用するときに、より多くのバリエーションを導入するのに役立ちます。
Mapgen Aliases
既存のゲームにはすでに適切な mapgen エイリアスが含まれているはずなので、独自のゲームを作成する場合は、独自の mapgen エイリアスの登録を検討するだけで済みます。
Mapgen エイリアスは、コア mapgen に情報を提供し、次の形式で登録できます。
minetest.register_alias("mapgen_stone", "base:smoke_stone")
少なくとも、登録する必要があります。
- mapgen_stone
- mapgen_water_source
- mapgen_river_water_source
すべてのバイオームに対して洞窟液体ノード( cave liquid nodes )を定義していない場合は、以下も登録する必要があります。
- mapgen_lava_source
19 - Lua Voxel Manipulators
Introduction
Basic Map Operationの章で概説されている関数は、便利で使いやすいですが、広い領域では非効率的です。 set_node
または get_node
を呼び出すたびに、mod はエンジンと通信する必要があります。これにより、エンジンと mod の間で一定の個別のコピー操作が発生し、速度が低下し、ゲームのパフォーマンスが急速に低下します。 Lua ボクセルマニピュレーター( LVM )を使用することはより良い代替手段です。
Concepts
LVM を使用すると、マップの広い領域を mod のメモリにロードできます。その後、エンジンとの対話やコールバックを実行せずに、このデータの読み取りと書き込みを行うことができます。つまり、これらの操作は非常に高速です。完了したら、その領域をエンジンに書き戻し、照明の計算を実行できます。
Reading into the LVM
LVM にロードできるのは立方体領域のみであるため、変更する必要のある最小位置と最大位置を計算する必要があります。次に、LVM を作成して読み込むことができます。例えば:
local vm = minetest.get_voxel_manip() local emin, emax = vm:read_from_map(pos1, pos2)
パフォーマンス上の理由から、LVM は指示された正確な領域を読み取ることはほとんどありません。代わりに、より広い領域を読み取る可能性があります。より大きな領域は emin
と emax
で与えられ、これらは emerged min pos と emerged max pos を表しています。 LVM は、メモリからのロード、ディスクからのロード、マップジェネレーターの呼び出しなど、LVM に含まれる領域をロードします。
⚠ LVMとMapgen |
---|
グリッチを引き起こす可能性があるため、 mapgen で minetest.get_voxel_manip() を使用しないでください。代わりに minetest.get_mapgen_object("voxelmanip") を使用してください。 |
Reading Nodes
特定の位置にあるノードのタイプを読み取るには、 get_data()
を使用する必要があります。これは、各エントリが特定のノードのタイプを表すフラット配列を返します。
local data = vm:get_data()
メソッド get_light_data()
と get_param2_data()
を使用して、 param2 とライティングデータを取得できます。
上記のメソッドで指定されたフラット配列のどこにノードがあるかを調べるには、 emin
と emax
を使用する必要があります。計算を処理する VoxelArea
というヘルパークラスがあります。
local a = VoxelArea:new{ MinEdge = emin, MaxEdge = emax } -- Get node's index local idx = a:index(x, y, z) -- Read node print(data[idx])
これを実行すると、 data [vi]
が整数であることがわかります。これは、パフォーマンス上の理由から、エンジンが文字列を使用してノードを保存しないためです。代わりに、エンジンはコンテンツ ID と呼ばれる整数を使用します。 get_content_id()
を使用して、特定のタイプのノードのコンテンツ ID を確認できます。例えば:
local c_stone = minetest.get_content_id("default:stone")
次に、ノードが石であるかどうかを確認できます。
local idx = a:index(x, y, z) if data[idx] == c_stone then print("is stone!") end
ノードタイプの ID は変更されないため、ロード時にノードタイプのコンテンツ ID を見つけて保存することをお勧めします。パフォーマンス上の理由から、 ID は必ずローカル変数に格納してください。
LVM データ配列内のノードは逆座標の順序で格納されるため、常に z, y, x
の順序で反復する必要があります。例えば:
for z = min.z, max.z do for y = min.y, max.y do for x = min.x, max.x do -- vi, voxel index, is a common variable name here local vi = a:index(x, y, z) if data[vi] == c_stone then print("is stone!") end end end end
この理由は、コンピュータアーキテクチャのトピックに触れています。 RAM からの読み取りはかなりコストがかかるため、CPU には複数レベルのキャッシュがあります。プロセスが要求するデータがキャッシュにある場合、プロセスはそれを非常に迅速に取得できます。データがキャッシュにない場合、キャッシュミスが発生し、 RAM から必要なデータをフェッチします。要求されたデータを取り巻くデータもフェッチされ、キャッシュ内のデータが置き換えられます。これは、プロセスがその場所の近くのデータを再度要求する可能性が非常に高いためです。つまり、最適化の適切なルールは、データを次々に読み取る方法で反復し、キャッシュスラッシングを回避することです。
Writing Nodes
まず、データ配列に新しいコンテンツIDを設定する必要があります。
for z = min.z, max.z do for y = min.y, max.y do for x = min.x, max.x do local vi = a:index(x, y, z) if data[vi] == c_stone then data[vi] = c_air end end end end
LVM でノードの設定が完了したら、データ配列をエンジンにアップロードする必要があります。
vm:set_data(data) vm:write_to_map(true)
ライティングと param2 データを設定するには、適切な名前の set_light_data()
メソッドと set_param2_data()
メソッドを使用します。
write_to_map()
はブール値を取ります。これは、照明を計算する場合に当てはまります。 false を渡した場合は、後で minetest.fix_light
を使用して照明を再計算する必要があります。
Example
-- Get content IDs during load time, and store into a local local c_dirt = minetest.get_content_id("default:dirt") local c_grass = minetest.get_content_id("default:dirt_with_grass") local function grass_to_dirt(pos1, pos2) -- Read data into LVM local vm = minetest.get_voxel_manip() local emin, emax = vm:read_from_map(pos1, pos2) local a = VoxelArea:new{ MinEdge = emin, MaxEdge = emax } local data = vm:get_data() -- Modify data for z = pos1.z, pos2.z do for y = pos1.y, pos2.y do for x = pos1.x, pos2.x do local vi = a:index(x, y, z) if data[vi] == c_grass then data[vi] = c_dirt end end end end -- Write data vm:set_data(data) vm:write_to_map(true) end
Your Turn
replace_in_area(from, to, pos1, pos2)
を作成します。これにより、指定された領域でfrom
のすべてのインスタンスがto
に置き換えられます。ここで、from
とto
はノード名です。- すべての胸節を90°回転させる関数を作成します。
- LVM を使用して、苔むした丸石を近くの石や丸石のノードに広げる関数を作成します。あなたの実装は苔むした丸石を毎回 1 ノード分の距離以上に広げる原因になりますか?もしそうなら、どうすればこれを止めることができますか?
20 - Creating Games
Introduction
Minetest の力は、独自のボクセルグラフィックス、ボクセルアルゴリズム、または派手なネットワークコードを作成しなくても、ゲームを簡単に開発できることです。
- What is a Game?
- Game Directory
- Inter-game Compatibility
- Your Turn
What is a Game?
ゲームは、連携してまとまりのあるゲームを作成する mod のコレクションです。優れたゲームには、一貫した基本的なテーマと方向性があります。たとえば、サバイバル要素が難しい古典的なクラフターマイナーの場合もあれば、スチームパンクな自動化の美学を備えたスペースシミュレーションゲームの場合もあります。
ゲームデザインは複雑なトピックであり、実際には専門分野全体です。簡単に触れるだけでも、本の範囲を超えています。 Game design is a complex topic and is actually a whole field of expertise. It’s beyond the scope of the book to more than briefly touch on it.
Game Directory
ゲームの構造とロケーションは、mod を使用した後はかなり馴染みがあるように見えます。ゲームは、 minetest/games/foo_game
などのゲームのロケーションにあります。
foo_game ├── game.conf ├── menu │ ├── header.png │ ├── background.png │ └── icon.png ├── minetest.conf ├── mods │ └── ... mods ├── README.txt └── settingtypes.txt
必要なのは mods フォルダーだけですが、 game.conf
と menu/icon.png
をお勧めします。
Inter-game Compatibility
API Compatibility
mod と別のゲームへの移植がより簡単になるため、 Minetest Game との API の互換性をできるだけ便利に保つようにすることをお勧めします。
別のゲームとの互換性を維持するための最良の方法は、同じ名前の mod との API の互換性を維持することです。つまり、 mod が別の mod と同じ名前を使用している場合、サードパーティであっても、互換性のある API が必要です。たとえば、ゲームに「 doors 」という mod が含まれている場合、 Minetest Game の「 doors 」と同じ API が必要です。
mod の API 互換性は、次の合計です。
- Lua API テーブル - 同じ名前を共有するグローバルテーブル内のすべての文書化/アドバタイズされた関数。たとえば、
mobs.register_mob
です。 - 登録されたノード/アイテム - アイテムの存在。
小さな破損は、実際には内部でのみ使用されるランダムなユーティリティ関数がないなど、それほど問題ありませんが、コア機能に関連する大きな破損はとてもよくないです。
Minetest Game の default のような嫌なメガ God-mod との API 互換性を維持することは困難です。その場合、ゲームに default という名前の mod を含めるべきではありません。
API の互換性は、他のサードパーティの mod やゲームにも適用されるため、新しい mod には一意の mod 名が付いていることを確認してください。 mod 名が使用されているかどうかを確認するには、content.minetest.netで mod 名を検索してください。
Groups and Aliases
グループとエイリアスはどちらも、ゲーム間の互換性を維持するのに役立つツールです。これにより、ゲームごとにアイテム名を変えることができます。石や木のような一般的なノードには、材料を示すグループが必要です。デフォルトノードから直接置換するエイリアスを提供することもお勧めします。
Your Turn
- プレイヤーが特別なブロックを掘ることでポイントを獲得する簡単なゲームを作成します。
21 - Common Mistakes
Introduction
この章では、よくある間違いとその回避方法について詳しく説明します。
- Never Store ObjectRefs (ie: players or entities)
- Don’t Trust Formspec Submissions
- Set ItemStacks After Changing Them
Never Store ObjectRefs (ie: players or entities)
ObjectRef が表すオブジェクトが削除された場合(たとえば、プレーヤーがオフラインになったり、エンティティがアンロードされたりした場合)、そのオブジェクトのメソッドを呼び出すとクラッシュします。
たとえば、こうしないでください。
minetest.register_on_joinplayer(function(player) local function func() local pos = player:get_pos() -- BAD! -- `player` is stored then accessed later. -- If the player leaves in that second, the server *will* crash. end minetest.after(1, func) foobar[player:get_player_name()] = player -- RISKY -- It's not recommended to do this. -- Use minetest.get_connected_players() and -- minetest.get_player_by_name() instead. end)
代わりにこうしてください:
minetest.register_on_joinplayer(function(player) local function func(name) -- Attempt to get the ref again local player = minetest.get_player_by_name(name) -- Check that the player is still online if player then -- Yay! This is fine local pos = player:get_pos() end end -- Pass the name into the function minetest.after(1, func, player:get_player_name()) end)
Don’t Trust Formspec Submissions
悪意のあるクライアントは、好きなときに好きなコンテンツでフォームスペックを送信できます。
たとえば、次のコードには、プレーヤーが自分自身にモデレーター特権を与えることができる脆弱性があります。
local function show_formspec(name) if not minetest.check_player_privs(name, { privs = true }) then return false end minetest.show_formspec(name, "modman:modman", [[ size[3,2] field[0,0;3,1;target;Name;] button_exit[0,1;3,1;sub;Promote] ]]) return true }) minetest.register_on_player_receive_fields(function(player, formname, fields) -- BAD! Missing privilege check here! local privs = minetest.get_player_privs(fields.target) privs.kick = true privs.ban = true minetest.set_player_privs(fields.target, privs) return true end)
これを解決するために特権チェックを追加します。
minetest.register_on_player_receive_fields(function(player, formname, fields) if not minetest.check_player_privs(name, { privs = true }) then return false end -- code end)
Set ItemStacks After Changing Them
「 InvRef 」のように、「 ItemStackRef 」ではなく、API では単に「 ItemStack 」と呼ばれていることに気づきましたか?これは、 ItemStack
が参照ではなくコピーであるためです。スタックは、インベントリ内のスタックではなく、データのコピーで機能します。つまり、スタックを変更しても、インベントリ内のそのスタックは実際には変更されません。
たとえば、これを行わないでください。
local inv = player:get_inventory() local stack = inv:get_stack("main", 1) stack:get_meta():set_string("description", "Partially eaten") -- BAD! Modification will be lost
代わりにこれを行ってください:
local inv = player:get_inventory() local stack = inv:get_stack("main", 1) stack:get_meta():set_string("description", "Partially eaten") inv:set_stack("main", 1, stack) -- Correct! Item stack is set
コールバックの動作は少し複雑です。与えられた ItemStack
を変更すると、呼び出し元とその後のコールバックでも変更されます。ただし、コールバックの呼び出し元が設定した場合にのみ、エンジンに保存されます。
minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing) itemstack:get_meta():set_string("description", "Partially eaten") -- Almost correct! Data will be lost if another -- callback cancels the behaviour end)
コールバックがこれをキャンセルしない場合、スタックが設定され、説明が更新されますが、コールバックがこれをキャンセルすると、更新が失われる可能性があります。
代わりにこれを行うことをお勧めします。
minetest.register_on_item_eat(function(hp_change, replace_with_item, itemstack, user, pointed_thing) itemstack:get_meta():set_string("description", "Partially eaten") user:get_inventory():set_stack("main", user:get_wield_index(), itemstack) -- Correct, description will always be set! end)
コールバックがキャンセルされるか、コールバックランナーがスタックを設定しない場合でも、更新は設定されます。コールバックまたはコールバックランナーがスタックを設定する場合、 set_stack の使用は重要ではありません。
22 - Automatic Error Checking
Introduction
この章では、LuaCheck と呼ばれるツールを使用して、間違いがないか mod を自動的にスキャンする方法を学習します。このツールをエディターと組み合わせて使用すると、間違いを警告できます。
- Installing LuaCheck
- Running LuaCheck
- Configuring LuaCheck
- Using with editor
- Checking Commits with Travis
Installing LuaCheck
Windows
Githubリリースページから luacheck.exe をダウンロードするだけです。
Linux
まず、LuaRocks をインストールする必要があります。
sudo apt install luarocks
その後、LuaCheck をグローバルにインストールできます。
sudo luarocks install luacheck
次のコマンドでインストールされていることを確認します。
luacheck -v
Running LuaCheck
LuaCheck を初めて実行すると、多くの誤ったエラーが発生する可能性があります。これは、まだ構成する必要があるためです。
Windows では、プロジェクトのルートフォルダーにある powershell または bash を開き、 path\to\luacheck.exe .
を実行します。
Linux では、プロジェクトのルートフォルダで luacheck.
を実行します。
Configuring LuaCheck
プロジェクトのルートに .luacheckrc というファイルを作成します。これは、ゲーム、 modpack 、または mod のルートである可能性があります。
その中に次の内容を入れてください:
unused_args = false allow_defined_top = true globals = { "minetest", } read_globals = { string = {fields = {"split"}}, table = {fields = {"copy", "getn"}}, -- Builtin "vector", "ItemStack", "dump", "DIR_DELIM", "VoxelArea", "Settings", -- MTG "default", "sfinv", "creative", }
次に、 LuaCheck を実行して動作することをテストする必要があります。今回はエラーが大幅に少なくなるはずです。発生した最初のエラーから始めて、コードを変更して問題を削除するか、コードが正しい場合は構成を変更します。以下のリストを参照してください。
Troubleshooting
- 未定義の変数foobar へのアクセス -
foobar
がグローバルであることが意図されている場合は、それをread_globals
に追加します。それ以外の場合は、不足しているlocal
を mod に追加します。 - 非標準のグローバル変数 foobar の設定 -
foobar
がグローバルであることを意図している場合は、それをglobals
に追加します。存在する場合は、read_globals
から削除します。それ以外の場合は、不足しているlocal
を mod に追加します。 - 読み取り専用グローバル変数foobar の変更 -
foobar
をread_globals
からglobals
に移動するか、 foobar への書き込みを停止します。
Using with editor
コマンドを実行せずにエラーを表示するために、選択したエディターのプラグインを見つけてインストールすることを強くお勧めします。ほとんどのエディターは、プラグインを利用できる可能性があります。
- Atom -
linter-luacheck
。 - VSCode - Ctrl + P、次に貼り付けます:
ext install dwenegar.vscode-luacheck
- Sublime - package-control を使用してインストール:SublimeLinter、SublimeLinter-luacheck 。
Checking Commits with Travis
プロジェクトが公開されており、 Github 上にある場合は、 TravisCI を使用できます。これは無料のサービスで、コミット時にジョブを実行してチェックします。これは、プッシュするすべてのコミットが LuaCheck に対してチェックされ、 LuaCheck が間違いを検出したかどうかに応じて、それらの横に緑色のチェックマークまたは赤い十字が表示されることを意味します。これは、プロジェクトがプルリクエストを受信した場合に特に役立ちます。コードをダウンロードしなくても、 LuaCheck の出力を確認できます。
まず、travis-ci.orgにアクセスし、 Github アカウントでサインインする必要があります。次に、 Travis プロファイルでプロジェクトのリポジトリを見つけ、スイッチを切り替えて Travis を有効にします。
次に、次の内容の .travis.yml というファイルを作成します。
language: generic sudo: false addons: apt: packages: - luarocks before_install: - luarocks install --local luacheck script: - $HOME/.luarocks/bin/luacheck . notifications: email: false
プロジェクトが mod や modpack ではなくゲームの場合は、 script:
の後の行を次のように変更します。
- $HOME/.luarocks/bin/luacheck mods/
次に、コミットして Github にプッシュします。 Github でプロジェクトのページに移動し、[コミット]をクリックします。行ったコミットの横にオレンジ色のディスクが表示されます。しばらくすると、 LuaCheck の結果に応じて、緑色のチェックマークまたは赤い十字のいずれかに変わるはずです。いずれの場合も、アイコンをクリックして、ビルドログと LuaCheck の出力を確認できます。
23 - Security
Introduction
mod によってサーバーの所有者がデータや制御を失うことがないようにするためには、セキュリティが非常に重要です。
- Core Concepts
- Formspecs
- (Insecure) Environments
Core Concepts
セキュリティの最も重要な概念は、ユーザーを決して信頼しない ことです。ユーザーが送信するものはすべて悪意のあるものとして扱われる必要があります。つまり、入力した情報が有効であること、ユーザーが正しい権限を持っていること、その他の方法でそのアクションを実行できること(つまり、レンジ内または所有者)を常に確認する必要があります。
悪意のあるアクションは必ずしもデータの変更や破壊ではありませんが、パスワードハッシュやプライベートメッセージなどの機密データにアクセスする可能性があります。サーバーが電子メールや年齢などの情報を保存している場合、これは特に悪いことです。これは検証目的で行われる場合があります。
Formspecs
Never Trust Submissions
すべてのユーザーは、いつでも任意の値でほぼすべてのフォームスペックを送信できます。
mod で見つかった実際のコードは次のとおりです。
minetest.register_on_player_receive_fields(function(player, formname, fields) for key, field in pairs(fields) do local x,y,z = string.match(key, "goto_([%d-]+)_([%d-]+)_([%d-]+)") if x and y and z then player:set_pos({ x=tonumber(x), y=tonumber(y), z=tonumber(z) }) return true end end end
問題を見つけることができますか?悪意のあるユーザーは、自分の位置の値を含む formspec を送信して、好きな場所にテレポートできるようにする可能性があります。これは、クライアントの変更を使用して自動化することもでき、特権を必要とせずに「/teleport」コマンドを本質的に複製できます。
この種の問題の解決策は、 Formspecs の章で前述したように、Contextを使用することです。
Time of Check isn’t Time of Use
エンジンで禁止されている場合を除き、すべてのユーザーはいつでも任意の値で任意の formspec を送信できます。
- ユーザーが離れすぎている場合、ノード formspec の送信はブロックされます。
- 5.0 以降、名前付き formspec は、まだ表示されていない場合はブロックされます。
これは、ユーザーが最初に formspec を表示するための条件と、対応するアクションを満たしていることをハンドラーで確認する必要があることを意味します。
handle formspec ではなく showformspec でアクセス許可をチェックすることによって引き起こされる脆弱性は、Time Of Check is not Time Of Use (TOCTOU) と呼ばれます。
(Insecure) Environments
Minetest を使用すると、 mod はサンドボックス化されていない環境を要求でき、 Lua API 全体にアクセスできます。
次の脆弱性を見つけることができますか?
local ie = minetest.request_insecure_environment() ie.os.execute(("path/to/prog %d"):format(3))
string.format
は、グローバル共有テーブル string
の関数です。悪意のある mod が関数をオーバーライドし、 os.execute にデータを渡す可能性があります。
string.format = function() return "xdg-open 'http://example.com'" end
mod は、リモートユーザーにマシンの制御を与えるなど、Web サイトを開くよりもはるかに悪意のあるものを渡す可能性があります。
安全でない環境を使用するためのいくつかのルール:
- 常にローカルに保存し、関数に渡さないでください。
- 上記の問題を回避するために、安全でない関数に与えられた入力を信頼できることを確認してください。これは、グローバルに再定義可能な関数を回避することを意味します。
24 - Intro to Clean Architectures
Introduction
mod が適切なサイズに達すると、コードをクリーンでバグのない状態に保つことがますます難しくなります。これは、Lua のような動的に型付けされた言語を使用する場合に特に大きな問題です。これは、型が正しく使用されていることを確認する場合など、コンパイラーがコンパイラー時のヘルプをほとんど提供しないためです。
この章では、コードをクリーンに保つために必要な重要な概念と、それを実現するための一般的なデザインパターンについて説明します。この章は規範的なものではなく、可能性についてのアイデアを提供することを目的としていることに注意してください。 mod を設計する良い方法は1つではなく、良い mod の設計は非常に主観的です。
- Cohesion, Coupling, and Separation of Concerns
- Observer
- Model-View-Controller
- Conclusion
Cohesion, Coupling, and Separation of Concerns
計画がなければ、プログラミングプロジェクトは徐々にスパゲッティコードに陥る傾向があります。スパゲッティコードは構造の欠如を特徴としています-すべてのコードは明確な境界なしで一緒にスローされます。これにより、最終的にプロジェクトは完全に保守不可能になり、放棄されてしまいます。
これの反対は、相互作用する小さなプログラムまたはコードの領域のコレクションとしてプロジェクトを設計することです。
すべての大きなプログラムの中には、抜け出そうとする小さなプログラムがあります。
–C.A.R. Hoare
これは、関心の分離を達成するような方法で行う必要があります。各領域は別個のものであり、個別のニーズまたは懸念に対処する必要があります。 This should be done in such a way that you achieve Separation of Concerns - each area should be distinct and address a separate need or concern.
これらのプログラム/エリアには、次の 2 つのプロパティが必要です。
- 高凝集性 - 領域は密接に/密接に関連している必要があります。
低結合 - 領域間の依存関係を可能な限り低く保ち、内部実装に依存しないようにします。カップリングの量が少ないことを確認することをお勧めします。これは、特定の領域の API を変更することがより実現可能になることを意味します。
High Cohesion - the area should be closely/tightly related.
- Low Coupling - keep dependencies between areas as low as possible, and avoid relying on internal implementations. It’s a very good idea to make sure you have a low amount of coupling, as this means that changing the APIs of certain areas will be more feasible.
これらは、mod 間の関係と、mod 内の領域間の関係の両方に当てはまることに注意してください。
Observer
コードのさまざまな領域を分離する簡単な方法は、 Observer パターンを使用することです。
プレイヤーが最初に珍しい動物を殺したときにアチーブメントのロックを解除する例を見てみましょう。ナイーブなアプローチは、 mob kill 関数にアチーブメントコードを入れ、 mob 名をチェックし、一致する場合は賞のロックを解除することです。ただし、これは mobs mod をアチーブメントコードに結合させるため、悪い考えです。これを続けた場合(たとえば、mob death コードに XP を追加した場合)、多くの厄介な依存関係が発生する可能性があります。
オブザーバーパターンを入力します。賞を気にする mymobsmod の代わりに、 mymobs mod は、コードの他の領域がイベントへの関心を登録し、イベントに関するデータを受信する方法を公開します。
mymobs.registered_on_death = {} function mymobs.register_on_death(func) table.insert(mymobs.registered_on_death, func) end -- in mob death code for i=1, #mymobs.registered_on_death do mymobs.registered_on_death[i](entity, reason) end
次に、他のコードがその関心を登録します。
mymobs.register_on_death(function(mob, reason) if reason.type == "punch" and reason.object and reason.object:is_player() then awards.notify_mob_kill(reason.object, mob.name) end end)
あなたは考えているかもしれません - ちょっと待って、これはひどく馴染みがあるように見えます。そして、あなたは正しい! Minetest API はオブザーバーベースであり、エンジンが何かをリッスンしていることを気にする必要がなくなります。
Model-View-Controller
次の章では、コードを自動的にテストする方法について説明します。問題の1つは、ロジック(計算、実行する必要があること)を API 呼び出し( minetest.*
、その他の mod )から可能な限り分離する方法です。
これを行う1つの方法は、次のことを考えることです。
- あなたが持っている データ。
- このデータで実行できる アクション。
- イベント (つまり、 formspec 、 punchs など)がこれらのアクションをトリガーする方法、およびこれらのアクションがエンジンで発生する方法。
土地保護 mod の例を見てみましょう。あなたが持っているデータは、エリアと関連するメタデータです。実行できるアクションは、 create
、 edit
、または delete
です。これらのアクションをトリガーするイベントは、チャットコマンドと formspec 受信フィールドです。これらは通常、かなりうまく分離できる 3 つの領域です。
テストでは、トリガーされたときのアクションがデータに対して正しいことを行うことを確認できます。イベントがアクションを呼び出すことをテストする必要はありません(これには Minetest API を使用する必要があり、コードのこの領域はとにかくできるだけ小さくする必要があります)。
Pure Lua を使用してデータ表現を作成する必要があります。このコンテキストでの「 Pure 」とは、関数が Minetest の外部で実行される可能性があることを意味します。つまり、エンジンの関数は呼び出されません。
-- Data function land.create(name, area_name) land.lands[area_name] = { name = area_name, owner = name, -- more stuff } end function land.get_by_name(area_name) return land.lands[area_name] end
あなたの行動も pure でなければなりませんが、他の関数を呼び出すことは上記よりも受け入れられます。
-- Controller function land.handle_create_submit(name, area_name) -- process stuff -- (ie: check for overlaps, check quotas, check permissions) land.create(name, area_name) end function land.handle_creation_request(name) -- This is a bad example, as explained later land.show_create_formspec(name) end
イベントハンドラーは Minetest API と対話する必要があります。この領域を簡単にテストすることはできないため、計算の数を最小限に抑える必要があります。
-- View function land.show_create_formspec(name) -- Note how there's no complex calculations here! return [[ size[4,3] label[1,0;This is an example] field[0,1;3,1;area_name;] button_exit[0,2;1,1;exit;Exit] ]] end minetest.register_chatcommand("/land", { privs = { land = true }, func = function(name) land.handle_creation_request(name) end, }) minetest.register_on_player_receive_fields(function(player, formname, fields) land.handle_create_submit(player:get_player_name(), fields.area_name) end)
上記は Model-View-Controller パターンです。モデルは、最小限の機能を備えたデータのコレクションです。ビューは、イベントをリッスンしてコントローラーに渡す関数のコレクションであり、コントローラーから呼び出しを受信して、 Minetest API で何かを実行します。コントローラーは、決定とほとんどの計算が行われる場所です。
コントローラーは MinetestAPI についての知識を持っていないはずです - Minetest 呼び出しまたはそれらに類似したビュー関数がないことに注意してください。 view.hud_add(player、def)
のような関数は持ってはいけません。代わりに、ビューは、 view.add_hud(info)
のように、コントローラーがビューに実行するように指示できるいくつかのアクションを定義します。ここで、 info は、 Minetest API にまったく関係のない値またはテーブルです。
エリアの内部または外部を変更する場合に変更する必要のある量を減らすために、上記のように、各エリアが直接隣接するエリアとのみ通信することが重要です。たとえば、 formspec を変更するには、ビューを編集するだけで済みます。ビュー API を変更するには、ビューとコントローラーを変更するだけで、モデルはまったく変更できません。
実際には、この設計は複雑さが増し、ほとんどの種類の mod に多くの利点がないため、ほとんど使用されません。代わりに、一般的に、それほど形式的で厳密ではない種類のデザイン( API-View のバリアント)が表示されます。
API-View
理想的な世界では、通常のビューに戻る前に、上記の 3 つの領域が完全に分離され、すべてのイベントがコントローラーに送られます。しかし、これは現実の世界ではありません。良い妥協案は、 mod を 2 つの部分に減らすことです。
- API - これは上記のモデルとコントローラーでした。ここでは
minetest.
を使用しないでください。 - ビュー - これも上記のビューでした。これをイベントの種類ごとに別々のファイルに構造化することをお勧めします。
rubenwardy のcraftingmodは、おおまかにこの設計に従います。 api.lua
は、データストレージとコントローラースタイルの計算を処理するほとんどすべての pure な Lua 関数です。 gui.lua
は formspecs と formspec 送信のビューであり、async_crafter.lua
はノード formspec とノードタイマーのビューとコントローラーです。
このように mod を分離すると、次の章に示すように、Minetest APIを使用しないため、API 部分を非常に簡単にテストできます。そして craftingmod で見られます。
Conclusion
優れたコード設計は主観的なものであり、作成するプロジェクトに大きく依存します。原則として、凝集力を高く、結合度を低く保つようにしてください。別の言い方をすれば、関連するコードをまとめ、関連しないコードを分離し、依存関係を単純に保ちます。
ゲームプログラミングパターンの本を読むことを強くお勧めします。 オンラインで読むから無料で入手でき、ゲームに関連する一般的なプログラミングパターンについて詳しく説明しています。
26 - Releasing a Mod
Introduction
mod をリリースまたは公開すると、他の人がそれを利用できるようになります。 mod がリリースされると、シングルプレイヤーゲームや、パブリックサーバーを含むサーバーで使用される可能性があります。
- ライセンスの選択
- パッケージング
- アップロード
- フォーラムトピック
License Choices
mod のライセンスを指定する必要があります。これは、他の人にあなたの作品の使用を許可する方法を伝えるため、重要です。 mod にライセンスがない場合、パブリックサーバーで mod を変更、配布、または使用することが許可されているかどうかはわかりません。
あなたのコードとあなたのアートは、彼らが使用するライセンスとは異なるものを必要とします。たとえば、クリエイティブコモンズライセンスはソースコードと一緒に使用するべきではありませんが、画像、テキスト、メッシュなどの芸術作品に適した選択肢となる可能性があります。
すべてのライセンスが許可されます。ただし、デリバティブを許可しない mod は、公式の Minetest フォーラムから禁止されています。 ( mod をフォーラムで許可するには、他の開発者が mod を変更して、変更されたバージョンをリリースできる必要があります。)
定義は国によって異なるため、パブリックドメインは有効なライセンスではないことに注意してください。
LGPL and CC-BY-SA
これは Minetest コミュニティで一般的なライセンスの組み合わせであり、 Minetest と Minetest Game が使用するものです。コードは LGPL2.1
でライセンスされ、アートは CC-BY-SA
でライセンスされます。この意味は:
- 誰でも、変更されたバージョンまたは変更されていないバージョンを変更、再配布、および販売できます。
- 誰かがあなたの mod を変更する場合、彼らは彼らのバージョンに同じライセンスを与える必要があります。
- 著作権表示を保持する必要があります。
CC0
これらのライセンスにより、誰でも mod を使ってやりたいことができるようになります。つまり、帰属を変更、再配布、販売、または除外することができます。これらのライセンスは、コードとアートの両方に使用できます。
WTFPL は強く推奨されておらず、このライセンスを持っている場合、人々はあなたの mod を使用しないことを選択する可能性があることに注意することが重要です。
MIT
これは mod コードの一般的なライセンスです。 mod のユーザーに課せられる唯一の制限は、 mod または mod の実質的な部分のコピーに同じ著作権表示とライセンスを含める必要があることです。
Packaging
リリースする前に mod に含めることをお勧めするファイルがいくつかあります。
README.txt
README ファイルには次のように記載する必要があります。
- mod の機能。
- ライセンスとは何ですか。
- どのような依存関係がありますか。
- mod のインストール方法。
- mod の現在のバージョン。
- オプションで、問題を報告したり、ヘルプを入手したりする場所。
description.txt
これはあなたの mod が何をするかを説明するはずです。曖昧にならずに簡潔にしてください。スペースに限りがあるコンテンツインストーラーに表示されるため、短くする必要があります。
良い手本:
Adds soup, cakes, bakes and juices.
これは避けてください:
(BAD) The food mod for Minetest.
screenshot.png
スクリーンショットは 3:2(高さ 2 ピクセルごとに幅 3 ピクセル)で、最小サイズは 300px x 200px である必要があります。
スクリーンショットは mod ストアに表示されます。
Uploading
潜在的なユーザーがあなたの mod をダウンロードできるように、あなたはそれを公的にアクセス可能な場所にアップロードする必要があります。これを行うにはいくつかの方法がありますが、これらの要件、およびフォーラムのモデレーターによって追加される可能性のあるその他の要件を満たしている限り、自分に最適なアプローチを使用する必要があります。
- 安定 - ホスティングウェブサイトが警告なしにシャットダウンする可能性はほとんどありません。
- 直接リンク - フォーラムのリンクをクリックして、別のページを表示せずにファイルをダウンロードできるはずです。
- ウイルスフリー - 悪意のあるコンテンツを含む mod はフォーラムから削除されます。
Version Control Systems
次のようなバージョン管理システムを使用することをお勧めします。
- 他の開発者が簡単に変更を送信できるようにします。
- ダウンロードする前にコードをプレビューできるようにします。
- ユーザーがバグレポートを送信できるようにします。
Minetest の改造者の大多数は、コードをホストする Web サイトとして GitHub を使用していますが、別の方法も可能です。
GitHub の使用は、最初は難しい場合があります。これについてサポートが必要な場合、 GitHub の使用方法については、以下を参照してください。
- Pro Gitブック - オンラインで無料で読むことができます。
- GitHub for Windowsアプリ - Windows のグラフィカルインターフェイスを使用してコードをアップロードします。
Forum Attachments
バージョン管理システムを使用する代わりに、フォーラムの添付ファイルを使用して mod を共有できます。これは、 mod のフォーラムトピック(以下で説明)を作成するときに実行できます。
mod のファイルを1つのファイルに圧縮する必要があります。これを行う方法は、オペレーティングシステムによって異なります。これはほとんどの場合、すべてのファイルを選択した後、右クリックメニューを使用して行われます。
フォーラムのトピックを作成するときは、「 Create a Topic トピックの作成」ページ(以下を参照)で、下部にある「 Upload Attachment 添付ファイルのアップロード」タブに移動します。 「 Browse 参照」をクリックして、 zip ファイルを選択します。コメントフィールドに mod のバージョンを入力することをお勧めします。
Upload Attachment tab.
Forum Topic
これで、フォーラムトピックを作成できます。 “WIP Mods”(作業中)フォーラムで作成する必要があります。 mod が進行中の作業であると見なされなくなったら、移動をリクエストに「 Mod Releases 」できます。
フォーラムのトピックには、 README と同様のコンテンツが含まれている必要がありますが、より宣伝的で、 mod をダウンロードするためのリンクも含まれている必要があります。可能であれば、実際の mod のスクリーンショットを含めることをお勧めします。
Minetest フォーラムでは、フォーマットに bbcode を使用しています。 superspecial という名前の mod の例を次に示します。
Adds magic, rainbows and other special things. See download attached. [b]Version:[/b] 1.1 [b]License:[/b] LGPL 2.1 or later Dependencies: default mod (found in minetest_game) Report bugs or request help on the forum topic. [h]Installation[/h] Unzip the archive, rename the folder to superspecial and place it in minetest/mods/ ( GNU/Linux: If you use a system-wide installation place it in ~/.minetest/mods/. ) ( If you only want this to be used in a single world, place the folder in worldmods/ in your world directory. ) For further information or help see: [url]https://wiki.minetest.net/Installing_Mods[/url]
上記の例を mod トピック用に変更する場合は、「 superspecial 」を mod の名前に変更することを忘れないでください。
Subject
トピックの主題は、次のいずれかの形式である必要があります。
- [Mod] Mod Title [modname]
- [Mod] Mod Title [version number] [modname]
例えば:
- [Mod] More Blox [0.1] [moreblox]
27 - Read More
List of Resources
この本を読んだら、以下を見てください。
Minetest Modding
- Minetest の Lua API リファレンス - HTMLバージョン | テキストバージョン
- Developer Wikiを調べてください。
- existing modsを見てください。
Lua Programming
3D Modelling
Lua Modding API
ダウンロード例
© 2014-20 | Helpful? Consider donating to support my work.