【Unity】WebGLビルドのStreamingAssetsパスを変更する

こじゃらこじゃら

WebGLビルドのStreamingAssetsのパスが変更できず困っているの…

このはこのは

公式で変更手段が提供されているわ。状況によっては少し特殊な方法を使わないといけないけど可能だわ。

UnityのWebGLビルドでは、初期設定ではアセットバンドル等で使用されるStreamingAssetsフォルダのパスは、ゲームの公開URL直下のStreamingAssetsパスに設定されています。

StreamingAssetsパスの例

https://example.nekojara.city/path/to/game/StreamingAssets

参考:Unity – Manual: Streaming Assets

このURLは、Application.streamingAssetsPathが返す結果と一緒です。

参考:Unity – Scripting API: Application.streamingAssetsPath

WebGLビルドにおけるStreamingAssetsのパスの変更手段は、ビルドして出力されるページのファイル(index.html)を編集する方法として公式で提供されています。

var config = {
    streamingAssetsUrl: "StreamingAssets", // この部分を変更する
};

・・・(中略)・・・

var script = document.createElement("script");
script.src = loaderUrl;
script.onload = () => {
    // Unityコンテンツのインスタンス生成(初期化)
    createUnityInstance(canvas, config, (progress) => {

・・・(以後省略)・・・

参考:Unity – Manual: WebGL templates

しかし、unityroomで公開するゲームでStreamingAssetsを使用したい場合など、index.htmlの編集以外の方法でパス変更したい場合、ハック的な方法ですが次の代替手段で書き換え可能です。

  • ビルド後に.loader.jsファイルを書き換える
  • jslibで動的にパスを変更する処理を実装する

本記事では、これら3種類のWebGLビルドにおけるStreamingAssetsのパスを書き換える方法を解説していきます。

動作環境
  • Unity 2023.3.20f1

スポンサーリンク

想定する状況

次のようなURLでゲームを公開しているものとします。

https://example1.nekojara.city/path/to/game/index.html

この時、StreamingAssetsのパスはデフォルトでは次のようになっています。

https://example1.nekojara.city/path/to/game/StreamingAssets

このStreamingAssetsパスを次のような別ドメインなどのパスに変更することを想定します。

https://example2.nekojara.city/other/path

変更後のパスは同ドメインであっても問題ありません。

本記事では、UnityプロジェクトのビルドのターゲットプラットフォームがWebGLになっていることを前提で手順を進めます。また、変更結果の確認はコンソールログ経由で行うものとします。

参考:Unity – Manual: Build Settings

index.htmlを書き換える(静的)

1つ目の方法は、Unityが紹介している公式の手順です。特別な理由がない限りはこの方法を推奨します。

WebGLビルドすると、ビルドフォルダにはゲームの公開ページ用のindex.htmlファイルが生成されます。

このindex.htmlファイル内のstreamingAssetsUrlに指定している文字列を変更することで、StreamingAssetsのパスをデフォルトから変更できます。

streamingAssetsUrl: "StreamingAssets",

これは、Unityコンテンツのインスタンス生成関数createUnityInstanceの引数に渡すconfigオブジェクトのフィールドでconfig.streamingAssetsUrlフィールドがそのままStreamingAssetsパスのURLとして設定されます。

var config = {
    streamingAssetsUrl: "StreamingAssets",
};

・・・(中略)・・・

var script = document.createElement("script");
script.src = loaderUrl;
script.onload = () => {
    createUnityInstance(canvas, config, (progress) => {

・・・(以後省略)・・・

参考:Unity – Manual: WebGL templates

ビルド後のindex.htmlを直接手で書き換えても良いですが、本記事ではカスタムテンプレートを作成して適用するものとして解説します。

WebGLTemplateの準備

index.htmlファイル等のテンプレートは、カスタムのものを適用できます。[Unityプロジェクトフォルダ]/Assets/WebGLTemplates/の下にテンプレートを置き、それをPlayer settingsから適用すれば良いです。

本記事では、既存のDefaultテンプレートを流用するものとします。

まず、対象のUnityアプリケーションフォルダに格納されている以下フォルダを複製します。

コピー元

[Unityフォルダ]/Hub/Editor/[Unityバージョン]/Editor/Data/PlaybackEngines/WebGLSupport/BuildTools/WebGLTemplates/Default

コピー先

[Unityプロジェクトフォルダ]/Assets/WebGLTemplates/Custom

「Custom」の部分はお好みの名前で問題ありません。

作成したカスタムテンプレートを適用するために、トップメニューよりEdit > Project Settings…の順に選択してProject Settingsウィンドウを開きます。左側のツリーよりPlayerを選択し、Resolution and Presentation > WebGL Templateよりカスタムテンプレートを選択してください。

index.htmlの書き換え

次に、カスタムテンプレートフォルダ配下のindex.htmlの内容を書き換えます。

テキストエディタ等でファイルを開き、以下行をURLまたは相対パスに書き換えてください。

変更前

streamingAssetsUrl: "StreamingAssets",

変更後の例

streamingAssetsUrl: "https://example2.nekojara.city/other/path",
メモ

例えばURL形式ないパスを指定した場合、公開ページを基準とした相対パスとして扱われます。

streamingAssetsUrl: "other/path",

確認用スクリプト

本記事では、正しくStreamingAssetsのパスが変更されているかをコンソールログから確認するものとします。

以下、StreamingAssetsのパスをログ出力するだけのスクリプトです。

PrintPathExample.cs
using UnityEngine;

public class PrintPathExample : MonoBehaviour
{
    private void Start()
    {
        // StreamingAssetsのパスをログ出力
        Debug.Log($"StreamingAssetsPath: {Application.streamingAssetsPath}");
    }
}

上記をPrintPathExample.csという名前でUnityプロジェクトに保存し、ビルド対象シーンに適当なゲームオブジェクトを配置してアタッチしてください。

例では、起動直後に空シーンを読み込み、この中で表示するものとします。

実行結果

ログ出力されるStreamingAssetsのパスがデフォルトのものから変更されていることが確認できました。

.loader.jsの中身を書き換える(静的)

2つ目は、ビルド後に生成される.loader.jsファイルの中身を書き換える方法です。index.htmlを直接書き換えられない場合の代替候補の一つになります。

ここから先は正式には提供されていないハック的な方法になるためご注意ください。

index.html内で初期化時に呼び出されるcreateUnityInstance関数は、[ビルド出力フォルダ]/Build/[名前].loader.jsというJSファイル内で定義されています。

StreamingAssetsパスは、この関数が生成するunityInstanceオブジェクトModule.streamingAssetsUrlフィールドに格納されています。

このフィールド値が最終的に希望のパスに書き換わるように.loader.jsファイル内をビルド後に書き換えれば良いです。

createUnityInstance関数内部では、

  • Module.streamingAssetsUrlを「StreamingAssets」という文字列で初期化
  • config.streamingAssetsUrlでパス指定されていればそれで上書き
  • Module.streamingAssetsUrlをURLとして正規化

という流れでStreamingAssetsのパスが決定される処理になっているようです。

streamingAssetsUrl: "StreamingAssets",

・・・(中略)・・・

for (var parameter in config)
  Module[parameter] = config[parameter];

Module.streamingAssetsUrl = new URL(Module.streamingAssetsUrl, document.URL).href;
メモ

生成される.loader.jsファイルは、UnityアプリケーションフォルダにあるUnityLoader.jsファイルを処理して最小化したものです。

[Unityフォルダ]/Hub/Editor/[Unityバージョン]/Editor/Data/PlaybackEngines/WebGLSupport/BuildTools/UnityLoader/UnityLoader.js

内部実装を確認したい場合は、上記を覗いてみると良いでしょう。

なお、.loader.jsのソースコード解析および実装にあたっては、以下記事を参考にさせていただきました。

書き換えスクリプトの実装

ビルド後の.loader.jsファイルのStreamingAssetsパスを書き換えるためのスクリプトを実装します。

本記事では、IPostprocessBuildWithReportインタフェースを通じてビルド終了時に.loader.jsファイル内の文字列置換を行うこととします。

参考:Unity – Scripting API: IPostprocessBuildWithReport

.loader.jsファイル内には、次のような文字列があるため、ここをカスタムURLに置き換えます。

m.streamingAssetsUrl=new URL(m.streamingAssetsUrl,document.URL).href;
注意

上記コードの「m」の部分は「Module」という名前の変数が最小化(Minify)されたものであるため、Unityバージョンや環境によっては違う名前になる可能性があります。これに注意して文字列置換を行っていきます。

実装例

以下、ビルド後に生成された.loader.jsファイル内のStreamingAssetsパスを置換するスクリプトの実装例です。

簡単のため、例ではハードコードでURLを指定していますが、実際にはScriptableObjectやシンボル定義など、外部に設定を持たせることをお勧めします。

StreamingAssetsPathPostProcessor.cs
#if UNITY_EDITOR

using System.IO;
using System.Text.RegularExpressions;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;

public class StreamingAssetsPathPostProcessor : IPostprocessBuildWithReport
{
    // 置換後のURL
    // ★ここを変更してください★
    // 実際には設定ファイルなどから取得することを推奨します
    private const string NewStreamingAssetsPath = "https://example2.nekojara.city/other/path2";

    // このスクリプトの実行優先度
    public int callbackOrder => 0;

    public void OnPostprocessBuild(BuildReport report)
    {
        // 出力フォルダから.loader.jsファイルを検索
        var buildDir = Path.Combine(report.summary.outputPath, "Build");

        try
        {
            // ビルドフォルダの存在チェック
            if (!Directory.Exists(buildDir))
                throw new UnityException("ビルドフォルダが見つかりません。");

            // .loader.jsファイルの存在チェック
            var loaderFiles = Directory.GetFiles(buildDir, "*.loader.js", SearchOption.AllDirectories);
            if (loaderFiles.Length <= 0)
                throw new UnityException(".loader.jsファイルが見つかりません。");

            // .loader.jsファイル読み込み
            var loaderJsPath = loaderFiles[0];
            var content = File.ReadAllText(loaderJsPath);

            // 文字列置換用の正規表現パターン
            const string pattern = @"(\w+)\.streamingAssetsUrl=new URL\(\1\.streamingAssetsUrl,document\.URL\)\.href;";

            // 置換後の文字列を定義
            var replacement = $"$1.streamingAssetsUrl=new URL(\"{NewStreamingAssetsPath}\",document.URL).href;";

            // 文字列を置換
            var modifiedContent = Regex.Replace(content, pattern, replacement);
            
            // 置換後の内容をファイルに書き込み
            File.WriteAllText(loaderJsPath, modifiedContent);
            
            Debug.Log("StreamingAssetsPathをカスタムパスに変更しました。");
        }
        catch (UnityException e)
        {
            Debug.LogError(e);
        }
    }
}
#endif

上記をStreamingAssetsPathPostProcessor.csという名前でUnityプロジェクトに保存しておきます。

後はこのままビルドすれば、ビルド終了時に置換処理が走ります。

実行結果

1つ目の例同様にStreamingAssetsパスが変わっていることが確認できました。

また、.loader.jsファイル内の該当箇所も独自パスに変化しています。

スクリプトの説明

ビルド終了後のタイミングは、IPostprocessBuildWithReportインタフェースを通じて取得できます。したがって、クラスにIPostprocessBuildWithReportを実装すれば良いです。

public class StreamingAssetsPathPostProcessor : IPostprocessBuildWithReport

参考:Unity – Scripting API: IPostprocessBuildWithReport

例では、以下constフィールドに変更後のStreamingAssetsパスを定義しています。

// 置換後のURL
// ★ここを変更してください★
// 実際には設定ファイルなどから取得することを推奨します
private const string NewStreamingAssetsPath = "https://example2.nekojara.city/other/path2";

.loader.jsファイルのフォルダは、[ビルド生成フォルダ]/Buildの直下に生成されるため、そのファイルを探します。

// 出力フォルダから.loader.jsファイルを検索
var buildDir = Path.Combine(report.summary.outputPath, "Build");

try
{
    // ビルドフォルダの存在チェック
    if (!Directory.Exists(buildDir))
        throw new UnityException("ビルドフォルダが見つかりません。");

    // .loader.jsファイルの存在チェック
    var loaderFiles = Directory.GetFiles(buildDir, "*.loader.js", SearchOption.AllDirectories);
    if (loaderFiles.Length <= 0)
        throw new UnityException(".loader.jsファイルが見つかりません。");

そして、テキストファイルとして内容をそのまま読み込みます。

// .loader.jsファイル読み込み
var loaderJsPath = loaderFiles[0];
var content = File.ReadAllText(loaderJsPath);

文字列置換は、変数名が変わることに注意して、正規表現を用いて行うようにしています。

// 文字列置換用の正規表現パターン
const string pattern = @"(\w+)\.streamingAssetsUrl=new URL\(\1\.streamingAssetsUrl,document\.URL\)\.href;";

// 置換後の文字列を定義
var replacement = $"$1.streamingAssetsUrl=new URL(\"{NewStreamingAssetsPath}\",document.URL).href;";

// 文字列を置換
var modifiedContent = Regex.Replace(content, pattern, replacement);

正規表現による置換を行った後は、ファイル内容を再び.loader.jsに適用(書き込み)して終了です。

// 置換後の内容をファイルに書き込み
File.WriteAllText(loaderJsPath, modifiedContent);

実行時にパスを書き換える(動的)

2つ目の方法では、unityInstance.Module.streamingAssetsUrlの内容をビルドの最後で書き換えていました。

これはjslibプラグインを通じて実行時に書き換えることも可能です。例えば、実行時にAPIサーバー等からパスを受け取って動的に振り分けたいケースなどで活躍できるでしょう。

3つ目の方法では、jslibプラグインを実装してunityInstance.Module.streamingAssetsUrlを動的に書き換える方法を紹介します。

注意

当方法もハック的な手法になります。また、動的にパスを書き換える性質上、書き換え前にApplication.streamingAssetsPathにアクセスしてしまわないように処理順に注意するといった対策が必要になるかもしれません。

.jslibファイルの実装

StreamingAssetsパスを書き換えるためのjslibプラグインを実装します。

以下、SetStreamingAssetsPathという名前の関数を定義した例です。

SetStreamingAssetsPath.jslib
mergeInto(LibraryManager.library, {
    SetStreamingAssetsPath: function (url) {
        const stringUrl = UTF8ToString(url);
        Module.streamingAssetsUrl = new URL(stringUrl, document.URL).href;
    },
});

上記をSetStreamingAssetsPath.jslibなどという名前で、Pulgins配下の階層に保存してください。

例では、/Assets/Plugins/WebGL/直下に置くものとしました。

参考:Unity – Manual: Call JavaScript functions from Unity C# scripts

参考:Unity – Manual: Import and configure plug-ins

C#スクリプトでの使用例

前述の.jslibで実装したSetStreamingAssetsPath関数をC#スクリプト側から呼び出して実行してパス書き換えを実現します。

以下、初期化時に書き換えてログ出力する例です。

SetPathAtRuntimeExample.cs
using System.Runtime.InteropServices;
using UnityEngine;

public class SetPathAtRuntimeExample : MonoBehaviour
{
#if !UNITY_EDITOR && UNITY_WEBGL
    // Webビルド時はjslibプラグインの関数を呼び出す
    [DllImport("__Internal")]
    private static extern void SetStreamingAssetsPath(string path);
#else
    // Editor時は何もしない
    private static void SetStreamingAssetsPath(string path)
    {
    }
#endif

    // 置換後のURL
    // ★ここを変更してください★
    // 実際には設定ファイルなどから取得することを推奨します
    private const string NewStreamingAssetsPath = "https://example2.nekojara.city/other/path3";

    private void Start()
    {
        // StreamingAssetsのパスを変更
        SetStreamingAssetsPath(NewStreamingAssetsPath);

        // StreamingAssetsのパスをログ出力
        Debug.Log($"StreamingAssetsPath: {Application.streamingAssetsPath}");
    }
}

上記をSetPathAtRuntimeExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチしてください。

前の例のPrintPathExample.csがアタッチされていれば、それを無効化するか削除しておきます。

実行結果

これまでの例と同様にパスが書き換えられていることが確認できました。

なお、上記書き換え後の結果は、.loader.jsのパスがデフォルトのまま(または別パスになっている)状態でも上書きされて適用されます。

動的に書き換えられる前のパスの例

スクリプトの説明

実際にStreamingAssetsパスを書き換える処理は以下部分です。

mergeInto(LibraryManager.library, {
    SetStreamingAssetsPath: function (url) {
        const stringUrl = UTF8ToString(url);
        Module.streamingAssetsUrl = new URL(stringUrl, document.URL).href;
    },
});

C#スクリプトから渡される文字列はそのままでは使えず、UTF8ToStringヘルパー関数でJavaScript用の文字列に変換する必要があります。

参考:Interaction with browser scripting – Unity マニュアル

jslibプラグインから実行される関数はunityInstanceスコープとなっているため、unityInstance.Module.streamingAssetsUrlフィールドにアクセスしたい場合はModule.streamingAssetsUrlとすれば良いです。

C#スクリプト側では、WebGLビルドとそうでない場合で分岐させています。

#if !UNITY_EDITOR && UNITY_WEBGL
    // Webビルド時はjslibプラグインの関数を呼び出す
    [DllImport("__Internal")]
    private static extern void SetStreamingAssetsPath(string path);
#else
    // Editor時は何もしない
    private static void SetStreamingAssetsPath(string path)
    {
    }
#endif

jslibの関数を使用するためには、[DllImport(“__Internal”)]属性を付加します。

参考:Unity – Manual: Call JavaScript functions from Unity C# scripts

別ドメインのパスに変更する際の注意点

本記事の例のように、異なるドメインのStreamingAssetsパスを設定する場合、変更先のサーバーでオリジン間リソース共有 (CORS)の設定が必要になります。

StreamingAssetsのアセットとしてダウンロードしたりする場合、GETメソッドを許可すれば良いです。

アセットをダウンロード(変更先サーバーのリソースにGETメソッドでアクセス)する時にレスポンスに次のようなヘッダが追加されていれば良いです。

Access-Control-Allow-Origin: https://example1.nekojara.city

設定方法はサーバーによって異なるため、各自サーバー環境の仕様やドキュメント等を参考に適切に設定してください。

DigitalOcean SpacesのCORS設定画面の場合

参考:How to Configure CORS on DigitalOcean Spaces|DigitalOcean Documentation

さいごに

公式では、ビルド出力されるindex.htmlファイルをカスタマイズすることでStreamingAssetsパスを変更する方法が提供されています。

やむを得ず別手段で変更したい場合、ハック的な方法になりますが.loader.jsファイルのStreamingAssetsパスに相当する処理を置換するか、jslibプラグインを実装して動的に書き換えることで実現可能です。

いずれの方法も、最終的にunityInstanceのModule.streamingAssetsUrlのURLを変更するという挙動になる点では共通しています。

関連記事

参考サイト

スポンサーリンク