ForgeVision Engineer Blog

フォージビジョン エンジニア ブログ

UnityGoogleDriveを使わずにUnityでGoogleDriveのファイルを読み書きする

こんにちは、VR事業部の石井です。

今回はUnityからGoogle Driveのファイルを読み書きする方法を紹介します。

はじめに

UnityからGoogle Driveのファイルを読み書きする方法を検索するとUnityGoogleDriveというOSSがよくヒットします。
UnityGoogleDriveはOAuthで認証するアカウントを指定できますが、認証するにはブラウザを開く必要があります。
UnityGoogleDriveは他の認証方法をサポートしていないのでブラウザを開くのは避けられません。

例えばサーバーPCからGoogle Driveにアクセスしたい場合など、ウェブブラウザを経由せずに認証を通す必要があります。
そこで本記事ではサービスアカウントを使用できるGoogle Drive Api v3を使います。

バージョン情報など

各種バージョン情報は以下の通りです。

手順

Google Cloud Platformのセットアップ

Google Cloud Platformのコンソールにログインします。
cloud.google.com

まずは新しいプロジェクトを作成します。
上部にあるリストボックスをクリックします。

右上の[新しいプロジェクト]をクリックします。

プロジェクト名を入力します。
ここでは「google-drive-sample」とします。
適当な組織を選択し、[作成]ボタンをクリックします。

プロジェクトができました。

次に左上のハンバーガーメニューのアイコンをクリックして、APIとサービス>認証情報をクリックします。

上部の[+認証情報を作成]をクリックし、サービスアカウントをクリックします。

サービスアカウント名を入力します。
ここでは「service-account-sample」とします。
なお、このタイミングで自動的にそのサービスアカウントのメールアドレスが決定します。
[完了]ボタンをクリックします。

サービスアカウントの欄で、作成したサービスアカウントのメールアドレスをクリックします。

[キー]タブをクリックします。
鍵を追加>新しい鍵をクリックします。

JSON形式を選択し、[作成]ボタンをクリックします。

これでキーがダウンロードできたので、Cドライブ直下にsampleフォルダを作成してそこに保存しておきます。
このキーは下記スクリプトの「キーのJSONファイル」で使用します。

次にGoogle Drive APIを有効化します。
APIとサービス>有効なAPIとサービスで[+APIとサービスの有効化]をクリックします。

検索欄でdriveと入力して検索します。
検索結果にある「Google Drive API」をクリックします。
[有効にする]ボタンをクリックします。

これでGoogle Cloud Platformのセットアップが完了しました。

Google Driveのセットアップ

Google Driveに読み書きしたいフォルダを作成します。
ここではマイドライブに「Sample」というフォルダを作成しました。
作成したフォルダに移動し、フォルダ名をクリックするとメニューが表示されるので、[共有]ボタンをクリックします。

入力欄に先程作成したサービスアカウントのメールアドレスを入力します。
右のリストで「編集者」を選択し、[共有]ボタンをクリックします。


Sampleフォルダに適当なファイルを置いておきます。
ここでは「DriveFile.txt」とします。

SampleフォルダのURLの末尾のIDをメモしておきます。
このIDは下記スクリプトの「Google DriveのフォルダーID」で使用します。

Unityプロジェクトのセットアップ

新規プロジェクトを作成し、Package Managerを開きます。

左上の[+]ボタンをクリックし、「Add package from git URL...」を選択します。

以下のURLを貼り付けて、[Add]ボタンをクリックします。
https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity これでNuget for Unityをインポートできました。

Nuget for Unityを開きます。

Search欄に「google.apis.drive」と入力して、[Search]ボタンをクリックします。

「Google.Apis.Drive.v3」の[Install]ボタンをクリックします。

Google.Apis.Drive.v3以外にも依存関係のあるライブラリが自動的にインポートされます。

Unityスクリプト

今回はボタンクリックでファイル一覧表示、アップロード、ダウンロードをします。
まずは空のC#スクリプトを作成します。
ここでは「GoogleDriveSample.cs」とします。
そして、以下のように実装します。

using System;
using System.Collections.Generic;
using System.IO;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Services;
using Google.Apis.Upload;
using TMPro;
using UnityEngine;

public class GoogleDriveSample : MonoBehaviour
{
    /// <summary>
    /// キーのJSONファイル
    /// </summary>
    private const string JSON_FILE = @"C:\sample\[サービスアカウントのJSON].json";

    /// <summary>
    /// Google DriveのフォルダーID
    /// </summary>
    private const string GOOGLE_DRIVE_FOLDER_ID = "[Google DriveのフォルダーID]";

    /// <summary>
    /// アップロードするファイルパス
    /// </summary>
    private const string FILE_PATH = @"C:\sample\LocalFile.txt";

    /// <summary>
    /// ダウンロードするファイルID
    /// </summary>
    private const string DOWNLOAD_FILE_ID = "[ダウンロードするファイルID]";

    /// <summary>
    /// ダウンロードファイル保存パス
    /// </summary>
    private const string SAVE_PATH = @"C:\sample";

    /// <summary>
    /// キャンバステキスト
    /// </summary>
    [SerializeField] private TextMeshProUGUI _canvasText;

    /// <summary>
    /// Google Driveのサービス
    /// </summary>
    private DriveService _driveService;

    private void Start()
    {
        // 認証情報を取得
        GoogleCredential credential;
        using (var stream = new FileStream(JSON_FILE, FileMode.Open, FileAccess.Read))
        {
            credential = GoogleCredential.FromStream(stream).CreateScoped(DriveService.ScopeConstants.Drive);
        }

        // Drive APIのサービスを作成
        _driveService = new DriveService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credential,
            ApplicationName = "Google Drive Sample",
        });

        OutputCanvasText("Service:" + _driveService.Name);
    }

    /// <summary>
    /// ファイルの一覧表示
    /// </summary>
    public void List()
    {
        try
        {
            List(_driveService);
        }
        catch (Exception e)
        {
            OutputCanvasText(e.Message);
        }
    }

    /// <summary>
    /// ファイルの一覧表示
    /// </summary>
    /// <param name="service">Google Driveのサービス</param>
    private void List(DriveService service)
    {
        OutputCanvasText("List start");

        // フォルダ内を検索する
        var request = service.Files.List();
        request.Q = "'" + GOOGLE_DRIVE_FOLDER_ID + "' in parents";

        // files(*) だとすべての情報が取得できる
        request.Fields = "nextPageToken, files(id, name, size, createdTime)";
        var files = new List<Google.Apis.Drive.v3.Data.File>();
        do
        {
            var result = request.Execute();
            files.AddRange(result.Files);
            request.PageToken = result.NextPageToken;
        } while (!string.IsNullOrEmpty(request.PageToken));

        // 結果を出力する
        foreach (var file in files)
        {
            OutputCanvasText("Name: " + file.Name + " ID: " + file.Id +
                             " Size: " + file.Size + "byte CreatedTime: " + file.CreatedTime);
        }

        OutputCanvasText("List end");
    }

    /// <summary>
    /// ファイルのダウンロード
    /// </summary>
    public void Download()
    {
        try
        {
            Download(_driveService);
        }
        catch (Exception e)
        {
            OutputCanvasText(e.Message);
        }
    }

    /// <summary>
    /// ファイルのダウンロード
    /// </summary>
    /// <param name="service">Google Driveのサービス</param>
    private void Download(DriveService service)
    {
        OutputCanvasText("Download start");

        // メタデータを取得
        var file = service.Files.Get(DOWNLOAD_FILE_ID).Execute();
        if (file == null)
        {
            OutputCanvasText("fileがnull");
            return;
        }

        OutputCanvasText("Name: " + file.Name + " ID: " + file.Id);

        // ダウンロード
        var request = service.Files.Get(DOWNLOAD_FILE_ID);
        var fileStream = new FileStream(Path.Combine(SAVE_PATH, file.Name), FileMode.Create, FileAccess.Write);
        request.Download(fileStream);
        fileStream.Close();

        OutputCanvasText("Download end");
    }

    /// <summary>
    /// ファイルのアップロード
    /// </summary>
    public void Upload()
    {
        try
        {
            // アップロードするファイルのメタデータを作成
            var fileMetadata = new Google.Apis.Drive.v3.Data.File()
            {
                Name = Path.GetFileName(FILE_PATH),
                Parents = new[] {GOOGLE_DRIVE_FOLDER_ID},
            };

            Upload(_driveService, fileMetadata);
        }
        catch (Exception e)
        {
            OutputCanvasText(e.Message);
        }
    }

    /// <summary>
    /// ファイルのアップロード
    /// </summary>
    /// <param name="service">Google Driveのサービス</param>
    /// <param name="fileMetadata">メタデータ</param>
    /// <exception cref="Exception"></exception>
    private void Upload(DriveService service, Google.Apis.Drive.v3.Data.File fileMetadata)
    {
        OutputCanvasText("Upload start");

        var request = service.Files.Create(fileMetadata, new FileStream(FILE_PATH, FileMode.Open), "text/plain");
        request.Fields = "name, id";
        var uploadProgress = request.Upload();
        if (uploadProgress.Status != UploadStatus.Completed)
        {
            OutputCanvasText("Upload failed:" + uploadProgress.Status);
            return;
        }

        // アップロードされたファイルの名前とIDを取得
        var file = request.ResponseBody;
        OutputCanvasText("Name:" + file.Name + " ID:" + file.Id);

        OutputCanvasText("Upload end");
    }

    /// <summary>
    /// キャンバスにあるテキストに追記する
    /// </summary>
    /// <param name="text">テキスト</param>
    private void OutputCanvasText(string text)
    {
        _canvasText.text += text + "\n";
    }
}

上記スクリプトのJSON_FILE (キーのJSONファイル)、GOOGLE_DRIVE_FOLDER_ID (Google DriveのフォルダーID)は前記Google Cloud Platformのセットアップ、Google Driveのセットアップで取得した値を入力してください。
また、このサンプルでは通信に同期処理をしているため、大きなファイルを処理するとUIが固まります。
実際に利用するときはasync/awaitとExecuteAsyncメソッド、UploadAsyncメソッドを使うと良いでしょう。

シーンに空のGameObjectを作成し、「GoogleDriveSample」とします。
そのオブジェクトに上記のGoogleDriveSample.csをアタッチします。

空のCanvasを作成します。
そこにボタンを3つ並べ、「List Button」、「Download Button」、「Upload Button」とします。
さらにTextを追加し、「Canvas Text」とします。

List ButtonオブジェクトのButtonコンポーネントでOn Clickリストを1つ追加し、GoogleDriveSampleオブジェクトを参照します。
発生させるイベントでGoogleDriveSample.Listを選択します。
Download ButtonはGoogleDriveSample.Download、Upload ButtonはGoogleDriveSample.Uploadになるように同様に設定します。

GoogleDriveSampleオブジェクトのCanvas TextにCanvas TextオブジェクトのTextMeshProを参照します。

SkyBoxのままだと文字が見にくいので、Main CameraのClear Flagsを「Solid Color」、Backgroundを黒に近い色にします。

最後にC:\sampleフォルダに適当なファイルを置いておきます。
ここでは「LocalFile.txt」とします。

これで準備が整いました。

実行

実行します。
ゲームビューで[List]ボタンをクリックします。

Google Driveに保存したファイルの情報が表示されました。
このファイルをダウンロードしたいので、IDをメモしておきます。
一度Unityの実行を停止し、上記スクリプトのDOWNLOAD_FILE_ID (ダウンロードするファイルID)にそのIDを入力します。

再度実行します。
[Download]ボタンをクリックします。

C:\sampleフォルダを見ると「DriveFile.txt」が保存されています。

[Upload]ボタンをクリックします。

Google DriveのSampleフォルダを見ると「LocalFile.txt」が保存されています。

まとめ

UnityGoogleDriveを使わずにUnity上でGoogle Driveのファイルを読み書きできました。
OAuthを使用せずサービスアカウントを使うという選択肢もあることを知っておくと、要件に合わせて使い分けができます。
cloud.google.com cloud.google.com
それぞれの特性を理解した上でお試しください!