北極熊在沙漠中進行遊戲開發和設計的故事

Unity 上傳文件到 GCP Cloud Storage

如何透過Unity上傳文件, 圖片, 影片, 模型到GCP Cloud Storage, 並且取得存取網址。

第一步:GCP 事前准備

a. 註冊號GCP和填好付款資訊之後,打開Cloud Storage服務

b. 跟著引導步驟新增一個新的Bucket (可以理解成一個存放檔案的文件夾)

c. 去到權限頁面,新增一個allUsers的儲存空間檢視者用戶

d. 這裡可以快速切換要不要把這個儲存空間對外公開(我們先把它公開)

e. 你可以在這裡嘗試上傳一些文件檔案看看

f. 接下來,去打開IAM與管理的服務

g. 打開服務賬戶標籤,點擊建立服務賬戶

h. 根據指示輸入一個名字,將角色設成 Storage 管理員

i. 建立好後,點擊打開,去到金鑰頁面,新增一個金鑰,並且把Json檔案下載下來。

GCP 的初始設定到這裡就告一段落了,接下來我們去Unity實戰 !

第二步: Unity安裝GCP服務

a. 下載 Nuget For Unity

b. 下載好後,在Unity中打開,搜尋Google GCP的套件安裝

我們要安裝 Google.ApisGoogle.Cloud.Storage.V1 的套件

c. 接下來把我們剛才下載的Json檔案放到Streaming Asset之中

做到這裡基本的環境架設就弄好了,接下來可以開始寫Code啦。

第三步: Unity 上傳文件

完整的代碼我會放在下面和提供UnityPackage,我們先來小部分拆解代碼:

a. 先寫一個ICloudStorage 的Interface

包含3個Function:

  1. Initialize – 帶入GCP 的 BucketName 和 Json的路徑
  2. UploadObject – 上傳本地檔案的Function
  3. UploadObjectByUrl – 直接上傳網絡URL檔案的Function

b. 寫一個CStorageImp Class繼承這個Interface:

c. 初始化StorageClient的代碼(這個是GCP內建的Class)

d. 上傳文件到GCP Cloud Storage的Function

e. 從本地文件路徑上傳就開File讀取

f. 從URL上傳的話就用WebRequest先抓一下

g. 最後把Interface的Function接好

using System;

namespace YFrame.API.Google.CloudStorage
{
    public interface ICloudStorage
    {
        public void Initialize(string bucketName, string jsonCrePath);
        public void UploadObject(string localFilePath, string remoteFilePath, Action<double> progressAction);
        public void UploadObjectByUrl(string fileUrl, string remoteFilePath, Action<double> progressAction);
    }
}
using Google.Cloud.Storage.V1;
using System;
using System.IO;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Upload;
using UnityEngine;
using UnityEngine.Networking;
using YFrame.Utility;

namespace YFrame.API.Google.CloudStorage
{
    public class CStorageImp : ICloudStorage
    {
        public string BucketName = "your-bucket-name";
        public string JsonCrePath = "path-to-your-service-account-json-key-file";

        private StorageClient storageClient;

        public void Initialize(string bucketName, string jsonCrePath)
        {
            BucketName = bucketName;
            JsonCrePath = jsonCrePath;
            InitializeStorageClient();
        }

        private void InitializeStorageClient()
        {
            try
            {
                GoogleCredential credential;
                using (var jsonStream = new FileStream(JsonCrePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    credential = GoogleCredential.FromStream(jsonStream);
                }

                storageClient = StorageClient.Create(credential);
            }
            catch (Exception ex)
            {
                Debug.LogError($"GCP Cloud Storage: Error initializing storage client: {ex.Message}");
            }
        }

        public async void UploadObject(string localFilePath, string remoteFilePath, Action<double> progressAction)
        {
            await UploadFile(localFilePath, remoteFilePath, progressAction);
        }

        public async void UploadObjectByUrl(string fileUrl, string remoteFilePath, Action<double> progressAction)
        {
            await UploadFromUrl(fileUrl, remoteFilePath, progressAction);
        }

        private async Task UploadFile(string localFilePath, string remoteFileName, Action<double> progressAction)
        {
            try
            {
                await using var fileStream = new FileStream(localFilePath, FileMode.Open, FileAccess.Read);
                await UploadStream(fileStream, remoteFileName, progressAction);
            }
            catch (Exception ex)
            {
                Debug.LogError($"GCP Cloud Storage: Error uploading file: {ex.Message}");
            }
        }

        private async Task UploadFromUrl(string url, string remoteFileName, Action<double> progressAction)
        {
            using var www = UnityWebRequest.Get(url);
            www.SendWebRequest();

            while (!www.isDone)
            {
                await Task.Yield();
            }

            if (www.result != UnityWebRequest.Result.Success)
            {
                throw new Exception($"GCP Cloud Storage: Failed to download file: {www.error}");
            }

            using var memoryStream = new MemoryStream(www.downloadHandler.data);
            await UploadStream(memoryStream, remoteFileName, progressAction);
        }

        private async Task UploadStream(Stream stream, string remoteFileName, Action<double> progressAction)
        {
            var progress = new Progress<IUploadProgress>(p =>
            {
                var percentComplete = (double)p.BytesSent / stream.Length * 100;

                Debug.Log($"GCP Cloud Storage Upload Progress: " +
                          $"{percentComplete:F2}% ({p.BytesSent}/{stream.Length} bytes)");

                // Update UI Action on main thread
                if (progressAction != null)
                {
                    UnityMainThreadDispatcher.Instance().Enqueue(() => { progressAction?.Invoke(percentComplete); });
                }
            });

            var uploadObjectOptions = new UploadObjectOptions
            {
                ChunkSize = UploadObjectOptions.MinimumChunkSize
            };

            await storageClient.UploadObjectAsync(
                BucketName,
                remoteFileName,
                null,
                stream,
                options: uploadObjectOptions,
                progress: progress
            );

            Debug.Log($"GCP Cloud Storage: Success Uploaded {remoteFileName} to {BucketName}.");
        }
    }
}

第四步: 最終調用

a. 新建一個簡單的Demo Scene場景進行測試

包含的UI元件:

  1. Dropdown – 可以切換本地上傳 / URL上傳
  2. Local File InputField – 要上傳的文件路徑
  3. Remote File Input Field – GCP Cloud Storage 上的儲存路徑名字
  4. 上傳按鈕
  5. 上傳進度顯示文字

b. 新增一個Cloud Storage View 的Mono Behavior 代碼:

記得一開始要初始化CloudStorage的函數,把BucketName和Json路徑帶進去

c. 按鈕點擊的時候呼叫CloudStorage的UploadObject即可

簡單乾淨!

接著打開你的Scene,在Streaming Asset放一些測試圖片,上傳測試看看即可。

上傳成功之後,可以去GCP Bucket上刷新看看有沒有出現。

using System.IO;
using UnityEngine;
using UnityEngine.UI;

namespace YFrame.API.Google.CloudStorage.Scene
{
    public class CloudStorageView : MonoBehaviour
    {
        public string bucketName = "your-bucket-name";
        public string jsonKeyFilePath = "path-to-your-service-account-json-key-file";

        [Header("File Upload UI")] 
        public Dropdown UploadTypeDropdown; 
        public Button UploadBtn;
        public Text UploadProgressText;
        public InputField FileInputField;
        public InputField RemoteFileNameInputField;


        private ICloudStorage CloudStorage = new CStorageImp();

        private void Start()
        {
            CloudStorage.Initialize(bucketName,  Path.Combine(Application.streamingAssetsPath, jsonKeyFilePath));
            UploadBtn.onClick.AddListener(UploadBtnOnClick);
        }

        private void UploadBtnOnClick()
        {
            switch (UploadTypeDropdown.value)
            {
                // Streaming Local File
                case 0:
                {
                    var filePath = Path.Combine(Application.streamingAssetsPath, FileInputField.text);
                    CloudStorage.UploadObject(filePath, RemoteFileNameInputField.text, UpdateProgressText);
                    return;
                }
                // Upload By URL
                case 1:
                {
                    CloudStorage.UploadObjectByUrl(FileInputField.text, RemoteFileNameInputField.text, UpdateProgressText);
                    break;
                }
            }
        }

        private void UpdateProgressText(double progress)
        {
            UploadProgressText.text = $"Upload Progress: {progress:F2}";
        }
    }
}

效果展示

完成的Unity Package包下載:

Share this article
Shareable URL
Prev Post

Git 是什麼?全面搞懂Git知識懶人包

Next Post

12週做完一年工作

Comments 1
發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Read next

與身體對話,就是與神對話

身體裡的每一個細胞、每一個器官都有「意識」同時也有「潛意識」 身體各個部位要帶給你什麼訊息: 身體部位 訊號 特質 怎麼做 肩部骨骼與肌肉 硬邦邦 不懂得麻煩人,不懂得撒嬌,不懂得拜托人…

讀書會 3 – <有錢人跟你想的不一樣>

這次我們讀《有錢人跟你想的不一樣》 這是一本影響我對金錢觀念最大的一本書 第一次瞭解到原來頭腦的意識觀念 -> 決定了自己可以擁有多少財富 讓我們一起來進入這場讀書會吧: 1. 檢視對於金錢的觀念…

如何在Text Mesh Pro使用中文字?

在Unity中要使用TextMeshPro中文字的時候會出現亂碼,每次要建字要輸入1萬多個中文字也很麻煩,本文將使用動態生成的方式建置TMP的中文字體。