マンガサイト

漫画サイト。 それは漫画がブラウザで読める 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

cat2.PNG

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の作者、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つであるため、プログラミング言語の最も基本的な構文を初心者に説明したり、言語またはシステムが正しく動作していることを確認したりするためによく使用されます。

ウィキペディアHello world program

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

この章の内容を理解したことを確認するために質問がいくつかあります。これらの質問の対する答えを見つけるには、この章に記載されていない知識が必要になる場合があることに注意してださい。これは正常なことです。クイズは学習体験の一部であり、本の他の場所では入手できない情報を紹介することができます。

1 ポルトガル語で「Lua」とはどういう意味ですか?

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

Luaの数値の演算子は次のとおりです。

操作 構文 説明
算術否定 -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 のいずれかになりますが、それ以外はありません。これは、予約キーワードであるtruefalseとして Lua で記述されています。注意すべき重要な点は、これは前述nilのように異なるデータ型であるということです。andornot() は通常ブール値と関連していますが、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と空の文字列も含む)。

次の章で紹介する関係演算子<><=>=~===)は必ずしもオペランドとしてブール値を取ることはありませんが、常に結果としてブール値が得られます。

これを調整するのは難しい場合があります。わかりやすくするために、ここにいくつかの真理値表または式と結果のペアを示します。ここでxniltrueまたは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)エンドツーエンドをつなげる操作です。たとえば、「雪」と「ボール」の連結は「雪玉」です。

ウィキペディアConcatenation

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".

Wikipedia, Concatenation

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)_{{10}}=(0101)_{2}

(3)_{{10}}=(0011)_{2}

(1)_{{10}}=(0001)_{2}

5と3の両方のバイナリ表現の特定の位置にあるビットが1である場合(最後のビットの場合のように)、その位置にあるビットは1になります。それ以外の場合はすべて0になります。

ビット単位のOR演算(演算子|を使用)は、ビット単位のAND演算と同じように機能し、論理積を実行する代わりに論理和を実行します。したがって、5 | 3 は7と評価されます。

(5)_{{10}}=(0101)_{2}

(3)_{{10}}=(0011)_{2}

(7)_{{10}}=(0111)_{2}

ここで、2つのオペランドのバイナリ表現がその位置に0ビットを持っていた場合にのみ、最終結果の各位置のビットが0であることがわかります。

ビット単位のXOR演算(演算子~を使用)は他の2つの演算と同じように機能しますが、特定の位置では、オペランドのビットの両方ではなく片方が1の場合、最後のビットは1になります。

(5)_{{10}}=(0101)_{2}

(3)_{{10}}=(0011)_{2}

(6)_{{10}}=(0110)_{2}

これは前の例と同じですが、両方のオペランドの最後のビットが1であったため、結果の最後のビットが1ではなく0であることがわかります。

ビット単位のNOT演算(演算子~を使用)は、一意のオペランドの各ビットに対して論理否定を実行します。つまり、0が1になり、1が0になります。したがって、~7 は -8 と評価されます。

(7)_{{10}}=(0111)_{2}

(8)_{{10}}=(1000)_{2}

ここで、最初のビットはオペランドが0であるため結果で1になり、他のビットはすべて1であるため0になります。

Left shiftRight shift

これらのビット演算子に加えて、Lua5.3は算術ビットシフトもサポートしています。演算子<<を使用して左側に示されている左シフトは、すべてのビットを、第2オペランドに対応するビット数だけ左にシフトすることで構成されます。演算子>>で示され、右に示されている右シフトも同じですが、ビットシフトは反対方向です。

Operator precedence

演算子の優先順位は、Lua でも数学で通常行われるのと同じように機能します。特定の演算子は他の演算子よりも先に評価され、括弧を使用して、操作を実行する順序を任意に変更できます。演算子が評価される優先度は、優先度の高いものから低いものへと、以下のリストにあります。これらの演算子のいくつかはまだ議論されていませんが、それらはすべてこの本のある時点でカバーされます。

  1. べき乗: ^

  2. 単項演算子not#-~

  3. レベル2数学演算子*///%

  4. レベル1の数学演算子+-

  5. 連結: ..

  6. ビットシフト:<<>>

  7. ビットごとのAND: &

  8. ビットごとのXOR: ~

  9. ビットごとのOR: |

  10. 関係演算子<><=>=~===

  11. ブール値と: and

  12. ブール値または: 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によって予約されており、名称として使用することはできません:andendinrepeatbreakfalselocalreturndofornilthenelsefunctionnottrueelseififoruntilwhile

変数またはテーブルフィールドに名前を付けるときは、その有効な名前を選択する必要があります。したがって、文字またはアンダースコアで始まり、文字、アンダースコア、および数字のみを含める必要があります。Luaでは大文字と小文字が区別されることに注意してください。これは、Hellohelloが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)、JavaScriptRuby&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値が追加されて、のリストと同じ長さになります。変数値リストの最後に関数呼び出しがある場合、関数呼び出しが括弧で囲まれていない限り、返される値はそのリストの最後に追加されます。

さらに、ほとんどのプログラミング言語とは異なり、Luapermutation (順列)を介して変数の値の再割り当てを可能にします。例えば:

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(反復)と呼ばれます。whilerepeatループの違いは、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で実行されたチャンク内のすべてのエラーが伝播します。

  1. http://codepad.org/kYHPSvqx

Functions

スタックとそのスタックで実行できる操作の図。

An illustration of a stack and of the operations that can be performed on it.

スタックは、後入れ先出しの原則に従って動作する、アイテムを追加(プッシュ)または削除(ポップ)できるアイテムのリストです。つまり、最後に追加されたアイテムが最初に削除されます。このようなリストがスタックと呼ばれるのはこのためです。スタックでは、最初にその上にあるアイテムを削除せずにアイテムを削除することはできません。したがって、すべての操作はスタックの最上位(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() endlocal 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.
  1. 詳細については、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.
  2. 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).

Wikipedia, Coroutine

コルーチンは、Luaのコルーチンライブラリを使用して作成および操作できるコンポーネントであり、コルーチンを内部から生成する関数またはコルーチンを外部から再開する関数を呼び出すことにより、特定の場所で関数の実行を生成および再開できるようにします 。

例:

  1. メインスレッドの関数は、coroutine.create関数からコルーチンを作成し、coroutine.resumeで再開します。コルーチンには、番号3が渡されます。

  2. コルーチンの関数が実行され、引数として coroutine.resume に渡された数値を取得します。

  3. 関数は、実行の特定の時点に到達しcoroutine.yield、引数として、受け取った引数の合計(3)と2(したがって、3 + 2 = 5)を呼び出します。

  4. coroutine.resumeへの呼び出しはcoroutine.yieldに渡されたため、5を返し、メインスレッドは現在再び実行されており、その数値を変数に格納します。 コードを実行した後、コルーチンを再開し、 coroutine.resumeの呼び出しから受け取った値の2倍をcoroutine.resumeに渡します(つまり、5×2 = 10を渡します)。

  5. コルーチンは、 coroutine.yield の呼び出しの結果として、coroutine.resume に渡された値を取得し、さらにコードを実行した後に終了します。 これは、 coroutine.yield の呼び出しの結果と、最初にパラメーターとして指定された値(つまり、10-3 = 7)との差を返します。

  6. メインスレッドは、 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リファレンスマニュアルに記載があります。

  1. Ierusalimschy, Roberto; Celes, Waldemar; Henrique de Figueiredo, Luiz. Lua 5.2 Reference Manual. http://www.lua.org/manual/5.2. Retrieved 30 November 2013.
  2. Functions that were already described elsewhere will not be described in this chapter.

Appendix:Software testing

ソフトウェアテストという用語は、コンピュータソフトウェアのバグやプログラミングの間違いを発見するために使用されるいくつかの方法とプロセスを指します。 ソフトウェアテストは静的に実行できます。静的テストと呼ばれ、コンピュータソフトウェアを実行せずに実行されます。動的な場合は、動的テストと呼ばれ、テスト対象のコンピュータプログラムの実行中に実行されます。

Type checking

プログラミング言語では、type systemは、type と呼ばれるプロパティを、コンピュータープログラムで構成されるさまざまな構成要素(変数、式、関数、モジュールなど)に割り当てるルールの集まりです。 型システムの主な目的は、コンピュータープログラムのさまざまな部分の間のインターフェイスを定義し、それらの部分が一貫した方法で接続されていることを確認することによって、コンピュータープログラムのバグを減らすことです。このチェックは、静的(コンパイル時)、動的(実行時)、または静的チェックと動的チェックの組み合わせとして実行できます。 型システムには、特定のコンパイラの最適化の有効化、複数のディスパッチの許可、ドキュメントの形式の提供など、他の目的もあります。

Wikipedia, Type system

ウィキペディアからの抜粋が示すように、型チェックは実行時またはコンパイル時に実行できます。 コンパイル時に実行される場合、コンパイラソースコードコンパイルするときに、プログラムの型安全性を検証し、プログラムが特定の型安全性プロパティを満たしていることを保証します。通常、静的型チェッカーは、変数の値が常に 同じ型であり、関数に渡される引数は正しい型になります。

静的なアプローチにより、開発サイクルの早い段階でバグを発見できます。対照的に、動的アプローチは、プログラムの実行時にプログラムが型制約に従っていることを確認することで構成されます。 これは、動的型チェッカーがより多くの制約を検証できる必要があることを意味しますが、ほとんどの動的型付き言語には多くの型制約がありません。 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

    An argument) is a value passed to a function.

  • 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 or false, 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

    imgVenn diagram of {\displaystyle \scriptstyle a\veebar b}\scriptstyle a\veebar bThe 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}\scriptstyle a\veebar b. There is no operator corresponding to exclusive disjunction in Lua, but {\displaystyle \scriptstyle a\veebar b}\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 is false and vice versa.

  • logical conjunction

    imgVenn diagram of {\displaystyle \scriptstyle a\land b}{\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 and false in all other cases. It is implemented as the and operator in Lua and it returns its first operand if it is false or nil and the second operand otherwise. The logical conjunction of a and b is expressed mathematically as {\displaystyle \scriptstyle a\land b}{\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 and true.

  • logical disjunction

    imgVenn diagram of {\displaystyle \scriptstyle a\lor b}{\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 and true in all other cases. It is implemented as the or operator in Lua and it returns the first operand if it is neither false nor nil and the second otherwise. The logical disjunction of a and b is expressed mathematically as {\displaystyle \scriptstyle a\lor b}{\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.

  • nil

    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

間違いに気づきましたか、それともフィードバックを送りたいですか?ぜひ連絡してください。

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 の場所が順番にチェックされます。:

  1. Game mods これらは、world が実行しているゲームを形成する mods です。例: minetest/games/minetest_game/mods//usr/share/minetest/games/minetest/
  2. Global mods 、ほとんどの場合 mods がインストールされる場所。疑わしい場合は、ここに配置してください。例: minetest/mods/
  3. World mods 、特定のワールドに固有の mods を保存する場所。例: minetest/worlds/world/worldmods/

Minetest は、上記の順序で場所を確認します。以前に見つかったものと同じ名前の mod が見つかった場合、前の mod の代わりに後の mod がロードされます。これは、global mod のロケーションに同じ名前の mod を配置することで、game mod をオーバーライド(上書き)できることを意味します。

各 mod ロードパスの実際の場所は、使用しているオペレーティングシステムと、Minetest のインストール方法によって異なります。

  • Windows
  • ポータブルビルドの場合、例:あなたが .zip ファイルから、抽出したディレクトリに移動します。 gamesmods および 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

Find the mod's 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):繰り返し、ループします。条件が満たされるまで、同じステートメントを実行し続けます。

では、 Luaステートメントはどのように見えますか?

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 AD = nil
Number 整数または10進数。 local A = 4
String テキストの一部 local D = "one two three"
Boolean True or False local is_true = falselocal 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?

ノード、クラフトアイテム、ツールはすべてアイテムです。アイテムは、通常のゲームプレイでは不可能な場合でも、インベントリ内で見つけることができるものです。

ノードは、世界に配置されているか、または発見することができるアイテムです。世界のすべての位置空間は、たった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 → tofromエイリアスで、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 を使用する場合、鉛筆ツールはツールボックスから選択できます。

Pencil in 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

Normal Drawtype

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

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'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

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

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

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 に似ています。

Firelike nodes

Firelike nodes

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?

ItemStack は、インベントリ内の単一セルの背後にあるデータです。

inventoryinventory 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 - ノード定義を参照してください。

ブロックが非アクティブの場合、関数は含まれているブロックをロードせず、代わりに nameignore のテーブルを返すことに注意してください。代わりに 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_metafind_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_tablefrom_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_punchminetest.set_node を使います)

10 - Objects, Players, and Entities

Introduction


この章では、オブジェクトを操作する方法と、オブジェクトを定義する方法を学習します。

What are Objects, Players, and Entities?

プレーヤーとエンティティはどちらもオブジェクトのタイプです。オブジェクトは、ノードグリッドとは独立して移動できるものであり、速度やスケールなどのプロパティがあります。オブジェクトはアイテムではなく、独自の個別の登録システムがあります。

プレイヤーとエンティティの間にはいくつかの違いがあります。最大のものは、プレイヤーがプレイヤーによって制御されるのに対し、エンティティは mod によって制御されることです。これは、プレイヤーの速度を mod で設定できないことを意味します。プレイヤーはクライアント側であり、エンティティはサーバー側です。もう1つの違いは、プレーヤーによってマップブロックが読み込まれるのに対し、エンティティは保存されて非アクティブになることです。

この区別は、後で説明するように、エンティティが Lua エンティティと呼ばれるテーブルを使用して制御されるという事実によって混乱しています。

Position and Velocity

get_posset_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_joinplayerset_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

特権はプレイヤーに何かをする能力を与えるべきです。クラスまたはステータスを示すための特権ではありません

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

ゲーム内のすべてのプレーヤーにメッセージを送信するには、 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 :teamnamejoin です。スペースも端子としてカウントされます。

変数は、ユーザーの入力内容に応じて値を変更できます。たとえば、 :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.2
  • text - 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 つの方法があります。

  1. ChatCmdBuilder を mod としてインストールし、それに依存します。
  2. 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

Furnace InventoryScreenshot of furnace formspec, labelled.

この章では、 formspec を作成してユーザーに表示する方法を学習します。 formspec は、フォームの仕様コードです。 Minetest では、フォームはプレーヤーインベントリなどのウィンドウであり、ラベル、ボタン、フィールドなどのさまざまな要素を含めることができます。

プレーヤーに情報を提供するだけでよい場合など、ユーザー入力を取得する必要がない場合は、formes ではなくヘッドアップディスプレイ(HUD)の要素の使用を検討する必要があることに注意してください。予期しないウィンドウはゲームプレイを混乱させる傾向があるためです。

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

Guessing FormspecThe 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

Padding and spacingThe 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

Position and Offset

Diagram showing a centered text element

画面にはさまざまな物理的サイズと解像度があり、HUDはすべての画面タイプで適切に機能する必要があります。

これに対する Minetest の解決策は、パーセンテージ位置とオフセットの両方を使用して要素の位置を指定することです。パーセンテージの位置は画面サイズを基準にしているため、要素を画面の中央に配置するには、画面の半分のパーセンテージの位置を指定する必要があります。 (50%, 50%) 、および (0, 0) のオフセット。

次に、オフセットを使用して、パーセント位置を基準にして要素を移動します。

Alignment

位置合わせは、位置とオフセットの結果が要素上にある場所です。たとえば、 {x = -1.0, y = 0.0} は、位置とオフセットの結果を要素の境界の左側に配置します。これは、テキスト要素を左、中央、または右に揃える場合に特に便利です。

Diagram showing alignment

上の図は、3つのウィンドウ(青)を示しています。各ウィンドウには、 1 つの HUD 要素(黄色)があり、毎回異なる配置になっています。矢印は、位置とオフセットの計算結果です。

Scoreboard

この章では、次のようにスコアパネルを配置および更新する方法を学習します。

screenshot of the HUD we're aiming for

上のスクリーンショットでは、すべての要素の位置が同じパーセンテージ (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,
})

これにより、次のようになります。

screenshot of the HUD we're aiming for

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 },
})

これで、次のようになります。

screenshot of the HUD so far

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

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,
})

上記のコードについては何も新しいことはありません。すべての概念は、上記および前の章で説明されています。

Player Admin Page

Receiving events

sfinv 定義に on_player_receive_fields 関数を追加することで、 formspec イベントを受け取ることができます。

on_player_receive_fields = function(self, player, context, fields)
    -- TODO: implement this
end,

on_player_receive_fieldsminetest.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?

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

VernoiVoronoi 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などのプログラムを使ってボロノイ図を作成することもできます。

  1. ツールバーのポイントツール(アイコンは「 A 」の付いたポイント)を選択し、チャートをクリックしてポイントを作成します。ポイントをドラッグしたり、左側のサイドバーで明示的に位置を設定したりできます。また、物事を明確にするために、各ポイントにラベルを付ける必要があります。

  2. 次に、左側のサイドバーの入力ボックスに次の関数を入力して、ボロノイを作成します。

    Lua Voronoi({ A, B, C, D, E })

    各ポイントが中括弧の内側にあり、コンマで区切られている場合。あなたは今すべきです。

  3. やったぁ!これで、ドラッグ可能なすべてのポイントを含むボロノイ図が作成されます。

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 = 1y = 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 は指示された正確な領域を読み取ることはほとんどありません。代わりに、より広い領域を読み取る可能性があります。より大きな領域は eminemax で与えられ、これらは emerged min posemerged 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 とライティングデータを取得できます。

上記のメソッドで指定されたフラット配列のどこにノードがあるかを調べるには、 eminemax を使用する必要があります。計算を処理する 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 に置き換えられます。ここで、fromto はノード名です。
  • すべての胸節を90°回転させる関数を作成します。
  • LVM を使用して、苔むした丸石を近くの石や丸石のノードに広げる関数を作成します。あなたの実装は苔むした丸石を毎回 1 ノード分の距離以上に広げる原因になりますか?もしそうなら、どうすればこれを止めることができますか?

20 - Creating Games

Introduction

Minetest の力は、独自のボクセルグラフィックス、ボクセルアルゴリズム、または派手なネットワークコードを作成しなくても、ゲームを簡単に開発できることです。

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.confmenu/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)

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

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 の変更 - foobarread_globals から globalsに移動するか、 foobar への書き込みを停止します。

Using with editor

コマンドを実行せずにエラーを表示するために、選択したエディターのプラグインを見つけてインストールすることを強くお勧めします。ほとんどのエディターは、プラグインを利用できる可能性があります。

  • Atom - linter-luacheck
  • VSCode - Ctrl + P、次に貼り付けます: ext install dwenegar.vscode-luacheck
  • Sublime - package-control を使用してインストール:SublimeLinterSublimeLinter-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

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

計画がなければ、プログラミングプロジェクトは徐々にスパゲッティコードに陥る傾向があります。スパゲッティコードは構造の欠如を特徴としています-すべてのコードは明確な境界なしで一緒にスローされます。これにより、最終的にプロジェクトは完全に保守不可能になり、放棄されてしまいます。

これの反対は、相互作用する小さなプログラムまたはコードの領域のコレクションとしてプロジェクトを設計することです。

すべての大きなプログラムの中には、抜け出そうとする小さなプログラムがあります。

–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 の例を見てみましょう。あなたが持っているデータは、エリアと関連するメタデータです。実行できるアクションは、 createedit 、または 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 にまったく関係のない値またはテーブルです。

Diagram showing a centered text element

エリアの内部または外部を変更する場合に変更する必要のある量を減らすために、上記のように、各エリアが直接隣接するエリアとのみ通信することが重要です。たとえば、 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 AttachmentUpload 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

Lua Programming

3D Modelling

Lua Modding API

Lua Modding APIリファレンス

ダウンロード例

© 2014-20 | Helpful? Consider donating to support my work.