アクションの動き方
シーンがアクションを表示した瞬間から、Vovó がそれをどう扱うか。Vovó が保証することと、アクションが当てにしてはいけないことです。人に渡したり販売したりするアクションを書く人のための説明です。書き方は「アクション」にあります。
専用のページ
アクションは 1 つずつ独立したドキュメントで、シーンのページが用意するフレームに入ります。中身はアクションの HTML で、style.css と script.js が書き込まれ、スクリプトは最後に置かれるので、HTML がそろってから実行されます。1 つのアクションが壊れても、ほかは巻き込まれません。シーンはアクションをシーンの順番どおりに重ね、最初のものがいちばん下になります。
フレームはクリックもキーも受け取りません。オーバーレイは見るもので、押すものではありません。配信を操作する人がすることは、パッド、アクションのパネル、カスタマイズで行い、イベントとしてアクションに届きます。
アクションがイベントを聞くのは、開いているページがそのアクションを表示している間だけで、アクティブなプロファイルに限られます。ページが 1 つも開いていなければ、フォローを数えるものはありません。
ネットワークなし、ブラウザーには何も残さない
アクションが届くのは、自分のファイルと Vovó が渡すものだけです。画像、音、動画、フォントは、アクションのファイルから名前で読み込むか、イベントが伝えたアドレス(エモート、カバー画像、カスタマイズで選んだファイル)から読み込み、インターネットからは決して読み込みません。コードとスタイルは script.js と style.css だけです。ファイルや外部からのスクリプトやスタイルシートは使えず、eval もワーカーも使えません。fetch、XMLHttpRequest、WebSocket は、アクション自身のファイルに対しても拒否されるので、必要なデータはスクリプトに書きます。ブラウザーのストレージ(localStorage、Cookie、IndexedDB)はありません。アクションが残すものは vovo.state に残します。
外から必要なものは、宣言した権限の範囲で Vovó に頼みます。チャットの 1 行、名前で指定する Webhook、パッドの音、公式プレーヤーでのクリップなどです。
開いているすべてのページが実行する
同じアクションは、そのシーンが開いているすべての場所で動きます。OBS、シーンのプレビューウィンドウ、ブラウザーのタブ、同時に 2 つのソースなどです。どのコピーも同じイベントを受け取り、同じものを描きます。だからページは描画と再生だけを行い、1 回だけ起きるべきことは Vovó に頼みます。Vovó は、同じイベントの処理中に頼んできたページに対して 1 回だけ実行します。チャットの 1 行、抽選、音声、Webhook、パッドの音、OBS のソース、アクション独自のイベントがそうです。
Vovó は、処理中のイベントによって、あるページの呼び出しと別のページの呼び出しを見分けます。リスナーが戻った後の呼び出し(await の後、アニメーションが終わったとき、タイマーの中)は、{ event: e } で応答先のイベントを示します。e はリスナーが受け取ったオブジェクトです。これがないと、Vovó は 2 つ目のページなのか 2 回目なのかを区別できません。
vovo.on("raid", async (e) =>
{
const gift = await vovo.draw(["a hug", "a song", "a dance"]);
vovo.show("raid", { user: e.user, gift: gift.value });
vovo.chat.send(e.user + " gets " + gift.value + "!", { event: e });
});
ページが自分で始めることには、背後にイベントがありません。アクションの setInterval はすべてのページで動き、読み込み時に行うことも同じです。その場合 Vovó は頼まれた内容でしか判断できません(5 秒以内の同じチャットの行、同じ音声、同じ Webhook、同じ独自イベントは 1 つとして扱います)。数えるものや 1 回だけ起きるべきことは timer トリガーに置きます。これはアプリのもので、すべてのページで 1 つです。
状態であって、再生し直しではない
vovo.state を保持するのはページではなく Vovó です。ページが読み込まれると(OBS の起動、ソースの更新、パニックの終了、Vovó の再起動)、スクリプトが動く前に状態がすべてそろっています。あるページで設定したキーは、ほかのページにもすぐに、何も知らせずに届きます。ほかのページも同じイベントを自分で処理しているからです。イベントが再生し直されることはありません。レイドが来たときにいなかったページは、後からそれを知ることはありません。だからアクションは、もう一度表示する必要があるもの(目標のカウント、抽選の名前、カウントダウンの終了時刻)をすべて状態に入れ、読み込み時にそこから描きます。
function draw()
{
document.getElementById("count").textContent = vovo.state.count || 0;
}
vovo.on("follow", () =>
{
vovo.state.count = (vovo.state.count || 0) + 1;
draw();
});
draw();
- 中身が変わったら、キーを設定し直します:
vovo.state.names = names.concat([user])。リストやオブジェクトをその場で変更しても(push)、保存されません。 - 状態はリスナーの中で、どの
awaitよりも前に変更します。そうすれば、すべてのページが同じ状態から始めて同じ状態になります。awaitの後では別のページがすでに書いているかもしれないので、今ある値に左右されないもの(当選者の名前、時刻)を書き、今の値に 1 を足すような書き方は決してしません。 - 1 つのキーに入るのは JSON で 64 KB まで、キー名は 64 文字までです。
現在の値と、流れ続ける読み取り値
4 つのイベントは、ある瞬間であると同時に値でもあります:obs.scene、media.track、twitch.viewers、screen.shared。読み込まれたアクションは、聞いているものそれぞれの最新の値を、たった今起きたかのようにすぐ受け取ります。だから次の曲を待たずに再生中の曲を表示できます。これは読み込みのたびに、すべてのページで、背後にイベントがない状態で届きます。そこから描画し、1 回だけ起きるべきことで応答しないでください。
audio.level、screen.color、input.move、speech.text は読み取り値で、最大で毎秒 60 回届きます。届けられて忘れられ、開いているページがそれを聞くアクションを表示している間だけ読み取られます。リスナーは軽くし、動きは CSS に任せましょう。
読み込み、再読み込み、パニック
ページはいつでも、何度でも読み込まれます。OBS はシーンが表示されるとソースを開始し、隠れている間は停止することがあり、再び表示されると更新することがあります。Vovó も、アクションのコードやファイルが変わると読み込み直します。パニックはすべてのシーンを一度に消してアクションを読み込み直し、アクションがしていたことはそこで止まります。パニックが終わると、もう一度読み込まれて状態から始まります。パニック中はアクションに何も届かず、その間に来たものが後から再生されることもありません。コントロールの値は状態なので保持されます。
つまりアクションは、前のページがやりかけで残したものを当てにせず、いつでも状態だけから正しく立ち上がる必要があります。
ステージ
アクションは、短辺が 1080 の CSS ピクセルのステージに描きます。16:9 の配信なら解像度にかかわらず 1920 × 1080(4K のソースは 2 倍の大きさでくっきり描きます)、縦型の配信なら 1080 × 1920、ほかの形の画面ならその形になります。アクションのドキュメントはステージと同じ大きさで透明なので、100vw × 100vh が画面全体になり、描かなかった部分にはゲームが見えます。ほかの人の配信向けに作るアクションは、形を決めつけません。端からの位置やパーセントで配置し、縦型の配信には @media (orientation: portrait) を使います。
OBS のブラウザー
OBS は独自のブラウザーでシーンを表示します。Vovó のエディターやプレビューウィンドウのものより古く(OBS 32 では Chromium 127)、グラフィックカードの支援なしで動くこともあります。新しい CSS や JavaScript の機能は、頼る前にこのバージョンで使えるか確認し、渡す前に OBS そのものでアクションを見てください。OBS では音がクリックなしですぐに鳴り、配信に流れます。配信者だけに向けたものはアクションに入れません。
OBS のページにはコンソールがありません。アクションが捕まえなかったエラーと、呼び出しについて Vovó が伝えることは、アクションの名前と行番号付きで Vovó のコンソールに届きます。console.log は届きません。それを見るには、プレビューのアドレス(vovo dev が表示します)をブラウザーで開き、開発者ツールを使います。
ペースと上限
- チャット:1 行 500 文字。全アクション合わせて 30 秒に 20 行、アクションごとに 1 秒に 1 行。
- Webhook:JSON で 16 KB。1 分に 30 回、アクションごとに 1 秒に 1 回。
- クリップと動画:アクションごとに承認待ち 3 件まで。
- カメラ:同時に表示できるのは 4 台、アクションごとのカメラ設定は 4 つまで。
- 音声通話:最長 300 秒。
- 進行中として表示するボタン:最長 15 分。
- 状態:キーごとに 64 KB。
- パッケージ:200 ファイル、1 ファイル 32 MB、合計 128 MB。
index.html、style.css、script.jsはそれぞれ 2 MB。
ペースを超えた呼び出しは捨てられ、順番待ちにはなりません。その Promise は理由とともに拒否されます。
渡す前に
- いろいろな動作の途中でシーンを再読み込みする:状態から正しく戻ります。
- シーンを OBS とプレビューウィンドウの 2 か所で開き、各トリガーを起こす:チャットは 1 行、音は 1 回、カウントは 1 つ。
- 何かを表示している間にパニックをオンにしてオフにする。
- 縦型のプロファイルで試し、OBS そのものでも試す。
- 使う権限だけを求め、「動作」でそれが何かを伝える:作者、サイト、1 行の説明。
- ID を付け、渡すたびにバージョンを上げます。そうすれば、更新が置き換え先のアクションを見つけ、どのバージョンかを伝えられます。
- どのライセンスで渡すかを書きます。条件が自分で決めたものなら、全文も付けます。