【Unity】StreamingAssetsからテキストを読み取る

Unity の StreamingAssets に置いたファイルを読み取る実装例です。

Android と WebGL は特別な処理が必要だそうで UnityWebRequest を使ってデータを読み取るようです。それ以外のプラットフォーム(iOS, Windows, デバッグ中のエディタ上)では普通にファイルに読み取って大丈夫だそうです。

Android はデータが apk/jar(zip) に圧縮されてるから特別な読み取りをしないといけないのですが何故か UnityWebRequest という名前のクラスで読み取れるのでそれを使用したいと思います。*1。WebRequest といいつつローカルにあるファイルを読み取る機能がある程度実装されているようです。ただ、何でもかんでも使えないのかな?なので、それぞれ向けに実装して両対応してみようと思います。

アプリ側からコールする API はプラットフォームの区別なく読み取れるようにしたいと思います。

という訳で、StreamingAssets から Android/iOS/WebGL/Windowsエディタ上で区別なくファイルを読み取るサンプルの紹介です。

確認環境

  • Uinty 2021.3.14f1
  • VisualStudio 2022
  • Windows 11
  • ★UniTask を使用しています

注意

Editor 上で確認したのでAndroid 部分は後で実機確認次第おかしい所があったら修正します

StreamingAssetsUtilクラス

Android と WebGL、それ以外のプラットフォームで ifdef を切って呼び出し方を変えてバイナリとして値を読みだしています。

コード中のコメントの通りですが StreamingAsset の内容を byte[] もしくは string(UTF-8)で取得する機能をサポートしています。

using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

/// <summary>
/// StreamingAssetsからデータを読み取る機能を提供します。
/// </summary>
public static class StreamingAssetsUtil
{
    // メモ
    // Assets > StreamingAssets にフォルダを作成して中にファイルが入ってる事
    //
    // バイナリとテキスト以外は対象外
    // TextureやAudioとかのアセットは読み取り方が違うのでここでは扱わない

    /// <summary>
    /// StreamingAssetsからバイナリデータを読み取ります。
    /// </summary>
    public static async UniTask<byte[]>
        ReadStreamingAssetsData(string path, CancellationToken token)
    {
        string realPath = Application.streamingAssetsPath + "/" + path.TrimStart('/');
#if (UNITY_ANDROID || UNITY_WEBGL) && !UNITY_EDITOR
       return await ReadDataAndroid(realPath, token);
#else
        return await ReadData(realPath, token);
#endif
    }

    /// <summary>
    /// StreamingAssetsからテキストを読み取ります。
    /// </summary>
    public static async UniTask<string>
        ReadStreamingAssetsText(string path, CancellationToken token)
    {
        string realPath = Application.streamingAssetsPath + "/" + path.TrimStart('/');
        Log.Trace($"Application.streamingAssetsPath={Application.streamingAssetsPath}");
#if (UNITY_ANDROID || UNITY_WEBGL) && !UNITY_EDITOR
        return await ReadTextSpesific(realPath, token);
#else
        return await ReadText(realPath, token);
#endif
    }

    //
    // Private Methods
    // - - - - - - - - - - - - - - - - - - - -

    // Android + WebGL向けのバイナリ読み取り
    private static async UniTask<byte[]> ReadDataSpesific(string path, CancellationToken token)
    {
        var ret = await ReadSpesific(path, token);
        return ret.data;
    }

    // Android + WebGL向けのテキスト読み取り
    private static async UniTask<string>
        ReadTextSpesific(string path, CancellationToken token)
    {
        var ret = await ReadSpesific(path, token);
        return ret.text;
    }

    // Android + WebGL向けの読み取り処理
    private static async UniTask<DownloadHandler>
        ReadSpesific(string path, CancellationToken token)
    {
        // WebRequestを使って読み取る
        var req = UnityWebRequest.Get(path);
        await req.SendWebRequest().ToUniTask(cancellationToken: token);
        if (req.result != UnityWebRequest.Result.Success) // 
        {
            // エラーどう処理するかは各自決める
            throw new UnityException($"処理に失敗しました。path={path}, code={req.result}");
        }
        return req.downloadHandler;
    }

    // 通常の環境のバイナリ読み取り
    private static async ValueTask<byte[]> ReadData(string path, CancellationToken token)
    {
        // 普通のファイルの読み書き
        using var fs = new FileStream(path, FileMode.Open);
        var array = new byte[fs.Length];
        await fs.ReadAsync(array, 0, (int)fs.Length, token);
        return array;
    }

    // 通常の環境のバイナリ読み取り(UTF-8)
    private static async ValueTask<string> ReadText(string path, CancellationToken _)
    {
        // 普通のファイルの読み書き
        using var fs = new FileStream(path, FileMode.Open);
        using var sr = new StreamReader(fs);
        return await sr.ReadToEndAsync();
    }
}

// ★(1) UnityWebRequest の isNetworkError、isHttpError、isError は非推奨にらしいです
// result プロパティを見て失敗の原因ごとにエラーハンドリングしましょう

// ★(2) GetTexture、GetAudioClipとかも軒並み使用不可になってるみたいです。
// 代替手段がObsoleteの説明に書いてあるのでそっちを使用しましょう

使い方

以下の通り使用できます。

public class Sample : MonoBehaviour
{
    public async void Foo()
    {
        CancellationToken token = this.GetCancellationTokenOnDestroy();
        await StreamingAssetsUtil.ReadStreamingAssetsData("sample.bin", token);
    }
}

もし取得したデータを UTF-8 の文字列として取得したい場合、GetText() メソッドや text プロパティから値を取得できるので適宜拡張する感じになると思います。

*1:WWWクラスを使うのはもう古いらしいです