7
Help us understand the problem. What are the problem?

posted at

updated at

Googleスプレッドシートを使ったUnityコラボ開発 (多言語テキストと定数表)

おことわり

この記事には、新しい版 (GitHub)が存在します。


はじめに

  • この記事は、過去に公開した記事の焼き直しで、以下の相違があります。
    • ScriptableObjectAddressablesに対応しました。
    • スプレッドシート側の計算式を使わずに、シート全体を取得して解析するようしました。
  • 公開当初に比して、以下の点が変更されました。
    • unity 2020.3で確認しました。
    • OAuth 2.0に対応しました。
    • Windowsに依存した可能性が高いです。

前提

  • unity 2020.3.30f1
    • Addressables 1.18.19
  • Googleスプレッドシート
  • Googleアカウント
  • Windows 10
    • 他のOSではテストされておらず、動作は不明です。
このシステムのセキュリティについて
  • このシステムのセキュリティ
    • スプレッドシート
      • 特別な共有設定は不要で、共同編集者のGoogleアカウントに対する共有のみを設定します。
      • ドキュメントIDが、平文でエディターの設定内(EditorPrefs)に保存されます。
    • Google Apps Script
      • 自分のみが使えるように設定します。
      • 承認したGoogleアカウントの権限で実行され、承認者がアクセス可能な全てのスプレッドシートにアクセス可能です。
      • アプリケーションのURLが、平文でエディターの設定内(EditorPrefs)に保存されます。
    • Google Cloud Platform
      • クライアント ID、クライアント シークレット、アクセストークン、リフレッシュトークンが、平文でエディターの設定内(EditorPrefs)に保存されます。

できること

  • 言語別テキストや定数などをGoogleスプレッドシートで共同編集できます。
    • 「シナリオやユーザー向けメッセージなどの編集者」や「各種の初期値を設定する担当者」がプログラマーでない場合に共同制作が容易になります。
    • 多言語対応のテキストと、言語に依存しない固定値 (int、float、string、bool)を扱えます。
  • 編集結果は、任意のタイミングでプロジェクトへ取り込むことができます。
  • ビルド番号を管理できます。
    • スクリプトから定数として参照できます。
    • ビルド毎に自動更新できます。
    • プラットフォーム毎の番号を統一できます。

特徴

  • 取り込んだスプレッドシートの情報を、C#スクリプトと言語別ScriptableObjectに変換します。
  • ScriptableObjectはAddressablesで管理されます。
  • スプレッドシートの取り込みは、別スレッドで実行され、エディターをブロックしません。

導入

クラウドの準備

Google Apps Script の作成

  • The Apps Script Dashboardへアクセスします。
  • 「新規スクリプト」を作成します。仮に、getspreadseet2と名付けたものとします。
  • 「コード.gs」の中身を、以下のコードで置き換えます。
getspreadseet2/コード.gs
// getspreadseet?d=document&s=seet&a=area

function doGet(e) {
  if (e.parameter.d != null) {
    var spreadsheet = SpreadsheetApp.openById(e.parameter.d);
    if (e.parameter.w == null) { // 読み出し
      if (e.parameter.a != null) { // セル
        var value = spreadsheet.getSheetByName(e.parameter.s).getRange(e.parameter.a).getValue();
        Logger.log (value);
        return ContentService.createTextOutput(value).setMimeType(ContentService.MimeType.TEXT);
      } else if (e.parameter.s != null) { // シート
        var values = spreadsheet.getSheetByName(e.parameter.s).getDataRange().getValues();
        Logger.log(values);
        return ContentService.createTextOutput(JSON.stringify(values)).setMimeType(ContentService.MimeType.TEXT);
      } else if (e.parameter.id != null) { // シートID一覧
        var sheets = spreadsheet.getSheets();
        var sheetids = [sheets.length];
        for (var i = 0; i < sheets.length; i++) {
          sheetids [i] = sheets [i].getSheetId();
        }
        Logger.log(sheetids);
        return ContentService.createTextOutput(JSON.stringify(sheetids)).setMimeType(ContentService.MimeType.TEXT);
      } else { // シート名一覧
        var sheets = spreadsheet.getSheets();
        var sheetnames = [sheets.length];
        for (var i = 0; i < sheets.length; i++) {
          sheetnames [i] = sheets [i].getName();
        }
        Logger.log(sheetnames);
        return ContentService.createTextOutput(JSON.stringify(sheetnames)).setMimeType(ContentService.MimeType.TEXT);
      }
    } else { // 書き込み
      if (e.parameter.a != null) { // セル
        spreadsheet.getSheetByName(e.parameter.s).getRange(e.parameter.a).setValue(e.parameter.w);
      } else if (e.parameter.s != null) { // シート
        var sheet = spreadsheet.getSheetByName(e.parameter.s);
        if (sheet != null) {
          sheet.getDataRange().clear();
        } else {
          sheet = spreadsheet.insertSheet(e.parameter.s);
        }
        //return ContentService.createTextOutput(e.parameter.w).setMimeType(ContentService.MimeType.TEXT);
        var values = JSON.parse(e.parameter.w);
        sheet.getRange(1,1,values.length,values[0].length).setValues(values);
        sheet.autoResizeColumns (1, values[0].length);
        if (e.parameter.r != null) {
          sheet.setFrozenRows(e.parameter.r);
        }
        if (e.parameter.c != null) {
          sheet.setFrozenColumns(e.parameter.c);
        }
      }
    }
  }
  return ContentService.createTextOutput("[]").setMimeType(ContentService.MimeType.TEXT); // 空
}

function doPost (e) {
  return doGet (e);
}
  • 「デプロイ」から「新しいデプロイ」を選び、以下を選択します。
    • 次のユーザーとしてアプリケーションを実行: 自分(~)
    • アクセスできるユーザー: 自分のみ
  • 「デプロイ」ボタンを押します。
    • デプロイする際には、先ず承認が必要です。
  • デプロイできたら、《ウェブ アプリケーションの URL》を控えておきます。
    • 後からでも、「デプロイの管理」から確認できます。

Google Cloud Platform の作成

  • Google Cloud Platformのダッシュボードへアクセスします。
  • 「APIとサービス」→「OAuth 同意画面」で、同意画面を作ります。
    • 初めてなら、まず、プロジェクトを作ることになります。
    • 「テストユーザー」に自分を追加します。
  • 「認証情報」に切り替えて、「OAuth クライアント ID」を作成します。
    • 「アプリケーションの種類」は「デスクトップアプリ」にします。
    • 《クライアント ID》と《クライアント シークレット》を控えます。
      • 後からでも、「認証情報」から確認できます。

スプレッドシートの用意

  • スプレッドシートの雛形を開き、ファイルメニューから「コピーを作成…」を選びます。
  • フォルダを選んで保存します。
    • Googleドライブに保存されます。
  • マイドライブから保存したフォルダへ辿り、コピーされたスプレッドシートを開きます。
  • 開いたスプレッドシートのURLで、https://docs.google.com/spreadsheets/d/~~~/editの「/d/~~~/」の部分に注目してください。
    • この「~~~」の部分が、このスプレッドシートの《ドキュメントID》です。
    • このIDは、後で使用しますので、何処か安全な場所に控えておいてください。

Unityプロジェクトの準備

アセットの入手と導入 (GitHub)

設定

  • 「Window」メニューの「GetSharedData > OPen Setting Window」を選択しウインドウを開きます。
  • 「Document ID」に、控えておいた《ドキュメントID》を記入します。
  • 「Asset Folder」は、プロジェクトのフォルダ構造に合わせて書き換え可能です。
  • 「Application URL」に、控えておいた《ウェブ アプリケーションの URL》を記入します。
  • 「OAuth Settings」を開き「Client ID」と「Client Secret」に、控えておいた《クライアント ID》と《クライアント シークレット》を記入します。
    • 後からIDとシークレットを確認するには、GCPダッシュボードの「認証情報」を参照します。
  • 「Build Number」は、ビルド番号を取り込む機構に対する設定です。
    • 「Unified Number」にチェックすると、機種毎のビルド番号を最大のものに合わせます。
    • 「Auto Increment」にチェックすると、ビルド毎に自動的にビルド番号を1増やします。
  • 設定はエディターを終了しても保存されます。
    • プロジェクトの設定は、Player SettingsCompany NameProduct Nameで識別されるプロジェクト毎に保持されます。
    • 「Application URL」は、プロジェクトを横断して保持されます。
    • 全ての設定は、EditorPrefsに平文で保存されます。

使い方

編集と取り込み

スプレッドシート

  • 「Text」と「Const」は、シート名として予約されています。
    • シートの追加や並び替えは任意に行うことができますが、シート名の重複はできません。
    • 「Text」シートに記載したものは言語切り替えテキスト、「Const」シートに記載したものは定数に変換されます。
    • 他のシートは無視されます。
  • シートでは、行を3つの部分(「列ラベル」、「列ラベルより上」、「列ラベルより下」)に分けます。
    • 「Key」と書かれたセルが最初に見つかった行が、列ラベルになります。
    • 列ラベルより上の行は無視されます。
  • 「Key」、「Type」、「Value」、「Comment」、および、UnityEngine.SystemLanguageで定義されている名前は、列ラベル名として予約されています。
    • ラベルの文字種や空白文字の有無は区別されます
    • 予約されていない名前の列は無視されます。
  • セルの値(計算結果)だけが使われ、シートの計算式や書式は無視されます。
  • 「Text」シートでは、「Key」および「Comment」列と、任意の数の言語名の列を記入します。
    • 最も左の列の言語がデフォルトになります。
    • セル内改行は、改行文字\nに変換されます。
  • 「Const」シートでは、「Key」、「Type」、「Value」、「Comment」列を記入します。
    • キーとして、「BuildNumber」、「BundleVersion」が予約されています。

Unityエディター

  • GetSharedDataウィンドウの「GetSharedData」ボタンを押すか、Windowメニューの「GetSharedData > Get Spreadsheet」を選ぶか、そのメニュー項目に表示されるキーボードショートカットを使うことで、スプレッドシートの情報が取り込まれ、コンパイルされます。

Addressables

  • 言語別テキストを格納したアセットText_言語名.assetは、Addressablesに入れてください。
    • アドレスを単純化(Simplify)して、名前だけにしておいてください。
  • 再取り込みを行う際は、ファイルは作り直されずに内容だけが上書きされます。
    • Unityエディターで内容を編集することは想定していません。

スクリプトでの使用

  • using SharedConstant;を指定してください。

テキスト

  • システムの言語設定(Application.systemLanguage)に従って初期設定されます。
  • SystemLanguage Txt.Locale 言語 (プロパティ)
    • SystemLanguage型の値を代入することで、強制的に言語を切り替えます。
    • SystemLanguage.Unknownを指定すると、システムの言語設定に従います。
    • 相当する言語がアセットにない場合は、デフォルト(シート最左)の言語が使われます。
    • 切り替えてから実際に値が変化するまでには、アセットをロードする時間分のラグがあります。
  • string Txt.S (Nam.key[, ...]) テキスト (メソッド)
    • キーを指定して現在設定されている言語のテキストを返します。
    • キーを列挙すると該当するテキストを連結して返します。
  • string Txt.F (Nam.key, object arg[, ...]) フォーマットテキスト (メソッド)
    • 指定したキーをフォーマットとして他の引数を展開したテキストを返します。
  • int Txt.N (string str) キーの逆引き (メソッド)
    • Txt.S (~)で得たテキストを渡すと、該当のキーを返します。
    • 未定義なら-1を返します。
  • キーNam.~は、const intとして定義された文字列のインデックスです。

数値

  • キーはconstとして定義されます。
  • Cns.~として使用します。
  • Cns.BuildNumberCns.BundleVersionが自動的に定義されます。

Unity使用者が複数いる場合の留意点

  • 例えば、「Unityを使わない企画者、Unityを使うデザイナとプログラマの3人」といったように、スプレッドシートからUnityに情報を取り込むメンバーが複数いる場合は以下の点に留意してください。
  • メンバーで共有するアセットの設定は、「Project Settings」と「Build/Bundle Number Settings」だけです。
  • 「OAuth Settings」は共有してはいけません。つまり、各メンバーそれぞれがGoogle Apps Script の作成を行う必要があります。
  • なお、これらの設定は、プロジェクトのフォルダには保存されないので、GitHubなどによるアセットの共有で問題が生じることはありません。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
7
Help us understand the problem. What are the problem?