Unity エディタ上で Test Runner を使ったテストの際に、一時フォルダを作成する処理を作ったので、今回はその実装や、テスト、使用例について紹介します。
※ Unity は 2021.3.14f1、Test Framework パッケージは Version 1.1.33、Visual Studio は Community 版 Version 17.2.5、 OS は Windows 10 です。
ありそうでなかった機能?
.NET で一時フォルダに関連する関数には以下のようなものがあります。
- Path.GetTempPath メソッド (System.IO) | Microsoft Learn
- Path.GetTempFileName メソッド (System.IO) | Microsoft Learn
しかし、 GetTempPath は環境変数から存在する Temp フォルダを探して返すもので、 GetTempFileName メソッドは決められたフォルダ内に一時ファイルを作成して返すものです。
筆者は Unity プロジェクトの Assets フォルダ以下の任意のフォルダ内に一時フォルダを作りたいので、上位フォルダを指定できない先ほどの関数は使えませんでした。
UnityEditor 名前空間にある AssetDatabase クラスには、フォルダを作成・削除したり、有効なフォルダかを判定する関数はありました。
- AssetDatabase-CreateFolder – Unity スクリプトリファレンス
- AssetDatabase-DeleteAsset – Unity スクリプトリファレンス
- AssetDatabase-IsValidFolder – Unity スクリプトリファレンス
しかし、一時フォルダを作ったり、指定したフォルダ内で重複しないフォルダ名を得るような関数は探せませんでした。
重複しないフォルダ名を返す関数の自作
フォルダの作成自体は AssetDatabase.CreateFolder で行えるので、Assets 以下の指定したフォルダの中で、重複しないフォルダ名を返す関数を作成しました。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif // #if UNITY_EDITOR
namespace SakuraCrowd.Tools
{
public static class SCPath
{
#if UNITY_EDITOR
/// <summary>
/// Assets 内で指定されたフォルダ内で重複しないフォルダ名を返します。具体的には数値を名前に付加します。重複する場合は数値を増やします。
/// </summary>
/// <param name="pathFolder">このフォルダ内で重複しないフォルダ名を返します。</param>
/// <param name="prefix">フォルダ名の最初に付加する文字列です。</param>
/// <param name="suffix">フォルダ名の最後に付加する文字列です。</param>
/// <returns></returns>
public static string SCGetUniqueFolderAssetName(string pathFolder, string prefix = "_tmp_", string suffix = "")
{
int number = 0;
string uniqueFolderAssetName;
while(true)
{
uniqueFolderAssetName = prefix + number.ToString() + suffix;
string pathCheck = pathFolder + "/" + uniqueFolderAssetName;
if (!AssetDatabase.IsValidFolder(pathCheck))
{
break; // 存在しないフォルダ名であることを確認したので、その数値を含むフォルダ名を返します。
}
++number; // 存在する場合は 1 増やした数値を付加した名前で確認します。
}
return uniqueFolderAssetName;
}
#endif // #if UNITY_EDITOR
}
}
処理自体は単純で、番号を 0 から 1 ずつ増やしたフォルダ名候補を作り、そのようなフォルダが指定したフォルダ内に存在するか確認し、存在しなかったらそのフォルダ名を返します。
UnityEditor の制約によりエディタでの編集・テスト時にしか動作しないので、数千個の膨大な一時フォルダを作成して速度が低下する場合などの対策は考えていません。
アセットパスという名称
アセットパスという名称は公式で確認したわけではなく、Windows OS で用いるパスと区別するために、筆者が便宜的に使っている名称です。
アセットパスは、Unity プロジェクト直下の Assets フォルダをルートにしたパスで、 区切り文字はバックスラッシュ(‘\\’) ではなく、スラッシュ (‘/’) です。
関数のテスト
テストは、次のコードで行いました。
/// <summary>
/// SCGetUniqueFolderAssetName 関数のテストです。
/// 関数の戻り値が、このファイルのあるフォルダに存在しないフォルダ名かを確認します。
/// </summary>
[Test]
public void SCPath_SCGetUniqueFolderAssetName_1()
{
string pathFile = SCPath.SCGetFilePath(); // このファイルへのフルパスを取得します。
string pathFolder = SCPath.SCGetFolderPath(pathFile); // このファイルがあるフォルダへのフルパスを取得します。
string assetPathFolder = SCPath.SCConvertAssetPath(pathFolder); // フォルダへのフルパスをアセットパスに変換します。
// このファイルのあるフォルダを指定して重複しないフォルダ名を得ます。
string uniqueFolderAssetName = SCPath.SCGetUniqueFolderAssetName(assetPathFolder);
// 得たフォルダ名が、このファイルのあるフォルダに存在しないか確認します。
bool isValid = AssetDatabase.IsValidFolder(assetPathFolder + '/' + uniqueFolderAssetName);
Assert.That(isValid, Is.False); // フォルダ名が存在しない(無効な)場合はテスト合格です。
}
テストコードは、テスト対象の関数が返したフォルダパスが存在しないことを確認しています。
ファイルパスをコードに埋め込むときに@(逐語的リテラル)
テストコードにファイルパスを指定する際は、逐語的リテラル @ を用いて、@”C:\path\to\file.txt” のように文字列の前に @ をつけるとエスケープシーケンスを使わないので \ を \\ 、例えば “C:\\path\\to\file.txt” にする必要がなく、パスをコピー&ペーストで指定でき便利です。
関数の使用例
関数は、指定したパスの中で、存在しないフォルダを連続して作成する関数のテストで使いました。
/// <summary>
/// SCCreateFolders テスト関数。存在しないサブフォルダを作成させて、存在が有効かテストします。
/// </summary>
/// <param name="pathFromCurrentFolder">.. などの相対パスを指定して、カレントフォルダより上位を指定しないでください。</param>
/// <param name="countGenerated"></param>
[TestCase("", 0)]
[TestCase("Hoge/Piyo", 2)]
public void SCPath_SCCreateFolders_1(string pathFromCurrentFolder, int countGenerated)
{
string pathFolderOrFile = SCPath.SCGetFilePath(); // このファイルへのパスを取得します。 @"C:\Unity\Proj\Assets\My\ThisFile.cs"
string pathUpperFolder = SCPath.SCGetFolderPath(pathFolderOrFile); // このファイルの置かれているフォルダへのパスを取得します。 @"C:\Unity\Proj\Assets\My"
string assetPathUpperFolder = SCPath.SCConvertAssetPath(pathUpperFolder); // このフォルダへのパスをアセットパスに変換します。 @"Assets/My"
string tempFolderName = SCPath.SCGetUniqueFolderAssetName(assetPathUpperFolder); // このフォルダ内に存在しないフォルダ名を取得します。 "tmp_0"
string assetPathTempFolder = assetPathUpperFolder + '/' + tempFolderName; // 一時的にテストで持ちいるサブフォルダへのアセットパスです。 @"Assets/My/tmp_0"
string assetPathNewFolders = assetPathUpperFolder + '/' + tempFolderName + '/' + pathFromCurrentFolder; // テストで作成するサブフォルダ以下のフォルダ群のアセットパスです。
// テスト専用のサブフォルダを作成
string tempFolderGuid = AssetDatabase.CreateFolder(assetPathUpperFolder, tempFolderName);
if (tempFolderGuid == string.Empty)
{
Debug.LogError("テンポラリフォルダの作成に失敗しました。" + assetPathNewFolders);
}
// テストする関数で、フォルダ群を作成し、新しく作成したフォルダ数を得ます。
int actual = SCPath.SCCreateFolders(assetPathNewFolders);
// TestCase で渡された解答の数と同じ数のフォルダが作成されたことを確認します。
Assert.That(actual, Is.EqualTo(countGenerated));
// 実際に指定されたサブフォルダ群が存在するか確認します。
bool isValidFolder = AssetDatabase.IsValidFolder(assetPathNewFolders);
Assert.That(isValidFolder, Is.True);
// テスト専用のサブフォルダを削除
AssetDatabase.DeleteAsset(assetPathTempFolder);
}
フォルダを作成するテストなので、その前にフォルダが作成されていない状況を作る必要があります。
また他の場所のアセットを誤って削除するリスクを抑えるため、テストフォルダの下位に一時フォルダを作るために用いました。
作成した一時フォルダはテストの最後で削除しています。
まとめ
Unity で、重複しないユニークなサブフォルダ名を得る関数を自作・テストし、 AssetDatabase.CreateFolder 関数と組み合わせることで、エディタ編集・テストの際に一時的に用いるフォルダを作成するコードの例を紹介しました。
※使用例のコードの最後の DeleteAsset により一時フォルダを削除する処理を無効にして TestCase(“”, 0) と TestCase(“Hoge/Piyo”, 2) を 2 回行った直後の Project ウィンドウのテスト用フォルダです。
参照サイト Thank You!
- Unity のリアルタイム開発プラットフォーム | 3D/2D、VR/AR のエンジン
- Path.GetTempPath メソッド (System.IO) | Microsoft Learn
- Path.GetTempFileName メソッド (System.IO) | Microsoft Learn
- AssetDatabase-CreateFolder – Unity スクリプトリファレンス
- AssetDatabase-DeleteAsset – Unity スクリプトリファレンス
- AssetDatabase-IsValidFolder – Unity スクリプトリファレンス
- ファイルパスを扱う際(?)に付ける@マークの意味は?
- 逐語的テキストと文字列 – @ | Microsoft Learn
記事一覧 → Compota-Soft-Press
コメント