Unity2021.2.7
はじめに
マルチプラットフォームでアプリを開発する際には、ファイルパスがよく問題を起こします。
その大きな要因として、WindowsとMacでディレクトリ区切り文字が異なる点が挙げられます。
UnityではWindowsとMacで同じプロジェクトを共有することがよく行われます。
そのため自前のエディタ用スクリプトでディレクトリ区切り文字を起因とした不具合が発生しがちです。
本記事ではこのような不具合を防ぐために、改めてUnityにおける正しいパスの使い方を考えます。
なお本文中に書いた通りUnityの仕様も.NETの仕様もここ数年で微妙に変わっていそうなので、
2021年現在における情報としてとらえていただければと思います。
プラットフォームとディレクトリ区切り文字
さてディレクトリを区切るための文字はプラットフォームにより異なります。
Macの場合には、ディレクトリ区切り文字はスラッシュ(/)で表されます。
これに対してWindowsの場合、ディレクトリ区切り文字はバックスラッシュ(\)で表されます。
ただしWindowsはスラッシュにも対応しており、スラッシュ区切りのパスでも読み込めます。
プラットフォーム | スラッシュ | バックスラッシュ |
---|---|---|
Windows | 〇 | 〇 |
Mac | 〇 | × |
プラットフォーム依存問題
さてこのような事情から、ディレクトリ区切り文字はプラットフォーム依存の問題を起こしがちです。
例えば以下のテストはMacで成功しますがWindowsで失敗します。
[Test] public void Test() { // プラットフォームのデフォルトの区切り文字で区切られたフルパス var assetsFolderFullPath = Path.GetFullPath("Assets"); var contains = assetsFolderFullPath.Contains("/"); Assert.That(contains); // Mac: 成功 Windows: 失敗 }
これはWindowsではデフォルトの区切り文字がバックスラッシュであるためです。
この例に限らず、等価判定やIndexOfなど他のAPIでも同様の問題が起こります。
またこのように取得したパスを保存して他のプラットフォームで扱うときにも問題が起こります。
UnityのAPIとパス
問題解決のための第一歩は正しい理解ということで、まずUnityのAPIの挙動を正しく理解しています。
最初にAssetDatabaseとResourcesのマニュアルを見てみます。
どちらもディレクトリ区切り文字には常に(Windowsであっても)スラッシュを使うよう書かれています。
Resourcesに関してはバックスラッシュでは動かないと明確に書かれています。
この挙動を確かめるためにテストを書いてみます。
[Test] public void AssetDatabase_Slash() { var prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Resources/Tests/Prefabs/FooPrefab.prefab"); Assert.That(prefab.name, Is.EqualTo("FooPrefab")); } [Test] public void AssetDatabase_Backslash() { var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(@"Assets\Resources\Tests\Prefabs\FooPrefab.prefab"); Assert.That(prefab.name, Is.EqualTo("FooPrefab")); } [Test] public void AssetDatabase_Mixed() { var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(@"Assets/Resources/Tests/Prefabs\FooPrefab.prefab"); Assert.That(prefab.name, Is.EqualTo("FooPrefab")); } [Test] public void Resources_Slash() { var prefab = Resources.Load<GameObject>("Tests/Prefabs/FooPrefab"); Assert.That(prefab.name, Is.EqualTo("FooPrefab")); } [Test] public void Resources_Backslash() { var prefab = Resources.Load<GameObject>(@"Tests\Prefabs\FooPrefab"); Assert.That(prefab.name, Is.EqualTo("FooPrefab")); } [Test] public void Resources_Mixed() { var prefab = Resources.Load<GameObject>(@"Tests\Prefabs/FooPrefab"); Assert.That(prefab.name, Is.EqualTo("FooPrefab")); }
このテストを走らせてみるとわかりますが、実際にはWindowsでもMacでもすべてのケースが成功します。
Resourcesに関してはAndroidとiOSの実機でも成功しました。
つまり、マニュアルではスラッシュを使用するように書いてあるもののバックスラッシュでも読み込めるということになります。
昔のUnityはバックスラッシュは使えなかったような気がしますが、現時点での挙動はこの通りです。
クラス | プラットフォーム | スラッシュ | バックスラッシュ |
---|---|---|---|
AssetDatabase | Win | 〇 | △(読めるけど公式には×) |
AssetDatabase | Mac | 〇 | △(読めるけど公式には×) |
Resources | Win | 〇 | △(読めるけど公式には×) |
Resources | Mac | 〇 | △(読めるけど公式には×) |
Resources | iOS | 〇 | △(読めるけど公式には×) |
Resources | Android | 〇 | △(読めるけど公式には×) |
.NETとパス
次に.NETにおけるパスの取り扱いについて考えます。
.NETではPathクラスでパスを取り扱うことができます。
このクラスはパスをそれぞれのプラットフォームに適した形で取り扱います。
したがって前述した例(以下に再掲)のように、得られる結果はプラットフォームにより異なります。
[Test] public void Test() { // プラットフォームのデフォルトの区切り文字で区切られたフルパス var assetsFolderFullPath = Path.GetFullPath("Assets"); var contains = assetsFolderFullPath.Contains("/"); Assert.That(contains); // Mac: 成功 Windows: 失敗 }
PathクラスのAPIの挙動は以下の記事のようにバージョンにより微妙に異なります。
いずれにせよPathクラスはプラットフォームに適した形でパスを取り扱うクラスであるといえます。
さてプラットフォームに適した形でパスを取り扱いたい場合はこれを使うとして、
どのプラットフォームでも同じ結果を得たい場合にはどうしたらいいのでしょうか。
以下の.NETのドキュメントにこのあたりの記述があります。
要点をまとめると以下の通りです。
- マルチプラットフォームで開発する場合、ディレクトリ区切り文字をハードコーディングするならスラッシュを使うこと
- AltDirectorySeparatorCharはWindowsでもUnixでも同じ文字(スラッシュ)を返すのでこれを使って文字列連結してもOK
Path.Combine()
とか使わずに文字列を連結するのが良いようです。
Unityでパスを正しく扱う
さてここまでの情報をまとめると以下のようになります。
API種別 | プラットフォーム | スラッシュ | バックスラッシュ |
---|---|---|---|
Unity系 | Win | 〇 | △(読めるけど公式には×) |
Unity系 | Mac | 〇 | △(読めるけど公式には×) |
System系 | Win | 〇 | 〇 |
System系 | Mac | 〇 | × |
Unityでパスを取り扱う際には以上のことに留意して正しく取り扱う必要があります。
その上で具体的にどうするかという話はそれぞれで良いと思います。いろんなやり方があります。
個人的には、以下のようなルールにするのがシンプルでわかりやすいかなと思います。
- パスの区切り文字には常にスラッシュを使う
- ハードコードあるいはPath.AltDirectorySeparatorChar
Path.Combine()
とかプラットフォーム依存なAPIはできるだけ使わない- 全部スラッシュとして取り扱うユーティリティを用意するとか
- Pathクラスとかを使う場合には必ずその結果のバックスラッシュをスラッシュに置換する