スクリプト編集者
ゲームスクリプトを編集するには、対象ゲームの「部屋詳細ページ」にある「Mod (API) スクリプト」リンクをクリックしてください(「チャットログ」や「ゲームのコピー/拡張」などのオプションと同じ場所にあります)。 いくつかの機能が提示されます:
- 上部にあるタブの一覧。 ゲームの管理を容易にするため、複数のスクリプトを用意できます。 すべてのスクリプトは同じコンテキストで実行されることに注意してください。つまり、複数のスクリプトが同時に同じ値を上書きしようとすると、意図しない結果が生じる可能性があります。
- スクリプトコードエディタ。 このエディタを使用するか、お好みの外部エディタでスクリプトを編集し、ここに貼り付けることができます。
- 下部にある「Mod (API) コンソール」(下記参照)。
「スクリプトを保存」ボタンをクリックするたびに、ゲームのサンドボックスが再起動されます(状態オブジェクトまたはRoll20オブジェクトに永続化されていないメモリ内のデータはすべて失われます)。 新しいスクリプトを追加する場合、スクリプトを削除する場合、またはスクリプトの有効化/無効化を切り替える場合にも同様です。
Mod (API) コンソール
Mod (API) コンソールは、スクリプトへの「窓口」です。 Mod(API)スクリプトはサンドボックス環境で実行されるため、スクリプトの実行中に結果やエラー情報を直接確認することはできません。 Mod (API) コンソールは、この情報をサンドボックス外に表示します。これにより、スクリプトを編集中に情報を確認できます。 すべてのlog()コマンドがここに表示されます。また、スクリプトの実行中に発生したエラーも同様に表示されます。 詳細については、 スクリプトのデバッグに関する記事をご覧ください。
リアクティブスクリプト:イベントを監視し、オブジェクトを変更する
最初の(そして最も単純なタイプの)Mod(API)の使用法は、卓上上の変化に反応し、変化したオブジェクトに対して追加機能で応答することである。 この種のスクリプトは、ゲーム中に発生するイベントを監視する複数の関数で構成されています。 すると、それらのイベント中に渡されるオブジェクトを修正し、卓上において起こることを変更します。
基本スクリプトで、駒をさらに5フィート移動させる場合(デフォルトのページ設定を前提とする)は次のようになります:
on("change:graphic", function(obj) {
obj.set({
left: obj.get("left") + 70
});
});
ご覧の通り、change:graphicイベントが発生するたびに実行されるシンプルなon関数を作成しました。 関数にはグラフィックオブジェクトobjが渡される。 変更を加えるには、set関数を使ってobjを修正するだけです。変更したプロパティはすべて検出され、卓上上で変更されます。
setとgetを使用する必要があります。そうしないと変更が保存されません。 (オブジェクトの種類とそのプロパティの一覧、およびすべてのイベントと各イベントに渡される引数のリストについては以下を参照のこと。)ユーティリティ関数に関する補足
もちろん、前の例はあまり役に立ちません。なぜなら、トークンの位置に常に70ピクセルを加算してしまうからです。 しかし、ユーザーがスケールを変更して5フィートが140ピクセルになっている場合はどうなるでしょうか? Roll20 APIは、この(およびその他の)一般的なシナリオを支援する便利なユーティリティ関数をいくつか提供しています。 前の例を修正して、distanceToPixels関数を使用しましょう。この関数は、卓上上の「5フィート」(またはインチ、メートル、その他の設定された距離単位)が何ピクセルに相当するかを教えてくれます。
on("change:graphic", function(obj) {
obj.set({
left: obj.get("left") + distanceToPixels(5);
});
});
現在のページがデフォルトのグリッドサイズ設定を使用している場合、distanceToPixels(5); は依然として70ピクセルを返しますが、ページが通常の2倍のサイズに設定されている場合、140を返します。
ページやトークンの設定が変更されてもスクリプトが壊れないようにするため、ユーティリティ関数が利用可能な場合は常にそれを使用することをお勧めします。
プロアクティブスクリプト:ユーザーの介入なしに処理を実行する
ユーザーイベントへの反応に加え、プレイヤーからの特定のイベントに紐づかない操作をAPIで自動的に実行することも可能です。 例えば、マップ上を行き来しながらパトロールするトークンを考えましょう。
注:この種のスクリプトはユーザー操作に依存しませんが、ゲーム用のMod(API)スクリプトは、少なくとも1人がゲームに接続している場合にのみ実行されます。
on("ready", function() {
//ゲームが完全に読み込まれたことを確認するため、readyイベントが発生するまで待機します。
//パトロールコマへの参照を取得する。
var patroltoken = findObjs({_type: "graphic", name: "Guard A"})[0]; //ゲーム内に「Guard A」というコマが存在することを確認しています。
var direction = -1 * distanceToPixels(5); // 左に70ピクセル移動する。
var stepstaken = 0; //現在の方向で歩いた歩数は?
setInterval(function() {
if(stepstaken > 3) {
//方向転換!
direction = direction * -1; //移動方向を反転させる
stepstaken = 0; //歩数を0にリセットする
patroltoken.set("left", patroltoken.get("left") + direction);//歩く!
stepstaken++;
}, 5000); //5秒ごとにアクションを実行
});
非同期関数に関する論文
非同期関数とは、呼び出し元スコープに制御を直ちに返した後、バックグラウンドで何らかの処理を実行する関数である。 以下は、Mod(API)スクリプトタブに貼り付けられる、非常にシンプルで理解しやすい例です:
on('load',function(){
log('親スコープ - 非同期関数呼び出し前');
setTimeout(function(){
log('非同期関数スコープ - 非同期関数の処理中');
},10 /* 10ミリ秒 */);
log('親スコープ - 非同期関数呼び出し後');
});
Mod(API)ログには、次のような内容が表示されます:
親スコープ - 非同期関数の呼び出し前 親スコープ - 非同期関数の呼び出し後 非同期関数のスコープ - 非同期関数の処理を実行する
そのコードを見て、「そりゃ後で実行されるに決まってるだろ、10ミリ秒で実行しろって指示したんだから、当たり前だろ?」と思う。 以下は、同じログメッセージを生成する、よりわかりにくい例です:
on('load',function(){
log('親スコープ - 非同期関数呼び出し前.');
sendChat('非同期関数','評価対象: [[1d6]]',function(msg){
log('非同期関数スコープ - 非同期関数の処理中.');
});
log('親スコープ - 非同期関数呼び出し後.');
非同期関数は、APIが常に停止状態になるのを防ぐために必要です。 もしすべてのダイスロールが同期的に処理されるなら、APIは非常に遅く反応しなくなるだろう。 コールバック関数を受け取る関数は、ほぼすべて非同期です。 (一部の_.map、_.reduceなどの関数は例外です。これらは、多くの人が慣れ親しんでいる手続き型プログラミングとは対照的な関数型プログラミングの例です。)