この記事は 広島大学 HiCoder & ゲーム制作同好会 GSD Advent Calendar 2023の 8日目です。
GoogleDriveのフォルダの中身を同期できるようにします。
同期ボタンを押してしばらく待つと、GoogleDriveの中身が反映されます。
音ゲーを複数人で作った際に素材班とのやり取りが楽にするため取り入れました。結構便利です。
1.「UnityGoogleDrive」をインストールする
「UnityGoogleDrive」というパッケージをUnityにインポオートし、
GoogleDriveにアクセスしてファイルをダウンロードする機能を追加します。
以下の記事を参考に「UnityでJSONを読み込み」の部分まで行ってください
[Unity]GoogleDrive上の指定のフォルダ内のファイルをダウンロードする
2.「Editor Coroutines」という公式パッケージをインストールする
「Editor Coroutines」という公式パッケージをインストールします 。
エディタ上でコルーチンを使用できるようにします。
以下の記事を参考に「Editor Coroutinesのインストール」の部分を行ってください
【Unity】公式パッケージ「Editor Coroutines」を利用してエディタ内でCoroutineを扱う事ができるようにする(内部実装で説明)
3.以下のスクリプトを入れる
同期するフォルダー情報等の設定を格納するScriptableObjectと、インスペクタに実行ボタンを表示するスクリプト
using UnityEngine;
using System.Collections;
#if UNITY_EDITOR
using UnityEditor;
using Unity.EditorCoroutines.Editor;
#endif
[CreateAssetMenu(fileName = "GoogleDriveSettingData", menuName = "ScriptableObjectの生成/GoogleDriveSettingDataの生成", order = 0)]
public class GoogleDriveSettingData : ScriptableObject
{
string LocalFolderPath => AssetDatabase.GetAssetPath(localFolder);
[SerializeField] Object localFolder;
[SerializeField, HideInInspector] Object oldlocalFolder;
[SerializeField] string GoogleDriveFolderID;
[SerializeField, Header("全てのファイルをダウンロードする")] bool downloadAllFiles = false;
[SerializeField, Header("ダウンロードするファイルの拡張子")]
string[] extentions =
{
@".png",
@".jpg",
@".jpeg",
@".ogg",
@".mp3",
@".wav",
@".csv",
@".txt",
};
[SerializeField, Header("デバッグログに詳細を表示する")] bool debugLog = false;
#if UNITY_EDITOR
private void OnValidate()
{
//localFolderが変更されたらフォルダかどうかを確認
if (localFolder != null)
{
string path = AssetDatabase.GetAssetPath(localFolder);
if (!AssetDatabase.IsValidFolder(path))
{
// Inspectorにエラーメッセージを表示
UnityEditor.EditorUtility.DisplayDialog("エラー", "フォルダを指定してください", "OK");
// 不正な値をnullに戻す
localFolder = oldlocalFolder;
return;
}
oldlocalFolder = localFolder;
}
}
public IEnumerator Download()
{
yield return GoogleDriveDownloader.DownloadFiles(LocalFolderPath, GoogleDriveFolderID, downloadAllFiles, extentions, debugLog);
}
#endif
}
//インスペクタにDownloadボタンを表示する
#if UNITY_EDITOR
[CustomEditor(typeof(GoogleDriveSettingData))]
public class GoogleDriveSettingDataEditor : Editor
{
private EditorCoroutine m_coroutine;
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("フォルダの同期"))
{
if (m_coroutine != null)
{
EditorCoroutineUtility.StopCoroutine(m_coroutine);
}
EditorUtility.ClearProgressBar();
m_coroutine = EditorCoroutineUtility.StartCoroutine(((GoogleDriveSettingData)target).Download(), this);
}
}
}
#endif
GoogleDriveの指定のフォルダ内のファイルを更新日時や削除されているか等を確認したうえでダウンロードするスクリプト
using UnityEngine;
using UnityEditor;
using UnityGoogleDrive;
using System.IO;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Collections;
public static class GoogleDriveDownloader
{
//アクセストークンを更新するメソッド
public static IEnumerator RefreshAccessToken()
{
AuthController.RefreshAccessToken();
while (AuthController.IsRefreshingAccessToken)
yield return null;
// ... now it's safe to use the APIs
}
// ファイルの更新日時を比較するメソッド
static bool CompareDate(DateTime time1, DateTime time2)
{
if (time1.Year == time2.Year && time1.Month == time2.Month && time1.Day == time2.Day && time1.Hour == time2.Hour && time1.Minute == time2.Minute && time1.Second == time2.Second)
{
return true;
}
return false;
}
// ファイルの拡張子を確認するメソッド
static bool CheckFileExtension(string name, string[] extensions)
{
bool type = false;
foreach (var item in extensions)
{
if (Regex.IsMatch(name, item))
{
type = true;
break;
}
}
return type;
}
public class FileInfo
{
public string localFilePath;
public string localDirPath;
public string remoteId;
public DateTime modifiedTime;
public string name;
}
/// <summary>
/// 指定したフォルダ内のファイルのうち、更新のあるファイルのみダウンロードします。
/// </summary>
/// <param name="localFolderPath">ダウンロード先のフォルダのパス</param>
/// <param name="GoogleFolderId">ダウンロード元のグーグルドライブのフォルダID</param>
/// <param name="downloadAllFiles">全てのファイルをダウンロードするかどうか</param>
/// <param name="extensions">ダウンロードする拡張子の指定(無い場合は全てダウンロードします)</param>
/// <param name="isDebug">デバッグモード</param>
/// <returns></returns>
public static IEnumerator DownloadFiles(string localFolderPath, string GoogleFolderId, bool downloadAllFiles = false, string[] extensions = null, bool isDebug = false)
{
Debug.Log("同期を開始します");
if (EditorUtility.DisplayCancelableProgressBar("同期中", "アクセストークンを取得しています。", 0))
{
EditorUtility.ClearProgressBar();
yield break;
}
// アクセストークンの更新が終わるまで待機
yield return RefreshAccessToken();
if (EditorUtility.DisplayCancelableProgressBar("同期中", "ダウンロード対象を探しています", 0.05f))
{
EditorUtility.ClearProgressBar();
yield break;
}
// ダウンロードするファイルのリストを作成
List<FileInfo> downloadFiles = new List<FileInfo>();
yield return MakeDownloadList(localFolderPath, GoogleFolderId, downloadAllFiles, extensions, isDebug, downloadFiles);
int index = 0;
// ファイルをダウンロードする
foreach (FileInfo downloadFile in downloadFiles)
{
index++;
var downloadFileRequest = GoogleDriveFiles.Download(fileId: downloadFile.remoteId);
var downloadFileAsync = downloadFileRequest.Send();
while (!downloadFileAsync.IsDone)
{
if (EditorUtility.DisplayCancelableProgressBar("同期中", downloadFile.name + "をダウンロード中です", (downloadFileAsync.Progress + index) / downloadFiles.Count * 0.95f + 0.05f))
{
EditorUtility.ClearProgressBar();
yield break;
}
yield return null;
// エラーが発生した場合
if (downloadFileRequest.IsError)
{
Debug.LogError(downloadFileRequest.Error);
yield break;
}
}
// ダウンロードしたファイルを保存
if (!Directory.Exists(downloadFile.localDirPath))
{
Directory.CreateDirectory(downloadFile.localDirPath);
if (isDebug)
{
UnityEngine.Object folderObj = AssetDatabase.LoadAssetAtPath(downloadFile.localDirPath, typeof(UnityEngine.Object));
Debug.Log("フォルダがローカルに存在しなかったため作成しました (" + downloadFile.localDirPath + ")", folderObj);
}
}
File.WriteAllBytes(downloadFile.localFilePath, downloadFileRequest.ResponseData.Content);
File.SetLastWriteTimeUtc(downloadFile.localFilePath, downloadFile.modifiedTime);
if (isDebug)
{
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath(downloadFile.localFilePath, typeof(UnityEngine.Object));
Debug.Log(downloadFile.name + "をダウンロードしました。( " + downloadFile.localFilePath + ")", obj);
}
}
AssetDatabase.Refresh();
Debug.Log("同期が完了しました");
EditorUtility.ClearProgressBar();
}
static IEnumerator MakeDownloadList(string localFolderPath, string GoogleFolderId, bool downloadAllFiles = false, string[] extensions = null, bool isDebug = false, List<FileInfo> downloadFiles = null)
{
using (GoogleDriveFiles.ListRequest fileListRequest = GoogleDriveFiles.List())
{
// 取得するフィールドを設定
fileListRequest.Fields = new List<string> { "files(id, name, size, createdTime, modifiedTime, trashed, mimeType)" };
// クエリの内容を設定
fileListRequest.Q = $"\'{GoogleFolderId}\' in parents";
var fileListAsync = fileListRequest.Send();
while (!fileListAsync.IsDone)
{
yield return null;
// エラーが発生した場合
if (fileListRequest.IsError)
{
Debug.LogError(fileListRequest.Error);
yield break;
}
}
// ファイルリストを取得
UnityGoogleDrive.Data.FileList list = fileListRequest.ResponseData;
if (list.Files.Count == 0)
{
Debug.LogWarning("フォルダ内にファイルが見つかりませんでした。 (FolderID: " + GoogleFolderId + ")");
yield break;
}
foreach (var file in list.Files)
{
// GoogleDrive内ファイルの更新日時(標準時間)
DateTime remoteDate = (DateTime)file.ModifiedTime;
// ローカルファイルのパス
string path = Path.Combine(localFolderPath, file.Name).Replace("\\", "/");
if (file.MimeType == "application/vnd.google-apps.folder")
{
if (isDebug) Debug.Log("子フォルダを検出しましたダウンロードします");
// フォルダの場合は再帰的に呼び出す
yield return MakeDownloadList(path, file.Id, downloadAllFiles, extensions, isDebug, downloadFiles);
continue;
}
// ダウンロードする拡張子が指定されている場合は、指定されていないファイルはダウンロードしない
if (downloadAllFiles == false)
{
if (!CheckFileExtension(file.Name, extensions))
{
if (isDebug) Debug.Log(file.Name + "は指定外の拡張子であるためダウンロードしません");
continue;
}
}
// ゴミ箱にある場合はダウンロードしない
if ((bool)file.Trashed)
{
if (isDebug) Debug.Log(file.Name + "はゴミ箱にあるためダウンロードしません");
continue;
}
if (File.Exists(path))
{
// ローカルファイル更新日時を取得(標準時間)
DateTime localdate = File.GetLastWriteTimeUtc(path);
// 既に同じ更新日時のファイルがある場合はダウンロードしない
if (CompareDate(localdate, remoteDate))
{
if (isDebug) Debug.Log(file.Name + "は更新されていないためダウンロードしません");
continue;
}
else
{
if (isDebug) Debug.Log(file.Name + "は更新されているためダウンロードします");
}
}
else
{
if (isDebug) Debug.Log(file.Name + "はローカルに存在しないためダウンロードします");
}
downloadFiles.Add(new FileInfo { localFilePath = path, localDirPath = localFolderPath, remoteId = file.Id, modifiedTime = remoteDate, name = file.Name });
}
}
}
}
4. フォルダのIDを取得する
同期したいGoogleDriveフォルダのIDを取得します。
フォルダをブラウザで開いたとき、https://drive.google.com/drive/u/0/folders/XXXXXXXXXXX
のXXXXXXXXXXX‘
部分がフォルダのIDとなります。
5. GoogleDriveSettingDataを生成して同期する
ScriptableObject「GoogleDriveSettingData」を生成する
インスペクタのフィールドで設定をして同期ボタンを押す
(「GoogleDriveSettingData」を複数生成し複数のフォルダを同期することもできます)
↓動作するとこんな感じ