Unityで構築したゲームをWebGLプラットフォーム向けにビルドし、ブラウザ上で実行した際、最も頻繁に遭遇する重大な不具合の一つが「Out of Memory (OOM / メモリ不足)」による突然のクラッシュです。ブラウザの開発者コンソール(F12キー)を確認すると、Cannot enlarge memory arrays や Out of memory といったWebGLエラーが出力され、ゲーム画面がフリーズ、またはクラッシュ画面(「このウェブページで問題が発生しました」など)に切り替わります。本記事では、ブラウザ特有 of メモリ制限の仕組みと、Unityでの適切なメモリ割り当て(ヒープサイズ調整)、およびアセット削減による根本的な解決方法をステップバイステップで解説します。
1. 根本原因:WebAssembly (Wasm) のメモリ空間とブラウザの制限
WebGLビルドにおけるメモリ管理は、PCやコンソール、モバイルアプリ向けビルドとは大きく異なります。
UnityのWebGLビルドは、C#コードをIL2CPP技術によってC++へ変換し、さらにそれをWebAssembly (Wasm)へとコンパイルします。Wasmはセキュリティ保護されたサンドボックス環境内で動作するため、システム全体の物理メモリを直接使うことができません。代わりに、ブラウザが割り当てた「仮想メモリ空間(Wasmヒープ)」と呼ばれる連続した領域をメモリとして使用します。
この環境下でメモリ不足クラッシュが発生する理由は、主に以下の3点です。
- 「WebGL Memory Size」設定の超過: Unityのビルド設定で指定した初期ヒープサイズ(デフォルトは256MBまたは512MB)を超えてアセット(テクスチャ、3Dモデル、オーディオなど)がメモリ上に展開された場合。
- ブラウザ全体のタブメモリ制限: ブラウザ(Chrome、Firefoxなど)やOS(特にiOS/Androidなどのモバイル環境)は、1つのタブが使用できるメモリ総量に厳しい制限を課しています。この上限に達すると、ブラウザ側でプロセスが強制終了(クラッシュ)します。
- メモリの断片化(フラグメンテーション): メモリの確保と解放を繰り返すことで、Wasmヒープ内に細かな空き領域が点在する状態になり、大きなアセット(例: 大容量のテクスチャやAssetBundle)をロードするための連続した空きメモリが確保できなくなる現象です。
2. 対策①:WebGL Memory Size(ヒープサイズ)の最適化
まずは、Unity側でWebGLビルドが使用するヒープメモリの初期サイズを適切に調整します。
- Unityエディタで Edit > Project Settings > Player を開きます。
- WebGL タブを選択し、Resolution and Presentation セクションを展開します。
- WebGL Memory Size の項目を確認します(※Unityのバージョンによっては「Memory Size」と表記されています)。
この設定値は、WebAssemblyが起動時に確保するメモリの最大量を決定します。
| メモリ設定値 | メリット | デメリット / 注意点 |
|---|---|---|
| 256MB ~ 512MB | 多くのモバイルブラウザや古いPCでも起動が安定する。 | 大きなシーンや高解像度アセットを読み込むと即座にOOMが発生する。 |
| 1024MB (1GB) | 一般的な3Dゲームに必要な十分なメモリ領域を確保できる。バランスが良い。 | 一部のスマートフォン(特にiPhoneのSafari)でメモリ制限に引っかかり起動直後にクラッシュすることがある。 |
| 2048MB (2GB) 以上 | 大量のアセットをメモリに保持できる。 | ブラウザ側が連続した2GBの仮想メモリ空間を確保できず、ゲームがロードすらできずにクラッシュするリスクが非常に高い。非推奨。 |
Unity 2020.1以降、デフォルトでヒープメモリの自動拡張がサポートされるようになりました。しかし、メモリが拡張される際、ブラウザ内での再割り当て処理により画面が数秒間フリーズする場合があります。極力、ゲームの「平常時の最大消費メモリ」よりわずかに大きいサイズ(例:実機計測で480MB消費するなら512MB)を初期サイズとして設定し、自動拡張はあくまで保険と位置づけるのが理想的です。
3. 対策②:アセットのストリーミングロードと適切なメモリ解放
WebGLビルド内に全てのアセット(特にBGMや重いテクスチャ)を詰め込むと、ゲームの起動時およびプレイ中に全アセットがWasmヒープに展開され、容易にOOMを引き起こします。アセットを必要な時にロードし、使い終わったら明示的に解放する実装を行います。
Addressables / AssetBundle を利用した動的ロード
重いキャラクターモデルや背景データは、本体ビルド(Wasm)に含めず、外部のアセットバンドル(またはAddressableアセットシステム)から通信経由で読み込む設計にします。これにより、初期ヒープサイズを大幅に小さく抑えることができます。
以下は、Addressablesを使用してアセットをロードし、不要になった段階でメモリから完全に解放するC#の実装例です。
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class AssetMemoryManager : MonoBehaviour
{
[SerializeField] private string assetAddress = "MyLargeHeavyCharacter";
private GameObject spawnedObject;
private AsyncOperationHandle<GameObject> loadHandle;
// ボタンやトリガーから呼び出す
public void LoadAsset()
{
// アセットを非同期でロード
loadHandle = Addressables.LoadAssetAsync<GameObject>(assetAddress);
loadHandle.Completed += OnLoadCompleted;
}
private void OnLoadCompleted(AsyncOperationHandle<GameObject> handle)
{
if (handle.Status == AsyncOperationStatus.Succeeded)
{
spawnedObject = Instantiate(handle.Result);
}
else
{
Debug.LogError($"Failed to load asset: {assetAddress}");
}
}
// 使い終わったら呼び出し、メモリを完全に解放する
public void ReleaseAsset()
{
if (spawnedObject != null)
{
Destroy(spawnedObject);
spawnedObject = null;
}
// Addressablesのハンドルを解放してメモリを返却
if (loadHandle.IsValid())
{
Addressables.Release(loadHandle);
Debug.Log("Asset memory released from Wasm Heap.");
}
// アンロードされたメモリをGCの対象にするためにリソースクリーンアップを呼ぶ
Resources.UnloadUnusedAssets();
}
}
4. 対策③:テクスチャフォーマットとサウンドの設定最適化
WebGLは、PCやモバイルのようにハードウェア固有の圧縮テクスチャフォーマット(ASTCやETC2、BC7など)が直接使用できないケースが多く、無圧縮(RGBA 32bit)の状態でVRAMやメモリに展開されてしまうことがあります。これがメモリ不足の引き金になります。
- Crunch圧縮(Crunch Compression)の適用: WebGLビルドで使用するUI画像や3Dモデルのテクスチャは、テクスチャインスペクターで Use Crunch Compression を有効にし、品質を調整します。ビルドサイズだけでなく、メモリ展開時のサイズを最適化できます。
- オーディオの「Compressed In Memory」と「Streaming」: 長いBGM(AudioClip)のロードタイプを「Decompress On Load(ロード時に解凍)」に設定すると、解凍された大量の生オーディオデータがWasmヒープを圧迫します。BGMは Streaming(ストリーミング) または Compressed In Memory(メモリ内で圧縮) に変更してください。
5. 対策④:WebAssemblyのバイナリサイズ削減(Managed Stripping)
ゲーム実行中のアセットだけでなく、Unityエンジン本体のコード(dll)や使用していないスクリプトそのものもWasmバイナリを肥大化させ、初期ロード時のOOMの原因になります。以下のコードストリップ設定でコード自体を軽量化します。
- Edit > Project Settings > Player を開きます。
- Other Settings セクションを展開します。
- Optimization の下にある Managed Stripping Level をデフォルトの「Disabled」または「Low」から、Medium または High に変更します。
これによって、ゲーム内で実際には使用されていないUnityエンジンの物理演算、アニメーション、オーディオなどの機能やコンポーネントがビルド時に自動的に除外(ストリップ)され、Wasmファイルのサイズと起動時のメモリ使用量が飛躍的に縮小します。
Managed Strippingを強めると、スクリプトからリフレクション(Reflection)経由で動的に参照しているクラスや、Prefabからのみ参照しているコンポーネントが「未使用」と判定されてビルドから削除されることがあります。これを防ぐためには、プロジェクトの
Assets/ ディレクトリ直下に link.xml を作成し、削除を防止するアセンブリやクラスを明示的に記述(Preserve)する必要があります。