GASのWebアプリケーションでストーリーのシナリオを自動で台本化するツールを作った話

f:id:sumzap_engineer_blog:20210119153209p:plain

はじめに

ボイスが再生されるストーリーパートやキャラクター同士の掛け合い機能があるゲームタイトルでは、ボイス収録時に声優さんに読んでいただくための台本を用意することが多いのではないでしょうか。

今回は、弊社で開発し運用タイトルでも実際に使われているストーリーのシナリオから台本を自動で出力してくれる台本化ツールについて紹介できればと思います。

背景

2020 年 2 月にリリースした『この素晴らしい世界に祝福を!ファンタスティックデイズ』(略称:『このファン』)という弊社の運用タイトルには、フルボイスのストーリーパートがあり、ストーリー毎に台本を作成しております。

台本化ツールを導入する以前は具体的にどのように台本を作っていたかといいますと、 シナリオライターさんが書かれた Google Spread Sheet のシナリオ原本の内容を台本のレイアウトが組まれた Excel シートに1つ1つ手動でコピーしていくということをしておりました。 手作業で行っていたため、作成時間が多くかかる上にミスが起きやすい状態でした。

そこで、台本を作る上での手作業を極限まで減らし、それに加えて、台本としての体裁が更に整ったものにすることを目的として、台本化ツールを導入することになりました。

台本化ツール

台本化ツールの概要

台本化ツールは、Google Apps Script (以下 GAS) の Webアプリケーションとして作成しており、 シナリオ原本の Google Spread Sheet に、その Webアプリケーションをライブラリとして追加すると、シナリオ原本のシートの内容を台本のテンプレートに流し込んだ Webページを開けるようになります。

f:id:sumzap_engineer_blog:20210114123522p:plain
シナリオ原本のメニューバーの台本出力ボタンを押すと、台本のWebページが開き台本が表示されます。

台本化ツールには、シナリオのセリフやキャラ名を時系列で表示する機能だけではなく、次のような便利機能もあります。
それぞれの便利機能については後述します。

  • 指定したキャラクターのセリフをハイライト表示する
  • 指定したキャラクターのセリフだけを表示する
  • 印刷ウィンドウを表示する

台本化ツールの構成

台本化ツールの構成は、下の図のようになっております。 f:id:sumzap_engineer_blog:20210114132938p:plain

まず、開発環境周りについてですが、 GASには専用のスクリプトエディタが用意されており、ブラウザ上でコードを書いて手軽に開発を進めていくことができますが、それは使わずに、 Googleが提供する GAS に関するCLIツールである clasp を使って、 ローカルで開発した GAS の Webアプリケーションのコードを GAS にデプロイする方式を取りました。 ローカルで好きなエディタで開発できるのは、かなり嬉しいことです。 また、clasp には TypeScript で書かれたローカルのコードを自動で.gsファイルに変換してくれる機能もあります。 台本化ツールの開発では、型推論などの恩恵を受けられそうだということで、GAS側のコードはTypeScript で書いていきました。クライアント側のフレームワークは Vue.js を採用しました。

ローカルで開発したコードを GAS に反映するには、clasp pushコマンドclasp deployコマンドを使います。

clasp pushコマンドでは、ローカルで開発した htmlファイルやjsファイル、tsファイルなどを script.google.com にプッシュします。

clasp push

プッシュしたWebアプリケーションは、『GAS プロジェクトの詳細ページ -> デプロイ -> デプロイをテスト』で確認できるデプロイテスト用のURLから確認できます。 f:id:sumzap_engineer_blog:20210114133144p:plain

しかし、これだけでは新しいバージョンとしては反映されておらず、新しいバージョンとして公開するにはclasp deployコマンドで、デプロイする必要があります。 オプションに指定するデプロイIDには、『GAS プロジェクトの詳細ページ -> デプロイ -> デプロイを管理』に記載されているものを指定します。

clasp deploy -i {デプロイID}

デプロイに成功すると、 GAS のデプロイバージョンがインクリメントされます。

デプロイしたWebアプリケーションは、『GAS プロジェクトの詳細ページ -> デプロイ -> デプロイを管理』で確認できる公開用のURLから確認できます。 f:id:sumzap_engineer_blog:20210114133206p:plain

GASのWebアプリケーションを実装する上でのポイント

WebアプリケーションのURLのパラメータをHTMLに渡す

GASからHTMLを出力するには、HtmlOutputオブジェクトを返すdoGet(e)関数を定義する必要があります。

このdoGet(e)関数は、WebアプリケーションのURLにアクセスしたときに実行され、クライアント側にHTMLを送信します。
引数eからは、リクエストパラメータについての情報を取得することができます。

動的なHTMLを生成したい場合は、HtmlService.createTemplateFromFile関数HtmlTemplateオブジェクトを生成し、evaluate関数を実行することでHtmlOutput オブジェクトが取得できます。
この方法でHTMLを出力すると、Scriptletsという仕組みを使えるようになり、HTML側からGASの関数を呼び出したりグローバル変数の値を参照したりすることができるようになります。

Scriptletsを使ってGAS側からHTMLに値を渡す方法はいくつかありますが、今回は、HtmlTemplateオブジェクトのプロパティを介して伝える方法をとりました。 HTML側では、<?= HtmlTemplateオブジェクトに設定したプロパティ名 ?>と書くことで、HtmlTemplate オブジェクトに設定したプロパティの値を参照できます。

注意点がありまして、Scriptletsの処理は、クライアント側にHTMLを送信する前にサーバ側で1度だけ実行されるもので、Webページがロードされてからは再実行できないです。

台本化ツールでは、URLのクエリパラメータに指定したシナリオ原本のGoogle Spread SheetのSheetIdとハイライト表示するキャラ名をindex.htmlに受け渡し、HTML側でシナリオ原本のGoogle Spread Sheetの 情報を取得するということをしています。

・WebApp.ts (GAS側のコード)

function doGet(e: any) {
    if (!e.parameter.sheetId) {
        return createErrorHtml("sheetIdが指定されていません");
    }

    // リクエストパラメータの取得
    const sheetId = e.parameter.sheetId;
    const targetCharaName = e.parameter.targetCharaName;

    // テンプレートの生成
    const template = HtmlService.createTemplateFromFile('webApp/index');

    // HTMLに渡す値の指定
    template.sheetId = sheetId;
    template.targetCharaName = targetCharaName;

    // HTMLの生成
    const html = template.evaluate();
    return html;
}

・index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>このファン シナリオ台本</title>
    <script type="text/javascript">
        // WebApp#doGetから渡ってくる値を受け取る
        const sheetId = "<?= sheetId ?>";
        const targetCharaName = "<?= targetCharaName ?>";
        <!---->
    </script>
</head>
<body>
    <!-- 略 -->
</body>
</html>

HTML側からGAS側の関数を呼び出す

HTML側からGAS側の関数を呼び出すには、google.script.runの関数を使います。
google.script.runは、HTML側のAPIであり、Scriptletsと違ってWebページがロードされた後でも実行できます。

使い方としては、HTMLのコード内でgoogle.script.run.{GAS側の関数}と書くことで、GAS側の関数を実行することができます。
返り値を受け取りたい場合は、google.script.run.withSuccessHandler関数を使います。

例えば、上の例の続きで、シナリオ原本のGoogle Spread Sheetのシートの情報を取得するには、以下のようになります。

・WebApp.ts (GAS側のコード)

/**
 * Google Spread SheetのタイトルとURLを取得する
 */
function getSpreadSheetInfo(sheetId: string) {
    // Google Spread SheetのsheetIdからSpreadsheetオブジェクトを取得する。
    const spreadsheet = SpreadsheetApp.openById(sheetId);

    return {
        // ここでは、例として、実際に台本化ツールで取得しているデータの一部のみを返すようにしている。
        url: spreadsheet.getUrl(),
        title: spreadsheet.getName(),
    };
}

・index.html

<!-- 略 -->
<script type="text/javascript">
    // WebApp#doGetから渡ってくる値を受け取る
    const sheetId = "<?= sheetId ?>";
    const targetCharaName = "<?= targetCharaName ?>";

 // GAS側のgetSpreadSheetInfo関数を呼び出し、結果をonSuccessで受け取る。
    google.script.run.withSuccessHandler(onSuccess).getSpreadSheetInfo(sheetId);

    function onSuccess(data) {
        const spreadSheettitle = data.title;
        const spreadSheetUrl = data.url;

        // 以下でVueインスタンスの生成を行い、getSpreadSheetInfoで取得した結果をVueのdataオブジェクトに格納している。
    }
</script>

他にも、Vueのcreated フックでGAS側に定義された選択範囲のセルからセリフなどのデータを取得する処理を呼び出して結果をVueのdataオブジェクトに設定する処理を定義しているところで、google.script.runを使っております。

台本化ツールの便利機能

ここでは、以下の便利機能について紹介しようと思います。

  • 指定したキャラクターのセリフをハイライト表示する
  • 指定したキャラクターのセリフだけを表示する
  • 印刷ウィンドウを表示する

指定したキャラクターのセリフをハイライト表示する

キャラクター切替のプルダウンからシナリオで登場するキャラクターの名前を選択できるようになっており、選択したキャラクターのセリフの部分がハイライトされます。
また、台本の表紙となるページに選択したキャラクターの名前と声優さんの名前を表示するようにしています。

f:id:sumzap_engineer_blog:20210114131038p:plain

実装的には、まず、Vueのcreatedの中で取得したセリフデータからクエリパラメータで指定されているキャラクターのセリフを抽出し、それらにハイライト表示するフラグを立てるという更新をVueのdataに対して行います。 HTML側では、そのフラグを元に以下のようにv-bindディレクティブを使ってセリフのキャラ名表示の箇所に適用するクラスを切り替えています。

<tr v-for="word in pageWords">
    <td class="number_horizontal">{{ word.tapNo }}</td>
    <td v-bind:class="{ own_serif : word.mark }">{{ word.shortName }}</td>
    <td v-html="word.serifForHtml"></td>
    <td class="effect_text">{{ word.effect }}</td>
</tr>

キャラクターと声優さんの名前の表示・非表示の切り替えは、キャラクターが指定されているかどうかでv-ifディレクティブで実現しています。

指定したキャラクターのセリフだけを表示する

キャラクター切替のプルダウンでキャラクターを選択した上で、対象ワード抽出というボタンを押して対象ワード抽出機能を有効にすると、選択したキャラクターのセリフだけがハイライトされた上で抽出された台本が表示されます。声優さん(の事務所)によっては、こちらの形式の台本を受け取りたいということがあったので、この機能を作りました。

全体のセリフや背景や演出の内容と合わせて特定のキャラクターのセリフをハイライトした台本を出力したい場合は、対象ワード抽出機能を無効にしておきます。

f:id:sumzap_engineer_blog:20210114131946p:plain

実装的には、すべてのセリフを表示するdivタグと選択したキャラクターのセリフだけを表示するdivタグに共通のname属性"marked_only_episode"を指定しておき、切り替えボタン押下時に呼ばれる関数でclassList.toggleを実行することでdivタグのclass属性の値を切り替えております。非表示にするdivタグには、displayプロパティがnoneのスタイルが割り当てられます。

・index.html

/**
* 対象者のセリフのみ表示するかどうかの切替を行う
 */
function toggleOnlyWord() {
    const tables = document.getElementsByName("marked_only_episode");
    tables.forEach(table => {
        table.classList.toggle("target_only_hidden");
        table.classList.toggle("target_only_visible");
    });
}

・style.html

<style>
.target_only_visible {
}

.target_only_hidden {
    display: none;
}
</style>

印刷ウィンドウを表示する

印刷ボタンを押すと印刷ダイアログが表示され、すぐに表示されている台本の Webページを印刷したり pdfファイルとして出力したりすることができます。

印刷ダイアログの表示は、window.print()で行っています。

導入してみての所感

この台本化ツールが作られたのは『このファン』とは別のプロジェクトで、後から『このファン』プロジェクトに導入したのですが、台本に表示する項目はプロジェクトによって違ったので、導入する際に台本化ツールのソースコードを編集する必要がありました。
GASのWebアプリケーションに触れたのは今回が初めてだったこともあり、初めはGAS側とHTML側でのやり取りを追うのに苦労しましたが、理解してからは、実際に手を入れていくソースコードは触れたことのある TypeScriptとVue.js で書かれたもので、使い慣れたエディタで編集できたので、導入コストは思っていたよりも低かったと思います。

おわりに

台本化ツールを導入することで、ツール導入前には数時間単位で掛かっていた作業が数分で終わるようになり、かなりの作業時間短縮につながりました。同時に、声優さんにお渡しする台本の体裁を整えることもできました。

個人的な学びとして、プロジェクトを跨いで使われそうなツールを GAS で作るとなった場合は、導入しやすいものにするために、clasp を使ってローカルで開発すること前提で作るのが良さそうだと思うようになりました。

台本の最終的なアウトプットの形を考える上で、SGE のグループ子会社の1つである QualiArts の方から資料をいただきました。
この場を借りてお礼を申し上げます。

 
©2019 暁なつめ・三嶋くろね/KADOKAWA/映画このすば製作委員会 ©Sumzap, Inc.