【GCP/Unity】Unity から Google Cloud の API を叩く基本的な流れ

# はじめに
## テスト環境
- IDE
    - Unity：6000.0.25f1
    - Visual Studio 2022：17.11.5
- プラグイン等
    - UniTask：2.5.10
    - NuGetForUnity：4.1.1
    - Google.Apis.Auth：1.68.0
# 手順
## サービスアカウントの作成
### 手順1
以下の記事などを参考にして Google アカウントの作成や無料トライアルへの登録、プロジェクトの作成などの Googe Cloud の初期設定を行う。
- [Google Cloud を初めて利用する方は、ここから始めましょう](https://cloud.google.com/blog/ja/topics/developers-practitioners/new-google-cloud-start-here)
- [【超初心者向け】Google Cloudを始めるための事前準備](https://www.topgate.co.jp/blog/google-service/29633)
- [基礎から学ぶGCP　第1回 〜GCPを使う準備をする〜](https://cloud-ace.jp/column/detail148/)
- [Google Cloud（旧GCP）無料で使ってみた！クラウド初心者もかんたんに開設、始め方大解説（前編：説明編）](https://blog.g-gen.co.jp/entry/googlecloud-start-01)
- [【Google Cloud】【最短10分】無料ではじめるGoogle Cloud入門](https://sight-r.sts-inc.co.jp/google_cloud_article/free_trial/)
- [【初心者向け】Google Cloudのアカウント作成手順を分かりやすく解説！](https://note.com/ogawa_analyze/n/n0d8c628a4b9e)
### 手順2
[Google Cloud](https://console.cloud.google.com) にアクセスして「[IAM と管理](https://console.cloud.google.com/iam-admin)＞サービスアカウント」に行き、「サービスアカウントを作成」を押す。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/30808909-1fd4-9e76-8dd3-f6d177aaa354.png">

### 手順3
「サービスアカウント名」と「サービスアカウント ID」を入力して「作成して続行」を押す。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/4dd073b5-f525-a4e4-f6cd-38531ea6f077.png">

### 手順4
必要に応じて「ロールを選択」を押してロールを選択し、「続行」を押す。

例：Cloud Storage のオブジェクトを読み取る場合は「Cloud Storage ＞ Storage オブジェクト閲覧者」など

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/5aa40251-a1a2-b103-c0eb-cdc3603b8681.png">

### 手順5
必要に応じて「サービスアカウントユーザーロール」と「サービスアカウント管理者ロール」を設定して「完了」を押す。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/621bf755-5338-87a2-f7b2-af3ff0277b44.png">

## 秘密鍵のダウンロード
### 手順6
作成したサービスアカウントを選択して「キー＞鍵を追加＞新しい鍵を作成」を押す。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/e18bdc2e-c71a-d1c2-fddd-0a71af20d1f5.png">

### 手順7
「キーのタイプ」で「JSON」を選択して右下の「作成」を押し、JSON ファイルをダウンロードする。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/43eeebff-d37d-8eba-7aed-7733b4e2c9a7.png">

## 依存関係のインポート
### 手順8
[UniTask](https://github.com/Cysharp/UniTask) と [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity) の最新版のリリースを GitHub からダウンロードし、Assets フォルダ内で右クリックして「Import Package ＞ Custom Package...」を選択し、ダウンロードした .unitypackage ファイルをインポートする。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/44c4e601-2856-b9bb-558d-dda4df6f78ce.png">

### 手順9
エディタ上部の「NuGet ＞ Manage NuGet Packages」を選択して NuGet のウィンドウを開く。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/ca9be973-90d8-181e-49a3-eeaa964fe672.png">

### 手順10
検索窓に「google api」等と入力して「Search」を押し、「Google.Apis.Auth」の「Install」を押してインストール完了後にウィンドウを閉じる。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/d92fc150-1d9c-f1c8-e781-0e32f6af1c8e.png">

## API の使用（Cloud Translation API の場合）
### 手順11
Google Cloud に戻り、使用したい API の「有効にする」を押す。

<img width="500" alt="TSUBASAMUSU.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670397/3fd941ab-6b1a-6b21-0176-81fb7b5e6da5.png">

### 手順12
Unity にて、Google Cloud の API を叩く際に必要なアクセストークンを取得する以下のような関数を作成する。
```cs:GetGoogleCloudJwtAsync()
/*
using Cysharp.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using System;
using System.IO;
using System.Text;
*/
public static async UniTask<string> GetGoogleCloudAccessTokenAsync(TextAsset serviceAccountJsonFile, string[] scopes)
{
    try
    {
        if (serviceAccountJsonFile == null || string.IsNullOrEmpty(serviceAccountJsonFile.text))
        {
            Debug.LogError("The service account JSON file is not set or empty.");

            return string.Empty;
        }

        byte[] jsonData = Encoding.UTF8.GetBytes(serviceAccountJsonFile.text);

        using MemoryStream memoryStream = new(jsonData);

        GoogleCredential googleCredential = GoogleCredential.FromStream(memoryStream).CreateScoped(scopes); ;

        if (googleCredential.IsCreateScopedRequired)
        {
            Debug.LogError("The provided credentials are not valid for generating JWT.");

            return string.Empty;
        }

        return await ((ServiceAccountCredential)googleCredential.UnderlyingCredential).GetAccessTokenForRequestAsync();
    }
    catch (Exception exception)
    {
        Debug.LogException(exception);

        return string.Empty;
    }
}
```
### 手順13
API ドキュメントを確認しながら API を叩く関数を作成する。
ヘッダーの「Authorization」に Google Cloud 用のアクセストークンを渡してあげるのがポイント。
（以下は Cloud Translation API の例）
- 構造体
    - リクエスト
        ```cs:Request.cs
        using System;
        
        [Serializable]
        public struct Request
        {
            public string[] q;
        
            public string target;
        }
        ```
    - レスポンス
        ```cs:Response.cs
        using System;
        
        [Serializable]
        public struct Response
        {
            public Data data;
        }
        ```
        ```cs:Data.cs
        using System;
        
        [Serializable]
        public struct Data
        {
            public Translation[] translations;
        }
        ```
        ```cs:Translation.cs
        using System;
        
        [Serializable]
        public struct Translation
        {
            public string translatedText;
        
            public string detectedSourceLanguage;
        }
        ```
- 関数
    ```cs:GetTranslatedTextAsync()
    /*
    using Cysharp.Threading.Tasks;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using UnityEngine;
    using UnityEngine.Networking;
    */
    
    public static async UniTask<Response> GetTranslatedTextAsync(string googleCloudAccessToken, string[] sourceTexts, string targetLanguageCode)
    {
        Dictionary<string, string> headers = new()
        {
            {
                "Content-type",
                "application/json"
            },
            {
                "Authorization",
                "Bearer " + googleCloudAccessToken//アクセストークンを渡す
            }
        };
    
        Request request = new()
        {
            q = sourceTexts,
            target = targetLanguageCode
        };
    
        using UnityWebRequest unityWebRequest = new("https://translation.googleapis.com/language/translate/v2", "POST")
        {
            uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(JsonUtility.ToJson(request))),
            downloadHandler = new DownloadHandlerBuffer()
        };
    
        foreach (KeyValuePair<string, string> header in headers)
        {
            unityWebRequest.SetRequestHeader(header.Key, header.Value);
        }
    
        try
        {
            await unityWebRequest.SendWebRequest();
        }
        catch (Exception exception)
        {
            Debug.LogException(exception);
    
            return new();
        }
    
        if (unityWebRequest.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError(unityWebRequest.error);
    
            return new();
        }
    
        return JsonUtility.FromJson<Response>(unityWebRequest.downloadHandler.text);
    }
    ```
### 手順14
実際に API を叩いて動作を確認する。
アクセストークンを発行する際に、API ドキュメントに書かれている必要なスコープを渡してあげるのがポイント。
```cs:Start()
private async void Start()
{
    string[] scopes =
    {
        "https://www.googleapis.com/auth/cloud-translation"
    };

    //サービスアカウントの秘密鍵を作成した際にダウンロードした JSON ファイルを serviceAccountJsonFile 変数に入れる
    string googleCloudAccessToken = await GetGoogleCloudAccessTokenAsync(serviceAccountJsonFile, scopes);

    string[] sourceTexts =
    {
        "Hello world",
        "My name is Tsubasamusu."
    };

    Response response = await GetTranslatedTextAsync(googleCloudAccessToken, sourceTexts, "de");

    Debug.Log(JsonUtility.ToJson(response));
}
```
# 最後に
## 参考記事
- [テキストの翻訳（Basic）](https://cloud.google.com/translate/docs/basic/translating-text)
- [Cloud Translation API を使用する際のポイント](https://qiita.com/howdy39/items/89614a342d02d346fb72)