初めに
個人等でゲーム開発を行う場合、開発初期時のなどはなるべくお金をかけない開発環境を作ることを求めるケースが多々あります そこで今回は Google DriveとGAS(Google App Script)を使ったマスターデータの管理方法について実際に行ってみます
求める要件
- マスターデータは Google Sheetで管理して、いくつかのシートに散らばっている
- 各環境ごとにマスターデータをデプロイして区別をしたい
- dev環境
- qa環境
- product環境 ...
- 外部(イメージはUnityなど)から指定した環境のマスターデータを取得した
マスターデータの準備
まずは以下のようなマスターデータを作成します。これらは複数のシートがあるものとします。(一つでも問題はないです)
test1 test2 test3_1 test4 test3 test4 test3_2 test5 test5 test6 test3_3 test6 test7 test8 test3_4 test1 2 -0.45 0 test2 -1.483333333 -2.483333333 -3.483333333 test3 -4.483333333 -5.483333333 -6.483333333 test4 -7.483333333 -8.483333333 test2 2 -0.45 0 test3 -1.483333333 -2.483333333 -3.483333333 test4 -4.483333333 -5.483333333 -6.483333333 test5 -7.483333333 -8.483333333 test3 2 -0.45 0 test4 -1.483333333 -2.483333333 -3.483333333 test5 -4.483333333 -5.483333333 -6.483333333 test6 -7.483333333 -8.483333333
イメージは以下のような感じです

GoogleDriveに環境ごとにCSVとバイナリーで保存
以下のようにmenuから各環境ごとに保存できるようにします。

実行すると以下のように環境ごとのフォルダにbinとcsvが保存されます

以下がCSVとバイナリーに保存する処理のコードです
/** * スプレッドシートのデータをCSVおよびバイナリ形式で保存し、指定された環境フォルダに配置する * @param {string} environment - 保存する環境名(例: 'product', 'qa', 'dev1', 'dev2', 'dev3') */ function convertSheetToCSVAndBinary(environment) { // アクティブなスプレッドシートとシートを取得 var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); var sheet = spreadsheet.getActiveSheet(); // シート内のデータ範囲を取得 var dataRange = sheet.getDataRange(); // データ範囲内の全データを取得 var data = dataRange.getValues(); // データをCSV形式の文字列に変換 var csv = data.map(function(row) { return row.map(function(cell) { // セル内のダブルクォーテーションをエスケープ return '"' + String(cell).replace(/"/g, '""') + '"'; }).join(','); }).join('\n'); // CSV文字列をBlobに変換 var csvBlob = Utilities.newBlob(csv, MimeType.CSV); // データをJSON文字列に変換 var jsonString = JSON.stringify(data); // JSON文字列をBlobに変換 var binaryBlob = Utilities.newBlob(jsonString, MimeType.PLAIN_TEXT); // フォルダIDを指定 var rootFolderId = 'your google drive id'; var rootFolder = DriveApp.getFolderById(rootFolderId); // "masterdata" フォルダを取得または作成 var masterdataFolder = getOrCreateFolder(rootFolder, 'masterdata'); // 環境フォルダを取得または作成 var environmentFolder = getOrCreateFolder(masterdataFolder, environment); // ファイル名を "masterdata-環境名" に設定 var baseName = 'masterdata-' + environment; // CSVファイルの処理 csvBlob.setName(baseName + '.csv'); var existingCsvFile = getFileByName(environmentFolder, baseName + '.csv'); if (existingCsvFile) { existingCsvFile.setTrashed(true); // 既存のファイルを削除 } environmentFolder.createFile(csvBlob); // バイナリファイルの処理 binaryBlob.setName(baseName + '.bin'); var existingBinFile = getFileByName(environmentFolder, baseName + '.bin'); if (existingBinFile) { existingBinFile.setTrashed(true); // 既存のファイルを削除 } environmentFolder.createFile(binaryBlob); // 完了メッセージを表示 SpreadsheetApp.getUi().alert('スプレッドシートのデータを ' + environment + ' 環境にデプロイしました。'); } /** * 指定したフォルダ内で、指定した名前のサブフォルダを取得する。存在しない場合は作成する。 * @param {Folder} parentFolder - 親フォルダ * @param {string} subFolderName - サブフォルダ名 * @return {Folder} - 取得または作成したサブフォルダ */ function getOrCreateFolder(parentFolder, subFolderName) { var subFolders = parentFolder.getFoldersByName(subFolderName); if (subFolders.hasNext()) { return subFolders.next(); } else { return parentFolder.createFolder(subFolderName); } } /** * 指定したフォルダ内で、指定した名前のファイルを取得する * @param {Folder} folder - 検索対象のフォルダ * @param {string} fileName - 検索するファイル名 * @return {File|null} - 見つかったファイル、またはnull */ function getFileByName(folder, fileName) { var files = folder.getFilesByName(fileName); return files.hasNext() ? files.next() : null; }
また以下がGoogle Sheetの上部にメニュとして表示する機能です
/** * スプレッドシートを開いたときにカスタムメニューを追加する */ function onOpen() { var ui = SpreadsheetApp.getUi(); ui.createMenu('マスターデータをデプロイ') .addItem('product 環境にデプロイ', 'deployToProduct') .addItem('qa 環境にデプロイ', 'deployToQA') .addItem('dev1 環境にデプロイ', 'deployToDev1') .addItem('dev2 環境にデプロイ', 'deployToDev2') .addItem('dev3 環境にデプロイ', 'deployToDev3') .addToUi(); } /** * product 環境にデプロイ */ function deployToProduct() { convertSheetToCSVAndBinary('product'); } /** * qa 環境にデプロイ */ function deployToQA() { convertSheetToCSVAndBinary('qa'); } /** * dev1 環境にデプロイ */ function deployToDev1() { convertSheetToCSVAndBinary('dev1'); } /** * dev2 環境にデプロイ */ function deployToDev2() { convertSheetToCSVAndBinary('dev2'); } /** * dev3 環境にデプロイ */ function deployToDev3() { convertSheetToCSVAndBinary('dev3'); }
GAS経由ででデータを取得するAPIを作成
外部から環境を指定したマスターデータを取得するために以下のようなget関数を定義します。
doGet 関数 API ドキュメント
doGet 関数は、指定された環境とデータ形式に対応するファイルを取得し、その内容をレスポンスとして返します。
エンドポイント
GET https://script.google.com/macros/s/あなたのスクリプトID/exec
パラメータ
| パラメータ名 | 必須 | 種類 | 説明 |
|---|---|---|---|
environment |
はい | クエリ | 取得する環境名。product、qa、dev1、dev2、dev3 のいずれかを指定します。 |
dataType |
はい | クエリ | 取得するデータの形式。bin(バイナリデータ)または csv(CSVデータ)を指定します。 |
リクエスト例
GET https://script.google.com/macros/s/あなたのスクリプトID/exec?environment=dev1&dataType=bin
レスポンス
成功時 (200 OK):
エラー時:
- 不足しているパラメータや無効な
dataTypeが指定された場合、エラーメッセージを含むテキストを返します。 - 例:
Missing parameter: environment or dataType.Invalid dataType parameter. Use "bin" or "csv".File not found.
- 不足しているパラメータや無効な
注意事項
environmentパラメータには、事前に設定された環境名を正確に指定してください。dataTypeパラメータは、binまたはcsvのいずれかを指定してください。大文字・小文字を区別します。- エンドポイントURLの
あなたのスクリプトID部分は、GASプロジェクトのデプロイ時に発行されるスクリプトIDに置き換えてください。
GET関数
実際にget関数は以下になります。
/** * HTTP GETリクエストを処理し、指定された環境とデータ形式に対応するファイルを返す * @param {Object} e - HTTPリクエストオブジェクト * @return {ContentService.Output} - ファイルデータのレスポンス */ function doGet(e) { // リクエストパラメータから環境名とデータ形式を取得 var environment = e.parameter.environment; var dataType = e.parameter.dataType; // パラメータのバリデーション if (!environment || !dataType) { return ContentService.createTextOutput('Missing parameter: environment or dataType.') .setMimeType(ContentService.MimeType.TEXT); } // データ形式のバリデーション if (dataType !== 'bin' && dataType !== 'csv') { return ContentService.createTextOutput('Invalid dataType parameter. Use "bin" or "csv".') .setMimeType(ContentService.MimeType.TEXT); } // ルートフォルダIDを指定 var rootFolderId = 'your google folder id'; var rootFolder = DriveApp.getFolderById(rootFolderId); // "masterdata" フォルダを取得 var masterdataFolder = getSubFolderByName(rootFolder, 'masterdata'); if (!masterdataFolder) { return ContentService.createTextOutput('Masterdata folder not found.') .setMimeType(ContentService.MimeType.TEXT); } // 環境フォルダを取得 var environmentFolder = getSubFolderByName(masterdataFolder, environment); if (!environmentFolder) { return ContentService.createTextOutput('Environment folder not found.') .setMimeType(ContentService.MimeType.TEXT); } // ファイル名を設定 var fileName = 'masterdata-' + environment + '.' + dataType; var file = getFileByName(environmentFolder, fileName); if (!file) { return ContentService.createTextOutput('File not found.') .setMimeType(ContentService.MimeType.TEXT); } // ファイルデータをレスポンスとして返す var mimeType = dataType === 'bin' ? ContentService.MimeType.TEXT : ContentService.MimeType.CSV; return ContentService.createTextOutput(file.getBlob().getDataAsString()) .setMimeType(mimeType); } /** * 指定した親フォルダ内で、指定した名前のサブフォルダを取得する * @param {Folder} parentFolder - 親フォルダ * @param {string} subFolderName - サブフォルダ名 * @return {Folder|null} - 見つかったサブフォルダ、またはnull */ function getSubFolderByName(parentFolder, subFolderName) { var subFolders = parentFolder.getFoldersByName(subFolderName); return subFolders.hasNext() ? subFolders.next() : null; } /** * 指定したフォルダ内で、指定した名前のファイルを取得する * @param {Folder} folder - 検索対象のフォルダ * @param {string} fileName - 検索するファイル名 * @return {File|null} - 見つかったファイル、またはnull */ function getFileByName(folder, fileName) { var files = folder.getFilesByName(fileName); return files.hasNext() ? files.next() : null; }
テストコード
実際に動くのかをテストコードで試します(GAS上のテストコードです)
/** * doGet関数のテストを実行する */ function testDoGet() { // テストする環境とデータタイプの組み合わせ var testCases = [ { environment: 'dev1', dataType: 'bin' }, { environment: 'dev1', dataType: 'csv' } ]; // 各テストケースを実行 testCases.forEach(function(testCase) { // モックのリクエストオブジェクトを作成 var e = { parameter: { environment: testCase.environment, dataType: testCase.dataType } }; // doGet関数を呼び出し var result = doGet(e); // 結果をログに出力 Logger.log('Environment: ' + testCase.environment + ', DataType: ' + testCase.dataType); Logger.log('Response Content: ' + result.getContent()); Logger.log('Response MimeType: ' + result.getMimeType()); }); }
これを実行すると以下のようなログが出ています。ログ上は問題なさそうです

GASの制限
以下のドキュメントに記載の通り一定の制限はあります。規模が大きくなったり開発工数が取れる場合は,GCPやAWSに移行しましょう
| 機能 | 制限 |
|---|---|
| スクリプトの実行時間 | 6 分 / 実行 |
| カスタム関数の実行時間 | 30 秒 / 実行 |
| トリガーの合計実行時間 | 90 分 / 日 |
| 同時実行数 | 30 / ユーザー |
| メールの添付ファイルの総サイズ | 25 MB / メッセージ |
| メール添付ファイル数 | 250 件 / メッセージ |
| メール本文のサイズ | 200 KB / メッセージ |
| 1 通あたりのメール受信者数 | 50 人 / メッセージ |
| 1 日あたりのメール受信者数 | 100 人 / 日 |
| プロパティ値のサイズ | 9 KB / 値 |
| プロパティの合計保存容量 | 500 KB / プロパティ ストア |
| トリガー数 | 20 / ユーザー / スクリプト |
| URL Fetch 呼び出し数 | 20,000 / 日 |
| URL Fetch レスポンスサイズ | 50 MB / 呼び出し |
| URL Fetch ヘッダー数 | 100 / 呼び出し |
| URL Fetch ヘッダーサイズ | 8 KB / 呼び出し |
| URL Fetch POST サイズ | 50 MB / 呼び出し |
| URL Fetch URL の長さ | 2 KB / 呼び出し |
| 作成可能なプロジェクト数 | 50 / 日 |