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

INSTEAD @ wiki

18. アドバイス

最終更新:

instead

- view
管理者のみ編集可

18. アドバイス


ゲームファイルの分割

“dofile”関数を使って、分割されたソースコードをインクルードできます。“dofile”はグローバルな関数として使い、“main.lua”がパースされる際に分割されたソースコードをロードします。次のようなコードになります:

-- main.lua
dofile "episode1.lua"
dofile "npc.lau"
dofile "start.lua"

動的にインクルードする(現在のオブジェクトとシーンの再定義を可能にする)ためのコードは“gamefile”関数を使います。以下のようになります:

...
act = code [[ gamefile ("episode2.lua"); ]]
...

以前にロードされたファイルのために確保されたメモリを気にすることなく、新しいファイルをロードして新しいゲームのように実行できます。

...
act = code [[ gamefile ("episode3.lua", true); ]]
...

モジュール

バージョン1.2.0以降では“require”関数でモジュールを呼び出せます。現在のところ以下のようなモジュールが使えます:

  • dbg デバッグ用モジュール(デバッガを有効にします)。
  • walk goの代わりにシーンを移動するために改善されたモジュールです。
  • xact オブジェクトへの参照を返します。
  • input キーボード入力を補助するためのものです。
  • click シーンの画像上でのマウスクリックを捕捉します。
  • vars 変数の定義をします。
  • prefs 参照を扱います。
  • snapshots スナップショットを取ります。
  • format 指定した書式で文字列を表示します。
  • object 改善されたオブジェクトです。
  • theme ゲーム自身でテーマを適用するためのモジュールです。

モジュールはたとえば次のように使います:

--$Name: My game!$
instead_version "1.2.0"
require "para"
require "dbg"
...

バージョン1.2.0以降では“vars”、“object”、“walk”が自動的に読み込まれます。

“prefs”オブジェクト(“prefs”モジュール内のオブジェクトも含めて)はゲームへの参照を保持できます。たとえばプレイヤーの進捗状況や実行回数を数えるのに役に立ちます。

require "prefs"
...
    prefs.counter = 0
...
    exit = function(s)
        prefs.counter = prefs.counter + 1
        prefs:store()
    end
...
    enter = function(s)
        return 'あなたがこのゲームをプレイするのは、これで'..prefs.counter..'回目。';
    end
...
    act = function(s)
        prefs:purge()
        return "設定がクリアされました。"
    end

“xact”モジュールは、ほかのオブジェクトやリアクション、また“life”関数などからオブジェクトを参照するときに使います。これらの参照の記述は“{object:string}”です。

...
    act = [[私はテーブルの下に{myknife:ナイフ}があることに気付いた。]]
...

“object”の参照の一部にはオブジェクトの値かオブジェクトの名前が使えます。このモジュールは“xact”と“xdsc”オブジェクトでも定義されています。

“xact”は簡単なリアクションのためのオブジェクトです。たとえば次のようなコードになります:

main = room {
    forcedsc = true;
    dsc = [[作者のコメント:私はこのゲームを{note1:かかって}作った。]];
    obj = {
        xact('note1', [[十年以上]]);
    }
}

リアクションは以下のようなコードを含むことができます:

       xact('note1', code [[p "十年以上"]]);

“xdsc”を使うとオブジェクトリストに複数の説明文を挿入できます。

main = room {
    forcedsc = true;
    dsc = [[私は今部屋の中にいる。]];
    xdsc = [[{anapple:リンゴ}と{aknife:ナイフ}がある。]];
    other = [[それから{achain:チェーン}と{atool:ノコギリ}もある。]];
    obj = {
        xdsc(), -- 'xdsc関数のデフォルト'
        xdsc('other'),
        'apple', 'knife', 'chain', 'tool',
    }
}

“xroom”オブジェクトも使えます:

main = xroom {
    forcedsc = true;
    dsc = [[私は今部屋の中にいる。]];
    xdsc = [[{anapple:リンゴ}と{aknife:ナイフ}がある。]];
    obj = {
        'apple', 'knife', 'chain', 'tool',
    }
}

“input”モジュールは簡単な文字入力の枠を実装するのに使います。“click”モジュールはシーンに表示されている画像をユーザーがマウスクリックした場合をフォローするのに使います。

“para”モジュールは文章にインデントを追加して段落を作ります。

“format”モジュールは文字列の出力を整形します。デフォルトではすべて無効になっています:

  • format.para = false 段落にインデントを追加します。
  • format.dash = false 引用符のダッシュを2つに変更します。
  • format.quotes = false << >>で囲まれた引用を変更します。
  • format.filter = nil ユーザーが指定するテキストの整形です。

それぞれの機能を使うには“para/dash/quotes”モジュールを有効にする必要があります。

テキストの整形

関数で簡単なテキストのフォーマットを指定できます:

  • txtc() 中央寄せ
  • txtr() 右詰め
  • txtl() 左詰め
  • txttop() 上詰め
  • txtbottom() 下詰め
  • txtmiddle() 行の中心(デフォルト)

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

main = room {
  nam = 'Intro',
  dsc = txtc('ようこそ!'),
}

以下のようなテキスト表示のスタイルを指定できます:

  • txtb() ボールド
  • txtem() エンボス
  • txtu() 下線付き
  • txtst() 取り消し線付き

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

main = room {
  nam = 'イントロ',
  dsc = 'あなたは今'..txtb('メイン')..'ルームの中にいる。',
}

バージョン1.1.0以降では“txtnb()”関数を使い、ラップされていない文字列を表示できます。

たとえば:

main = room {
  nam = 'イントロ',
  dsc = 'あなたは今'..txtnb('メイン')..'ルームの中にいる。',
}

メニュー

メニューコンストラクタと使ってインベントリのエリアにメニューを表示できます。menuハンドラはマウスが1回クリックされた後に呼ばれます。ハンドラに文字列が返されない場合はなにも変化しません。ポケットの中身を表現すると、たとえば次のようなコードになります:

pocket = menu {
  State = false,
  nam = function(s)
    if s.State then
      return txtu('ポケット');
    end
    return 'ポケット';
  end,
  gen = function(s)
    if s.State then
      s:enable_all();
    else
      s:disable_all();
    end 
  end,
  menu = function(s)
    if s.State then
      s.State = false;
    else
      s.State = true;
    end 
    s:gen();
  end,
};

knife = obj {
  nam = 'ナイフ',
  inv = 'これはナイフ。',
};

function init()
    inv():add(pocket);
    put(knife, pocket);
    pocket:gen();
end

main = room {
  nam = 'テスト',
};

プレイヤーステータス

以下はインベントリのようにプレイヤーステータスの表示を実装したものです。この文字列はアイテムのようには使えません。

global {
    life = 10;
    power = 10;
}

status = stat {
  nam = function(s)
    p ('ライフゲージ: ', life, 'パワー: ', power)
  end
};
function init()
    inv():add('ステータス');
end

“exit”と“enter”ハンドラから“walk”する

“exit”と“enter”ハンドラから“walk”ができます。

参照を動的に作る

参照を動的に作るにはいくつかの方法があります。以下の例では“vway”オブジェクトを使って参照を追加しています:

objs(home):add(vway('道', '私は森に続く{道}が伸びていることに気付いた。', 'forest'));

参照を削除するには“del”関数を使います。

objs(home):del('道');

シーン内で参照が存在するかどうか“srch”関数でチェックできます。

if not objs(home):srch('道') then
  objs(home):add(vway('道',
            '私は森に続く{道}が伸びていることに気付いた。', 'forest'));
end

動的な参照を“enter”ハンドラ内で作るかまたはゲームソースの任意の場所で作るかですが、それが要求されている場所で作れるので非常に便利です。現在のシーン内に作る場合のサンプルコードは以下のようになります:

if not seen('道') then
  objs():add(vway('道',
         '私は森に続く{道}が伸びていることに気付いた。', 'forest'));
end

また“enable()”と“disable()”で参照の有効化と無効化をする場合は以下のようになります:

  seen('道', home):disable();
        exist('道', home):enable();

“vobj”と“vway”を無効化するには:

 obj = {vway('道',
          '私は森に続く{道}が伸びていることに気付いた。', 'forest'):disable()},

それから“obj”配列のインデックスを使って参照を有効化するか、または“srch”か“seen/exist”を使って探し出し有効化します。次のようになります:

objs()[1]:enable();

ゲームソースをエンコードする(バージョン0.9.3以降)

ゲームのソースコードを見せたくない場合はコマンドラインからエンコードできます。

sdl-instead -encode <入力ファイル名> [出力ファイル名]

エンコードされたファイルをロードするには“doencfile”を使います。つまり最初にロードされる“main.lua”だけはエンコードしないプレーンテキストである必要があります。エンコードされたソースのロードは次のようになります(ゲームのファイル名はgame.luaです):

main.lua

-- $Name: 秘密のソースコード!$
doencfile("game");

注意:luac(Luaコンパイラ)はプラットフォームのコードに依存しているので使わないでください。しかしゲームのコンパイル時のメッセージはゲームコード中のバグを発見するのには役に立ちます。

ゲームのリソースをidfファイルにパッケージ化する(バージョン1.4.0以降)

画像や音、テーマなど、ゲームのすべてのリソースを1個の拡張子idfのファイルにパッケージ化できます。リソースのファイルをすべて“data”ディレクトリに配置し、以下のコマンドを実行します。

instead -idf <dataディレクトリのパス>

カレントディレクトリに data.idf ファイルが生成されます。このファイルをゲームのディレクトリに置き、ほかのリソースファイルを削除します。

以下のコマンドでゲーム全体をidfパッケージにできます:

instead -idf <ゲームのパス>

idfフォーマットのゲームファイルは、パッケージされていないゲームと同じようにコマンドラインから直接実行できます:

instead game.idf

プレイヤーを切り替える

ゲーム内にいくつかのキャラクタを用意して互いに切り替えることができます(“change_pl”を参照のこと)。同じ方法でキャラクタごとにインベントリを使い分けることもできます。

ハンドラの1つ目の引数を使う

たとえば以下のようなコードがあります:

stone = obj {
  nam = '石',
  dsc = '崖っぷちに{石}があります。',
  act = function()
    objs():del('stone');
    return '私が石を押すと下に転がり落ちていった。';
  end

“act”ハンドラの使い方は簡単です。たとえば以下のようになります:

  act = function(s)
    objs():del(s);
    return '私が石を押すと下に転がり落ちていった。';
  end

“set_music”関数を使う

サウンドを再生するために“set_music”の2つ目の引数を使うことができます。この引数はサウンドファイルの連続再生を何回繰り返すかを指定します。

liveオブジェクトから自前のサウンドプレイヤーを記述することができます。たとえば次のようになります:

コード
-- 2回目からランダムな順番で音楽のトラックを再生する
tracks = {"mus/astro2.mod",
          "mus/aws_chas.xm",
          "mus/dmageofd.xm",
          "mus/doomsday.s3m"}
mplayer = obj {
  nam = 'メディアプレイヤー',
  life = function(s)
    local n = get_music();
    local v = get_music_loop();
    if not n or not v then
      set_music(tracks[2], 1);
    elseif v == -1 then
      local n = get_music();
      while get_music() == n do
        n = tracks[rnd(4)]
      end
      set_music(n, 1);
    end
  end,
};
lifeon('mplayer');

“get_music_loop”と“get_music”関数を使って最後に再生されたサウンドを取得でき、また再度再生できます。たとえば次のようなコードです:

function save_music(s)
  s._oldMusic = get_music();
  s._oldMusicLoop = get_music_loop();
end

function restore_music(s)
  set_music(s._oldMusic, s._oldMusicLoop);
end

-- ....
enter = function(s)
  save_music(s);
end,
exit = function(s)
  restore_music(s);
end,
-- ....

バージョン0.8.5以降では“save_music”と“restore_music”を使うことができます。

liveオブジェクト

たとえば主人公に友達を作りたいとします。方法のひとつはキャラクタに“life”関数を使い、プレイヤーのいる場所に常にその友達オブジェクトを置くことです。

horse = obj {
  nam = '馬',
  dsc = 'おれの隣に{馬}が立っている。',
  life = function(s)
    if not seen('horse') then
      move('horse', here(), s.__where);
      s.__where = pl.where;
    end
  end,
};
function init()
    lifeon('horse');
end

タイマー

INSTEADのバージョン1.1以降ではタイマーオブジェクトが用意されています(SDLバージョンのみ)。タイマーはタイマーオブジェクトを管理します。

  • timer:set(ms) ミリ秒単位でタイマーの発動する間隔を指定します
  • timer:stop() タイマーを止めます
  • timer.callback(s) コールバックです。タイマーの間隔に達したら呼ばれます

タイマー関数はsteadインターフェイスのコマンドを返します。コールバックの実行後に呼ばれる必要があります。たとえば次のようなコードになります:

timer.callback = function(s)
  main._time = main._time + 1;  
  return "look";
end
timer:set(100);
main = room {
  _time = 1,
  forcedsc = true,
  nam = 'タイマー',
  dsc = function(s)
  return 'Example: '..tostring(s._time);
  end
};

キーボード操作

INSTEADバージョン1.1.0以降ではキーボード入力をサポートしています(SDLバージョンのみ)。これで入力オブジェクトを使うことができます。

  • input.key(s, pressed, key) キーボードハンドラ。第2引数は“pressed”または“release”のイベント。“key”はキーの名前

ハンドラはsteadインターフェイスのコマンドを返します。この例ではインタプリタはキーを扱いません。たとえば次のようなコードです:

input.key = function(s, pr, key)
  if not pr or key == "escape" then 
    return
  elseif key == 'space' then 
    key = ' '
  elseif key == 'return' then
    key = '^';
  end
  if key:len() > 1 then return end 
  main._txt = main._txt:gsub('_$','');
  main._txt = main._txt..key..'_';
  return "look";
end

main = room {
  _txt = '_',
  forcedsc = true,
  nam = 'キーボード',
  dsc = function(s)
    return '例: '..tostring(s._txt);
  end 
};

マウス操作

INSTEADバージョン1.1.5以降ではマウスクリックの検知をサポートします(SDLバージョンのみ)。これで入力オブジェクトを使うことができます。

input.click(s, pressed, mb, x, y, px, py) マウスクリックハンドラ。イベントは“press”か“release”。“mb”はマウスボタンのインデックス(1が左)を表す。“x”と“y”はカーソルの位置をウィンドウの左上からの座標で表す。“px”と“py”は画像がクリックされた場合の座標。マウスカーソルの位置を画像の左上からの座標で表す。

ハンドラはsteadインターフェイスのコマンドを返します。この例ではインタプリタはキーを扱いません。次のようなコードです:

input.click = function(s, press, mb, x, y, px, py)
  if press and px then
    click.x = px;
    click.y = py;
    click:enable();
    return "look"
  end
end

click = obj {
  nam = 'クリック',
  x = 0,
  y = 0,
  dsc = function(s)
    return "あなたは画像をクリックしました。座標: "..s.x..','..s.y..'.';
  end
}:disable();

main = room {
  nam = 'テスト',
  pic ='picture.png',
  dsc = 'サンプルコード。',
  obj = { 'click' },
};

以下は、現在のシーンに表示された1つの画像がクリックされたときの、階層化されたクリック関数の呼び出しを実装したコードです:

input.click = function(s, press, mb, x, y, px, py)
  if press and px then
    return "click "..px..','..py;
  end
end

game.action = function(s, cmd, x, y)
  if cmd == 'click' then
    return call(here(), 'click', x, y);
  end
end
----------------------------------------------------------------------
main = room {
  nam = 'テスト',
  pic ='picture.png',
  dsc = 'サンプルコード。',
  click = function(s, x, y)
    return "あなたは画像をクリックしました。座標: "..x..','..y..'.';
  end
};

注意:バージョン1.2.0以降ではclickモジュールが必要です。

ダイナミックオブジェクトを作る

“new”と“delete”関数でダイナミックオブジェクトを作り、また削除することができます。たとえば以下のようなコードです:

new ("obj { nam = 'テスト', act = 'test' }")
put(new [[obj {nam = 'テスト' } ]]);
put(new('myconstructor()');
n = new('myconstructor()');
delete(n)

“new”関数はオブジェクトのコンストラクタのように文字列の引数を扱います。コンストラクタはオブジェクトを1つ返します。文字列の引数はコンストラクタの関数呼び出しを含んでいます。次のようなコードになります:

function myconstructor()
  local v = {}
  v.nam = 'テストオブジェクト',
  v.act = 'test feedback',
  return obj(v);
end

このオブジェクトはゲームがセーブされるたびにセーブされます。new()関数はオブジェクトの実体を返します。オブジェクト名を得るためには“deref”関数が使えます。

o_name = deref(new('myconstructor()'));
delete(o_name);

イベントハンドラからの複雑な出力

プログラマはときどき、いくつかの条件に依存するいくつかの部品からの、イベントハンドラの出力を整形する必要が生じることがあります。このような例では“p()”関数と“pn()”関数を使うことができます。これらの関数は文字列をハンドラが持つ内部的なバッファに追加します。このバッファの中身はハンドラから返されます。

dsc = function(s)
  p "床の上に{樽}が置いてある。"
  if s._opened then
    p "樽のフタがそばに落ちている。"
  end
end

“pn()”関数はテキストに改行を追加しバッファに出力します。p()関数はそれと同じ機能を持ちますが改行の代わりに空白を1個追加します。

バージョン1.1.6以降では“pr()”関数が使えますが、これは出力される文字列の最後に何も追加しません。

バッファをクリアするためには“pclr()”関数が使えます。ユーザーアクションのステータスを文字列で返すためには“pget()”関数を使うか、または単に“return”で戻り値を返します。

use = function(s, w)
  if w == apple then
    p '私はリンゴの皮を剥いた。';
    apple._peeled = true
    return
  end
  p 'それは使えない!'
  return false; -- またはpget(), falseを返す
end

デバッグ

エラーが出た場合のLuaのスタックを見るには、sdl-insteadに“-debug”パラメタをつけて起動します。Windows版ではデバッグコンソールが表示されます。INSTEADを使わずにゲームをデバッグするには、次のようなコード“game.lua”を記述します:

dofile("/usr/share/games/stead/stead.lua"); -- stead.luaの絶対パス
dofile("main.lua"); -- ゲームのメインソース
game:ini();
iface:shell();

次にコマンドライン“lua game.lua”でゲームを起動します。この起動方法ではゲームは簡易シェルの環境で実行されます。使えるコマンドは、“ls”、“go”、“act”、“use”などがあります。

ゲーム内でデバッガを使うには次のコードを挿入してください:

require "dbg"

これは“main.lua”内のバージョン指定行の直後に記述します。F7キーでデバッガを呼び出せます。


19. テーマ
記事メニュー
最近更新されたスレッド
ウィキ募集バナー