アットウィキロゴ
INSTEAD @ wiki
掲示板 掲示板 ページ検索 ページ検索 メニュー メニュー

INSTEAD @ wiki

ドキュメント

最終更新:

instead

- view
管理者のみ編集可
このテキストは公式のデベロップメントドキュメントを翻訳したものです。原文は以下にあります:
http://instead.syscall.ru/wiki/en/gamedev/documentation

0. 一般情報

STEAD用のゲームコードはLua(5.1)で書かれておりLua言語の仕様を知っておくと便利ですが、かならずしも必要ではありません。Luaエンジンのコードはおよそ3000行もの長さがあります。それこそが最良のドキュメントだと言えます。(訳注:INSTEAD 1.5.3ではLua 5.2になりました)

メインのゲームウィンドウには、シーンの静的または動的なパーツ、アクティブなイベント、シーンを切り替えられる画像(INSTEADのようなグラフィックインタプリタの場合)などが含まれます。

静的なパーツはプレイヤーがシーンに入ったときに一度だけ表示されます。同じくlookコマンド(グラフィックインタプリタではシーン名をクリックしたとき)で繰り返し表示されます。

シーンの動的なパーツはシーンのオブジェクトを説明する文章から構成されます。これは常時表示されます。

インベントリにはプレイヤーがシーン内でアクセスできるオブジェクトが含まれます。プレイヤーはインベントリオブジェクトを操作したり、持っているオブジェクトをシーンやインベントリにあるほかのオブジェクトに使ったりできます。

“inventory”はどちらかといえば曖昧な定義です。オブジェクトに対して適応できるコマンドは、たとえば“open”、“examine(調べる)”、“use”のようなものがあります。

プレイヤーが操作可能なアクションは以下のようなものです:
  • シーンを見る
  • シーンのオブジェクトを使う
  • インベントリのオブジェクトを使う
  • インベントリのオブジェクトをシーンのオブジェクトに対して使う
  • インベントリのオブジェクトをインベントリのオブジェクトに対して使う
  • シーンのオブジェクトをシーンのオブジェクトに対して使う(scene_useモード)
  • シーンのオブジェクトをインベントリのオブジェクトに対して使う(scene_useモード)
  • ほかのシーンへ移動する

どのゲームも main.lua スクリプトがディレクトリに存在します。ほかのゲームリソース(Luaスクリプト、画像、音楽)はこのディレクトリ以下に存在しなければなりません。すべてのリソースを参照するときはこのゲームディレクトリからの相対位置になります。

main.lua を実行するにはヘッダを記述する必要があります。いくつかのタグが含まれます。どのヘッダタグもLuaプログラミングでののコメントを表すハイフン“-”の記号ではじまります。現在のところヘッダの種類は“$Name:”のみ使用できます。これはゲームのタイトルになり、UTF-8の文字コードで書きます。たとえば以下のようなコードになります:

-- $Name: すごく面白いゲーム!$

バージョン1.2.0以降のヘッダーではSTEAD APIバージョンを定義するようになっています。現在は“1.6.0”です。

instead_version "1.6.0"

この行がない場合、STEAD APIは互換モード(レガシーモード)になります。

ゲームの初期化ではinit関数のように定義します。たとえば次のようになります:

function init()
    me()._know_truth = false
    take(knife);
    take(paper);
end

グラフィックインタプリタは有効なゲームをゲームディレクトリから検索します。UNIX版では“~/.instead/games”になります。0.8.7以降のWindows版では“C:\Documents and Settings/[ユーザー名]/Local Settings/Application Data/instead/games”になります。

Windows版とUNIX版の1.2.0以降では“./appdata/games”が存在するとそのディレクトリを検索します。

1. シーン

シーンはゲーム内の単位です。その中では、プレイヤーがシーンのすべてのオブジェクトとアクションを調べることができます。ゲームはすくなくとも1個の“main”シーンを含みます。

main = room {
	nam = 'メインルーム',
	dsc = 'あなたは広い部屋にいます。',
};

各フィールドはroom型のmainオブジェクトの構成要素です。すべてのオブジェクトは属性とハンドラを持ちます。たとえばnam属性(name)はどのオブジェクトにも必須の属性です。

シーンで使うnam属性はプレイ時に表示されるシーン名になります。シーン名はシーンを移動する際にもそれを識別するために使用されます。

dsc属性はシーンの静的なパーツとして表示される説明文です。プレイヤーがシーンに入ったとき、または明示的にlookコマンド(訳注:シーンのタイトルをクリックしたとき)を使用したときに表示されます。

注意:シーン内の記述では要素の区切りに“;”を使ってください。次のようなコードになります:

main = room {
	nam = 'メインルーム';
	dsc = 'あなたは大きな部屋にいます。';
};

注意:ゲームコードで説明文を常に表示したい場合は“forcedsc”の引数をコードの先頭に使ってください。

game.forcedsc = true;

または“forcedsc”の引数を必要なシーンに記述してください。

長い説明文を書くときは以下のようなフォーマットを使うのが便利です:

dsc = [[ と て も 長 い 説 明 文... ]],

このフォーマットでは改行が無視されます。段落を分けたい場合は文末に記号“^”を記述します。2つ重ねると1行空きで表示されます。

dsc = [[ 最初の段落。 ^^
次の段落。^^
 
3つ目の段落。^
新しい行。]],

2. オブジェクト

オブジェクトはシーン内の単位であり、プレイヤーが操作するものです。

tabl = obj {
	nam = 'テーブル',
	dsc = '部屋の中に{テーブル}があります。',
	act = 'うーん……ただのテーブルのようです。',
};

オブジェクト名“nam”はオブジェクトがインベントリに入ったとき、またテキストインタープリタではオブジェクトを処理するときに使用されます。

“dsc”は説明文のオブジェクトです。シーンの動的パーツとして表示されます。パーレン記号“{ }”で囲まれた文字はグラフィックインタプリタのリンクを表しています。

“act”はハンドラであり、プレイヤーがシーンのオブジェクトを使うときに呼ばれます。これは文字列を返す必要があり、シーン内のイベントパーツかブール値になります(5.属性と関数ハンドラを参照)。

注意:Lua名前空間でいくつかの文字列は(オブジェクトテーブルで)予約されているものがあります。たとえば“table”、“io”、“string”などのオブジェクトを作る場合は注意が必要です。例として“tabl”はすでに使われています(INSTEADの最近のバージョンではこの問題は解決されています)。

3. シーンへのオブジェクトの追加

オブジェクトを参照するにはそれが作られたときの名前の文字列を使います。たとえば“tabl”は“tabl”オブジェクトを参照します。

シーンにオブジェクトを配置するためにオブジェクトへの参照の配列“obj”を定義します:

main = room {
	nam = 'メインルーム',
	dsc = 'あなたは大きな部屋にいます。',
	obj = { 'tabl' },
};

こうするとシーンが表示されたときプレイヤーは動的パーツのテーブルオブジェクトを見ることになります。

注意:オブジェクトがそれを使いたい場所より前に定義されている場合は、引用符なしでオブジェクトへの参照を記述することもできます。しかし構文としては引用符で囲むほうが安全です。

4. オブジェクト間の参照

オブジェクトには“obj”属性を入れ子にすることができます。これでリストを連続的に展開できます。たとえばテーブルの上にリンゴを置いてみます。

aple = obj {
	nam = 'リンゴ',
	dsc = 'テーブルの上に{リンゴ}があります。',
	act = '取るべきでしょうかね?',
};

tabl = obj {
	nam = 'テーブル',
	dsc = '部屋の中に{テーブル}があります。',
	act = 'うーん……ただのテーブルのようです。',
	obj = { 'aple' },
};

このシーンでの説明文は、“aple”オブジェクトはtablによって参照されているので“テーブル”と“リンゴ”の説明文になります。
/* 翻訳者ノート:英語版のドキュメントではaple.namの値“apple”を“aple”と区別しています。 */

5. 属性と関数ハンドラ

ほとんどの属性とハンドラは関数のように機能します。たとえば:

nam = function()
	return 'リンゴ';
end,

これは以下のコードと同じです。

nam = 'リンゴ';

ハンドラは必ず文字列を返します。以下のようなもっと便利な関数もあります:

  • p ("text") 空白つきで出力します。
  • pn ("text") 新しい行として表示します。
  • pr ("text") そのまま表示します。

これらの関数で引数が1個の文字列だけの場合はパーレン記号“{ }”を省略できます。パーレンを使うか、あるいは文字列の連結を使うことができます。たとば:

pn "パーレンなし";
pn ("これは文字列1で".." これは文字列2です");
pn ("これは文字列位置で", "これは文字列2です");

関数はSTEADの能力を強力にします。たとえば:

aple = obj {
	nam = 'リンゴ',
	dsc = function(s)
		if not s._seen then
			p 'テーブルの上に{なにか}があります。';
		else
			p 'テーブルの上に{リンゴ}があります。';
		end
	end,
	act = function(s)
		if s._seen then
			p 'リンゴでした!';
		else
			s._seen = true;
			p 'うーん、でもそれはリンゴでした!';
		end
	end,
};

属性またはハンドラが関数のように並んでいる場合は、最初の引数 (s) それ自身がオブジェクトになります。サンプルコードでは動的パーツが文字列“テーブルの上になにかがあります。”を含んでいます。もしプレイヤーが“なにか”を使うと、“リンゴ”オブジェクトの変数‘seen’の値が“true”に設定されます。プレイヤーはそれがリンゴであることを知ります。

‘s._seen’とは変数‘_seen’がオブジェクト“s”(このケースでは“リンゴ”)に配置されていることを意味します。アンダースコア“_”は、この値がゲームのセーブファイルに保存されることを意味しています。

バージョン1.2.0以降では以下のような変数の定義ができます:

global {
    global_var = 1;
}
main = room {
    var {
        i = "a";
        z = "b";
    };
    nam = '最初の部屋';
    var {
        new_var = 3;
    };
    dsc = function(s)
        p ("i == ", s.i);
        p ("new_var == ", s.new_var);
        p ("global_var == ", global_var);
    end;

バージョン1.2.0以降では関数の定義を以下のように記述できます:

	dsc = code [[
		if not self._seen then
			p 'テーブルの上に{なにか}があります。';
		else
			p 'テーブルの上に{リンゴ}があります。';
		end
	]],

code の実行中は、オブジェクトはそれ自身が書かれた“self”の変数になります。arg1からarg9のargs[]配列はすべての引数を保持します。

注意:変数のうち、room、game、obj、player、globalで定義されているものはすべてセーブファイルに保存されます。アンダースコアではじまる変数またはvar/globalで定義されている変数も保存されます。

以下のような変数の型が保存されます:

  • 文字列
  • ブール値
  • オブジェクトへのリンク
  • 構造体

説明文を一切表示しないでなにかをしようとする場合ハンドラが必要になります。たとえば以下のようなコードです:

button = obj {
	nam = "ボタン",
	dsc = "部屋の壁に{ボタン}がある。",
	act = function (s)
		here()._dynamic_dsc = [[ボタンを押したら部屋の様子が一変した。
			テーブルセットのそばにあった本棚が消え、
			奇妙なデバイスがそこに現れた。]];
		return true;
	end,
}
r12 = room {
	nam = '部屋',
	_dynamic_dsc = '部屋の中にいる。',
	dsc = function (s) return s._dynamic_dsc end,
	obj = {'button'}
}

このケースではactハンドラが部屋の説明文を変更するのに使われていますが、説明文を後ろに追加することは想定されていません。これを実行するためにはハンドラからtrueを返す必要があります。この戻り値はこのアクションが成功したことを意味しますが、追加の説明文を表示することは要求できません。

プレイヤーのアクションが無理であることを示したい場合は、ただハンドラから何も返しません。このケースではデフォルトの説明文がこのアクションのために表示されます。デフォルトのアクションはgame.actハンドラに設定します。デフォルトのアクションは一般的に不可能なアクションのための説明文として利用されます。

上記のサンプルコードの中で、動的に表示される説明文として使われている新しい変数 _dynamic_dsc に注意してください。これは新しい説明文がセーブデータに保存されることを保証します。シーン名‘dsc’は先頭にアンダースコアを付けず、必ず小文字にします。

以上の結果、サンプルコードは次のようになります:

button = obj {
	nam = "ボタン";
	dsc = "部屋の壁に赤い大きな{ボタン}がある。";
	act = function (s)
		here().dsc = "部屋は妙な様子だ……";
		p [[ボタンを押すと部屋の様子が一変した。
			テーブルセットのそばにあった本棚が消え、
			奇妙なデバイスがそこに現れた。]];
	end
}
r12 = room {
        forcedsc = true;
	nam = '部屋';
        var {
	    dsc = '私は今部屋の中にいる。';
        };
	obj = {'button'}
}

6. インベントリ

プレイヤーが取ることができるオブジェクトを作るもっとも簡単な方法は“tak”ハンドラを定義することです。

たとえば次のようなコードになります:

aple = obj {
	nam = 'リンゴ',
	dsc = 'テーブルの上に{リンゴ}があります。',
	inv = function(s)
		inv():del(s);
		return 'リンゴを食べました。';
	end,
	tak = 'リンゴを取りました。',
};

上記のコードではプレイヤーが“リンゴ”オブジェクトを使用したとき、リンゴはシーンから取り除かれインベントリに追加されます。プレイヤーがインベントリを使用すると“inv”ハンドラが呼ばれます。

サンプルコードではプレイヤーがインベントリ内のリンゴを使用すると、リンゴを食べたことになります。

7. シーンの遷移


ひとつのシーンからほかのシーンへ移るには“way”属性のリストを使います。

room2 = room {
	nam = 'ホール',
	dsc = 'あなたは広いホールにいます。',
	way = { 'main' },
};

main = room {
	nam = 'メインルーム',
	dsc = 'あなたは広い部屋の中にいます。',
	obj = { 'tabl' },
	way = { 'room2' },
};

このコードでは“main”と“room2”シーンの間を移動します。前述のとおり、“nam”は関数で作ることも可能で、シーン名を動的に生成できます。たとえば、プレイヤーがそこに入るまでシーン名を知らせたくないような場合です。

2つのシーンを切り替えるときゲームエンジンは現在のシーンから“exit”ハンドラを呼び、次に行き先のシーンで“enter”ハンドラを呼びます。たとえば次にのようなコードになります:

room2 = room {
	enter = 'あなたはホールに入りました。',
	nam = 'ホール',
	dsc = 'あなたは広いホールにいます。',
	way = { 'main' },
	exit = 'あなたはホールを出ました。',
};

“exit”と“enter”の文字列は関数から生成することもできます。最初の引数はオブジェクト自身で、2番目の引数はプレイヤーが行こうとする部屋(“exit”のために)か、プレイヤーが去ろうとする部屋(“enter”のため)になります。たとえば次のようになります:

room2 = room {
	enter = function(s, f)
		if f == main then
			return 'あなたは部屋から来ました。';
		end
	end,
	nam = 'ホール',
	dsc = 'あなたは広いホールにいます。',
	way = { 'main' },
	exit = function(s, t)
		if t == main then
			return '戻りたくないよ!', false
		end
	end,
};

ご覧のようにハンドラは2つの値を返すことができます。文字列とステータスです。このサンプルコードでは、プレイヤーがホールから“main”に戻ろうとしたとき、“exit”関数が“false”を返します。“false”はプレイヤーがシーンに移れないことを意味します。同じロジックを“enter”と“tak”でも使えます。

p/pn/prを使ってステータスを返すこともできます。次のようなコードになります:

room2 = room {
	enter = function(s, f)
		if f == main then
			p 'あなたは部屋から来ました。';
		end
	end,
	nam = 'ホール',
	dsc = 'あなたは広いホールにいます。',
	way = { 'main' },
	exit = function(s, t)
		if t == main then
			p '戻りたくないよ!'
                        return false
		end
	end,
};

シーンを移る瞬間の“enter”ハンドラの呼び出しでは、現在のシーンが変更されない場合があることに注意してください。バージョン1.2.0では2つのアクションハンドラが追加されました。“left”と“entered”です。これらは遷移の直後に呼ばれ、遷移が禁止されないケースでの使用が推奨されています。

8 オブジェクトの操作

プレイヤーはインベントリにあるオブジェクトをほかのオブジェクトに対して使うことができます。ここでは“use”ハンドラがインベントリのオブジェクトから呼ばれて、ほかのオブジェクトから“used”されるケースを扱います。たとえば次のようになります:

knife = obj {
	nam = 'ナイフ',
	dsc = 'テーブルの上に{ナイフ}があります。',
	inv = '鋭い!',
	tak = 'ナイフを取りました!',
	use = 'あなたはナイフを使おうとしました。',
};

tabl = obj {
	nam = 'テーブル',
	dsc = '部屋の中に{テーブル}があります。',
	act = 'うーん、ただのテーブルのようです。',
	obj = { 'aple', 'knife' },
	used = 'あなたはテーブルになにかをしようとしました。',
};

プレイヤーがナイフを取ってテーブルに対して使おうとすると、“use”と“used”ハンドラの文字列が表示されます。“use”と“used”には関数を与えることもできます。最初の引数はそれ自身のオブジェクトになります。2つめの引数は“use”のためのものでアクションの対象となるオブジェクトであり、“used”はアクションを実行したオブジェクトになります。

“use”が“false”ステータスを返すと“used”は呼ばれません(それが存在すればですが)。“used”ステータスは無視されます。

たとえば次のようになります:

knife = obj {
	nam = 'ナイフ',
	dsc = 'テーブルの上に{ナイフ}があります。',
	inv = '鋭い!',
	tak = 'ナイフを取りました!',
	use = function(s, w)
		if w ~= tabl then
			p 'それには切りつけたくない。'
                        return false
		else
			p 'あなたはテーブルに自分のイニシャルを掘りました。';
		end
};

プレイヤーはナイフをテーブルのみに使うことができます。

9. プレイヤーオブジェクト


STEADの内部ではプレイヤーは“pl”オブジェクトで表されます。オブジェクトの型は“player”です。エンジン内部では次のように生成されます:

pl = player {
	nam = "Incognito",
	where = 'main',
	obj = { }
};

“obj”属性はプレイヤーのインベントリを表します。

10. gameオブジェクト

ゲームもまた“game”型の“game”オブジェクトによって表されます。エンジン内部では次のように定義されています:

game = game {
	nam = "INSTEAD -- シンプルテキストアドベンチャー インタプリタ v"..version..
             " '2009 by Peter Kosyh",
	dsc = [[
コマンド:^
    look(あるいはenter), act <モノ> (あるいは何), use <何> [何に], go <場所>,^
    back, inv, way, obj, quit, save <ファイル名>, load <ファイル名>.]],
	pl ='pl',
	showlast = true,
};

このようにオブジェクトは現在のプレイヤーオブジェクト‘pl’への参照と引数を保持します。たとえばゲームソースの先頭にテキストのエンコードの種類を設定できます:

game.codepage="UTF-8"; 

エンコードの種類の指定は、すべてのバージョンのUNIX版インタプリタとWindows版0.7.7以降でサポートされています。

さらに“game”オブジェクトはデフォルトのハンドラを持っています。“act”、“inv”、“use”などです。これらはプレイヤーのアクションに対応するハンドラがほかに見つからない場合にデフォルトで呼び出されます。

game.act = 'それは無理です。';
game.inv = 'うーん、それは常軌を逸脱しています。';
game.use = '使えませんでした。';

11. 属性リスト

属性(“way”や“obj”などのような)リストは、それら自身が動的に定義されたシーン同士の接続、ライブオブジェクトなどを実装することを可能にします。

リストを操作するメソッドは“add”、“del”、“look”、“srch”、“purge”、“replace”があります。もっともよく使われるものは“add”と“del”です。

“add”はリストに追加し、“del”はリストから削除します。“purge”は削除しオブジェクトを無効にします。“srch”は検索です。“replace”はオブジェクトを置換します。“del”、“purge”、“replace”、“srch”はオブジェクト自身または識別子の引数として使用できます。またオブジェクト名にもできます。

バージョン0.8以降では、オブジェクト自身を“add”の引数として使用できます。このバージョンからはリスト中の位置を示す2つめの引数がオプションとして追加されています。“set”を使ってインデックスを指定しリストを修正できます。次のようなコードになります:

objs():set('knife',1);

上記のサンプルコード“ inv():del('aple');”にあるとおり、リンゴを食べるコードで使われています。

“inv()”は関数であり、インベントリのリストを返します。“:”の後の“del”は関数であり、インベントリの要素を1個削除します。

同様に“tak”は次のように実装されます:

knife = obj {
	nam = 'ナイフ',
	dsc = 'テーブルの上に{ナイフ}がある。,
	inv = '鋭い!',
	act = function(s)
		objs():del(s);
		inv():add(s);
	end,
};

オブジェクトリストの追加と削除とは別に、“enable()”と“disable()”を使ってオンとオフを切り替えることができます。たとえば“knife:disable()”のような使い方になります。これは“knife”オブジェクトがシーンの説明文から消えることを意味します。後で“knife:enable()”に切り替えることもできます。

バージョン0.9.1以降で追加された“enable_all()”と“disable_all()”は入れ子のオブジェクトに使えます。

バージョン0.9.1以降の“zap”と“cat”も使用できます。zap()はすべての要素を削除し、cat(b, [pos])はリストbのすべての要素を現在のリストの位置[pos]に追加します。

注意:現在のところ、オブジェクトとインベントリの操作には put/get/take/drop/remove/seen/have などの高レベルの関数を使うことが推奨されています。

12. オブジェクトを返す関数

STEADで定義されているいくつかの関数は、オブジェクトを返すときにたびたび使われます。たとえば次のようなものがあります:

  • inv() インベントリのリストを返します。
  • objs() 現在のシーンのオブジェクトのリストを返します(バージョン0.8.5以降はオプションの引数が1個あります。シーンのオブジェクトを返すための引数です)。
  • ways() 現在のシーンから移動できるシーンのリストを返します(バージョン0.8.5以降はオプションの引数が1個あります。シーンのリストを返すための引数です)。
  • me() プレイヤーオブジェクトを返します。
  • here() 現在のシーンを返します(バージョン0.8.5以降ではほかに、オブジェクトがどのシーンにあるかを返すwhere(obj)関数があります。これを使えるのはput/drop/moveで設定されたオブジェクトが存在する場合のみです)。
  • from() 直前のシーン名を返します。
  • seen(obj, [scene]) シーン内に存在する場合、あるいはdisable()で無効にされていない場合にオブジェクトを1個返します。
  • have(obj, [scene]) インベントリ内に存在する場合オブジェクトを1個返します。
  • exist(obj, [scene]) シーン内に存在する場合オブジェクトを1個返します。
  • live(obj) (訳注:有効な?)lifeの中に存在すればオブジェクトを1個返します。
  • path(obj,[where]) オブジェクトの検索をします。無効化されたアイテムに対しても有効です。

これらの関数と“add”、“del”を組み合わせて動的にシーンを変えることができます。たとえば以下のような使い方があります:

  • ways():add('nexroom'); --次に進むシーンへの選択肢を追加します。
  • objs():add('chair'); 現在のシーンにオブジェクトを1個追加します。

ref()関数はオブジェクトへの参照からオブジェクトを取得します:

たとえば‘home’シーンにオブジェクトに追加できます:

ref('home').obj:add('chair');

これは短くすると次のコードと同等です:

home.obj:add('chair');

あるいは0.8.5以降では:

objs('home'):add('chair');

さらに:

put('chair', 'home');

以下のコードも同じです:

put(chair, home);

バージョン0.8.5以降のderef(o)はオブジェクトへの参照文字列を1個返します。

13. 補助関数群

STEADには高レベルの関数がいくつかあります。ゲームを書くときに非常に便利です。

  • have() オブジェクトがインベントリにあるかどうかチェックします。オブジェクトの“nam”属性を使うかまたは参照を使います。たとえば次のようなコードです:

...
act = function(s)
	if have('knife') then
		return 'ナイフを入手した!';
	end
end
...

次のサンプルコードは等しく動作します。

...
	if have 'knife' then
...
	if have (knife) then
...

次のサンプルコードはアドレスモード以上で使います。

  • move(o, w) 現在のシーンから別のシーンへオブジェクトを移動させます。たとえば:

move('mycat','inmycar');

任意のシーンからオブジェクトを移動させたい場合は、“del”で元のシーンにあるオブジェクトを削除する必要があります。オブジェクトを作るのは複雑な手順になりますが、オブジェクトの位置(room)を保存する関数と元のシーンからオブジェクトを削除する関数をそれぞれ書く必要があります。“move”の3つ目の引数で初期の位置(room)を設定できます。

move('mycat','inmycar', 'forest'); 

バージョン0.8以降では“move”に似た機能を持つ“movef”関数が用意されています。この関数はリストの先頭にオブジェクトを追加します。

  • seen(o) 現在のシーンに存在しているオブジェクトを返します:

	if seen('mycat') then
		move('mycat','inmycar');
	end

バージョン0.8.6以降ではシーン名を指定する2つ目の引数がオプションとしてあります。

  • drop(o) インベントリにあるオブジェクトをシーンに捨てます。

drop('knife');

バージョン0.8以降では“drop”に似た“dropf”関数があります。これはリストの先頭にオブジェクトを追加します。バージョン0.8.5以降ではオプションで2つ目の引数があります。オブジェクトを置くシーン名です。また0.8.5以降には似た関数で“put/putf”があります。これらの関数はインベントリからオブジェクトを削除しません。

バージョン0.8.9以降では“remove(o, [from])”関数があります。これは現在のシーン、または2つ目の引数で指定したシーンからオブジェクトを削除します。

  • take(o) オブジェクトを取ります。インベントリに追加されます。

take('knife');

バージョン0.8.5以降ではオプションで2つ目の引数があります。オブジェクトを取るシーン名です。

  • taken(o) オブジェクトがすでに取られている場合 true を返します(“tak”または“take()”で取られたオブジェクト)。

  • rnd(m) 1からmまでの乱数を生成します。

walk(w) 指定したシーンwに移動します。文字列の表示に“p/pn/pr”関数を使っていない場合、ハンドラは“walk”が返す値を返します。たとえば次のようなコードになります:

act = code [[
        pn "次の部屋に行きます……。"
        walk (nextroom);
]]
...
act = code [[
        return cat('次の部屋に行きます……。', walk (nextroom));
]]

注意:walk 関数で呼び出した後、ハンドラはハンドラの end または return が来るまで実行を続けます。

  • change_pl(p) プレイヤーを切り替えます(インベントリと現在の位置が別のプレイヤーのものになります)。関数は新しいプレイヤーのシーンの説明文とハンドラから渡された戻り値を返します(“walk()”の項目を参照)。

mycar = obj {
	nam = '自分の車',
	dsc = '小屋の前にトヨタの{ピックアップトラック}が停めてある。',
	act = function(s)
		return walk('inmycar');
	end
};

  • walkback() 直前のシーンに戻ります。
  • back() 直前のシーンに戻ります。キャラクタとの会話からシーンに戻るケースでは、シーンの“dsc/enter/entered”関数は呼ばれません。会話のシーン内部で使ってください。
  • walkin(room) シーンを移動します。現在のシーンの“exit/left”は呼ばれません。
  • walkout() 直前のシーンの戻ります。そのシーンの“enter/entered”は呼ばれません。
  • time() 現在のゲームでプレイヤーが移動した回数を返します。
  • cat(…) 文字列または連結された文字列を返します。最初の引数が nil の場合は nil を返します。
  • par(…) 文字列または最初の引数文字列で分割された文字列を返します。
  • disable/enable/disable_all/enable_all それぞれオブジェクトを有効化/無効化します。
  • visited([where]) シーンに訪れた回数を返します(nilになります)。
  • path(obj,[where]) オブジェクトがどのシーンにあるか検索します。オブジェクトが無効化されていても機能します。
  • nameof(obj) オブジェクト名(nam属性)を取得します。
  • purge (obj, [where]) remove と同じ機能でオブジェクトを削除します。無効化されたオブジェクトにも機能します。
  • replace (obj, onobj, [where]) オブジェクトを置き換えます。
  • disabled(obj) オブジェクトが無効化されていれば true を返します。

14. ダイアログ

ダイアログは会話シーンなどに用いられ、オブジェクトの選択肢を表示します。シンプルなものは以下のようなコードです:

povardlg = dlg {
	nam = 'キッチンにて',
	dsc = '白いシェフ帽をかぶった太めの女性が疲れた視線でこっちを見た。',

	obj = {
	[1] = phr('「その緑色のやつ……ください。そう、その豆も!」',
	'「あいよ!」'),

	[2] = phr('「プライドポテトとベーコンください!」',
	'「ボナペティ!」'),

	[3] = phr('「ガーリックスープを2つ!」',
	'「シェフの自信作ですよ!」'),

	[4] = phr('「なんでもいいから、柔らかくて食べやすいものをください。\
	俺って胃が悪くて……。」', '「オートミールいっちょ!」'),
	},
};

“phr”で各セリフを用意します。セリフには質問と答え、リアクションが含まれます(サンプルにはリアクションがありません)。プレイヤーがセリフの1つを選択すると、それは一度無効化されます。すべてのセリフが無効になるとダイアログは終了します。リアクションは1行で書かれたLuaのコードであり、セリフが無効化されたときに実行されます。たとえば次のようなコードになります:

food = obj {
	nam = '食べ物',
	inv = function (s)
		inv():del('food');
		return 'I eat.';
	end
};

gotfood = function(w)
	inv():add('food');
	food._num = w;
	back();
end

povardlg = dlg {
	nam = 'キッチンにて',
	dsc = '白いシェフ帽をかぶった太めの女性が疲れた視線でこっちを見た。',
	obj = {

	[1] = phr('「その緑色のやつ……ください。そう、その豆も!」',
	'「あいよ!」', [[pon(); gotfood(1);]]),

	[2] = phr('「フライドポテトとベーコンをください!」',
	'「ボナペティ!」', [[pon(); gotfood(2);]]),

	[3] = phr('「ガーリックスープを2つ!」',
	'「シェフの自信作ですよ!」, [[pon(); gotfood(3);]]),

	[4] = phr('「なんでもいいから、柔らかくて食べやすいものをください。\
	俺って胃が悪くて……。」', '「オートミールいっちょ!」',
	[[pon(); gotfood(4);]]),
	},
};

上記のサンプルコードではプレイヤーがディナーを選択しています。メニューを受け取った後(選択した“food._num”の値が記録されます)、プレイヤーはダイアログからシーンに戻ります。

リアクションにはLuaのコードを記述します、STEADにはあらかじめ定義された便利な関数が用意されています。

  • pon(n..) n番のセリフを有効にします(サンプルコードでは同じメニューを何度も受け取れます)。
  • poff(n…) n番のセリフを無効にします。
  • prem(n…) n番のセリフを削除します(削除されたセリフは“pon()”で再有効化できません)。
  • pseen(n…) n番のセリフが可視化されていればtrueを返します。
  • punseen(n..) n番のセリフが不可視であればtrueを返します。

引数がない場合は現在のセリフに対して機能します。

プレイヤーはシーンに入ったときダイアログに入ります:

povar = obj {
	nam = 'シェフ',
	dsc = '{シェフ}がいる。',
	act = function()
		return walk('povardlg');
	end,
};

1つのダイアログからほかのダイアログに移動することができます。またダイアログを階層化することもできます。

ダイアログを初期化する際にセリフを隠し、一定の条件で表示させることができます。

facectrl = dlg {
	nam = '顔認証システム',
	dsc = '感じの悪い、あからさまに意地の悪そうな顔をした太った警備員がいる。',

	obj = {
	[1] = phr('「ベリン氏の講義を聞きに来たんだが。」', 
	'“「見ない顔だな。お前は誰だ?」^警備員はニヤリと笑った。^\
	「まともなやつしか中に入れないようにと言われてるんでな」”',
	[[pon(2);]]),

	[2] = _phr('「俺は招待状を持ってるぞ!」', 
	'「知ったことか!自分の姿を鏡で見てみろ!ベリン氏の講義を聞きに来ただと?」^\
	警備員はベリンの名前を口にして一瞬だけ迷ったが、^「ともかく出て行け!」',
	[[pon(3,4)]]),

	[3] = _phr(' 「お前の顔に蹴りを入れてやる!」',
	 '「へえ。やってみろ」^
	力強い腕が俺をつかんで廊下に放り出した。バラバラにされなかったのはラッキーかもしれん。',
	[[poff(4)]]),

	[4] = _phr('「このイボイノシシ野郎!招待状を持ってるって言ってるだろうが。」',
	'「なんだとお!?」警備員の目が充血してきた。^\
	強力なキックで廊下に蹴り出された。最悪だ……。',
	[[poff(3)]]),
	},
	exit = function(s,w)
		s:pon(1);
	end,
};

“_phr”は無効化されたセリフを表し、後で有効化できます。サンプルコードではダイアログに“pon”、“poff”、“prem”関数を使っています(“exit”も参照のこと)。

現在のダイアログに対してだけでなく、任意のダイアログオブジェクトに対して有効化・無効化・削除・チェックができます。“pon”、“poff”、“prem”、“pseen”、“pusneen”関数を使用します。たとえば以下のような使い方です:

shopman:pon(5);

15. 軽量オブジェクト

ゲームに表現力を持たせるには限界があり、そのためにシーンは複雑なコードで埋め尽くされることが多々あります。それを改善するために軽量のオブジェクトを使うことができます。たとえば以下のようなコードになります:

sside = room {
  nam = '南側',
  dsc = [[研究施設の南側の壁に来ている。]],
  act = function(s, w)
    if w == "玄関" then
      ways():add('stolcorridor');
      p "俺は玄関に向かって歩いていった。ドアには食堂と書かれている。^\
          ほう……ここから入れそうだな。";
    elseif w == "人々" then
      p 'ひとり出て行ったがなんだか満足そうな表情だ。';
    end
  end,
  obj = { vobj("玄関", "東の隅に小さな{玄関}がある。"),
    vobj("人々", "ときどき玄関のドアから{人々}が出入りしている。")},
};

上記のコードにあるように“vobj”は、プレイヤーが操作可能な軽量の静的オブジェクトを作ることができます(シーン内の“act”ハンドラの定義はオブジェクト名をチェックします)。“vobj”は3つ目の引数で“used”関数を呼び出します。これはオブジェクトが仮想オブジェクトのように機能します。

“vobj”の構文は“vobj(オブジェクト名, 説明文);”です。2つ目の引数にはシーンの“act”と“used”ハンドラに渡すキーワードが含まれます。

“vway”は“vobj”オブジェクトが修正されたものです。これは参照を作るためのものです。“vway”の構文は“vway(オブジェクト名, 説明文, 行き先のシーン名);”です。たとえば次のようなコードになります:

  obj = { vway("次へ", "{ここ}をクリックしてください。", 'nextroom') }

“vobj”と“vway”オブジェクトはシーン内で動的に設定できます。“add”と“del”関数を使います。たとえば次のようなコードです:

  objs(home):add(vway("次へ", "{次へ}。", 'next_room'));
-- ここになにかコードを書く
  home.obj:del("次へ");

シンプルなシーン“vroom”も使えます。構文は“vroom(行き先の表示, 行き先のシーン名)”になります。次のようなコードです:

  home.way:add(vroom("西へ行く", 'mountains');

16. 動的なイベント

実行するたびに配列のインデックスを増加させるようなハンドラを定義できます。たとえば次のようなコードになります:

mycat = obj {
  nam = 'バルシク',
  lf = {
    [1] = 'バルシクがボストンバックの中でもぞもぞと動いている。',
    [2] = 'バルシクがボストンバックの中から様子をうかがっている。',
    [3] = 'バルシクがボストンバックの中でのどをゴロゴロと鳴らしている。',
    [4] = 'バルシクがボストンバックの中で震えている。',
    [5] = '俺はボストンバックの中にいるバルシクの体温を感じた。',
    [6] = 'バルシクがボストンバックの中から顔を出してまわりを見回した。',
  },
  life = function(s)
    local r = rnd(6);
    if r > 2 then
      return;
    end
    r = rnd(6);
    return s.lf[r];
  end,
....

profdlg2 = dlg {
  nam = 'ベリン',
  dsc = 'ベリンは顔色が悪い。呆然と俺のショットガンを凝視している。',
  obj = {
    [1] = phr('「俺は猫を取り返しに来たんだ」',
  '俺はベリンの手からバルシクをひったくってボストンバックに押し込んだ。',
    [[inv():add('mycat'); lifeon('mycat')]]),
....

オブジェクトまたはシーンは“life”ハンドラを持つことができます。これはオブジェクトまたはシーンに、“lifeon”によって“生きている”オブジェクトのリストに追加された場合、ゲームが進行するたびに呼ばれます。この“生きている”オブジェクトが必要でなくなったら“lifeoff”関数で削除することを忘れないようにしてください。たとえば“exit”ハンドラ内などで行います。

“life”は文字列を返します。この文字列はシーン内の最後に表示されます。

バージョン0.9.1以降では2つ目のブール値の戻り値があります。たとえば次のようになります:

return '警備員が部屋に入ってきた。', true
-- このイベントはオブジェクトの説明文の前に表示されます。

または:

p '警備員が部屋に入ってきた。'
return true
-- このイベントはオブジェクトの説明文の前に表示されます。

17. グラフィックとサウンド

グラフィックインタプリタは、シーンに“pic”属性を持ち画像ファイルへのパスを保持しています。たとえば次のようなコードです:

home = room {
  pic = 'gfx/home.png',
  nam = '自宅',
  dsc = '俺は今家にいる。',
};

もちろん“pic”は関数としても使えます。これは開発者の能力を大きく補強します。現在のシーンに“pic”属性が定義されていない場合は、“game.pic”属性が代わりに使われます。“game.pic”が定義されていない場合は画像は表示されません。

バージョン0.9.2以降ではアニメーションGIFを表示できます。

バージョン0.9.2以降では“img”関数を使って説明文などのテキストやインベントリなどにも画像を配置できます。たとえば次のようなコードになります:

knife = obj {
  nam = 'Knife'..img('img/knife.png'),
}

バージョン1.3.0以降ではテキストの回りこみに対応しています。“imgl/imgr”関数を使い右と左に画像を配置できます。画像にはリンクはできません。

画像のまわりに隙間を空けるには“pad:”を使います。たとえば:

imgl 'pad:16,picture.png' -- 16ピクセル;
imgl 'pad:0 16 16 4,picture.png'
                    -- 上0ピクセル, 右16ピクセル, 下16ピクセル, 左4ピクセル
imgl 'pad:0 16,picture.png'
                    -- 上0ピクセル, 右16ピクセル, 下0ピクセル, 左16ピクセル

次のように空の画像を配置できます:

dsc = img 'blank:32x32'..[[ブランクイメージの線。]];
dsc = img 'box:32x32,red,128'..[[赤い半透過の正方形と線。]];

現在のバージョンでは“disp”属性を使うことができます:

knife = obj {
  nam = 'ナイフ';
        disp = 'ナイフ'..img('img/knife.png'),
}

インタプリタは“set_music(ファイル名)”関数で定義されたサウンドファイルをループ再生できます。

次のようなコードになります:

street = room {
  pic = 'gfx/street.png',
  enter = function()
    set_music('mus/rain.ogg');
  end,
  nam = '通りの真ん中',
  dsc = '雨が降っている。,
};

バージョン1.0.0以降では基本となる画像とオーバーレイから新しい画像を作って表示できます:

pic = 'gfx/mycat.png;gfx/milk.png@120,25;gfx/fish.png@32,32'

get_music() 現在のサウンドのトラック名を返します。

バージョン0.7.7以降では“set_music()”関数で再生回数を指定できる引数が追加されています。“get_music_loop()”関数で現在の再生回数を取得できます。戻り値が-1の場合は再生がすでに終了したことを意味します。

バージョン0.9.2以降では“set_sound()”関数でサウンドファイルを再生できます。“get_sound()”関数は再生されたファイル名を返します。

再生を止めるには“stop_music()”関数を使います。バージョン1.0.0以降で使えます。

“is_music()”関数は現在再生中かをブール値で返します。バージョン1.0.0以降で使えます。


添付ファイル
記事メニュー
最近更新されたスレッド
ウィキ募集バナー