前回、Unity の Test Framework パッケージによるテストを自作スクリプト(Tilemap関連)の関数に対して行いました。
今回は、プレハブからインスタンス化した Tilemap コンポーネントと、テスト対象の関数で処理を行ったタイルマップの状態が等しいかどうかテストするために、 Equals 関数を利用する手順を紹介します。
と、思っていたのですが、 Equals 関数は利用できないようなので、 Equals に似た関数を自作し、その関数が正しく動くかをプレハブを用いたテストで検証した経過をテストコードとともに紹介します。
※ Unity は 2021.3.14f1、Test Framework パッケージは Version 1.1.33、Visual Studio は Community 版 Version 17.2.5、 OS は Windows 10 です。
Tilemap コンポーネントの Equals
== オペレータは実体が同じコンポーネント・オブジェクトであることを判別する比較演算子です。
int などの値型の場合は == で値の比較を行いますが、コンポーネントなどは参照を比較されます。
コンポーネントの参照は異なるけど、値が同じかどうかは Equals 関数を用います。
しかし、中身が同じタイルマップ同士 t1, t2 で試したところ、 false が返り、t1.Equals(t1) の場合は true が返り、期待した戻り値ではありませんでした。
また Visual Studio のインテリセンスのヒントにも Object.Equals の呼び出しと書いてあります。
これにより Tilemap コンポーネントは Equals 関数をオーバーライドしていないと推測できます。
そのため、2 つのタイルマップの見た目が同じになるかどうかを、 Tilemap コンポーネントのデータを比較して判定する、 Equals の一部の機能をもった関数を作ります。
2つのタイルマップの比較関数の作成
2 つのタイルマップのタイルの配置、マスごとのタイルの表示設定が等しいかを比較する関数を作りました。
表示設定は、タイルマップに配置されたマスごとのタイルの色や移動・回転・拡大などの設定です。
/// <summary>
/// Tilemap の TileBase の参照が等しいかを比較します。
/// Tilemap ではタイルパレットの同じタイルが配置された場合、実体が同じ TileBase 派生オブジェクトを配置します。
/// そのためタイルマップに配置されたタイルごとの変形や色の変化は Tilemap で持っています。
/// </summary>
/// <param name="this">比較元の TileBase です。</param>
/// <param name="that">比較対象の TileBase です。</param>
/// <returns>二つの TileBase の参照が等しければ true を返します。</returns>
public static bool SCIsSameReference(this TileBase @this, TileBase that)
{
return @this == that; // 参照の比較
}
/// <summary>
/// 二つの Tilemap の同じ座標に同じタイルが配置されていること、
/// その二つのタイルの表示設定(AnimationFrameCount TileFlags, TransfomrMatrix(Move, Rotate, Scale)) がタイルごとに一致していることを比較します。
/// Tilemap 全体の設定については比較しません。
/// </summary>
/// <param name="this">比較元の Tilemap です。</param>
/// <param name="that">比較対象の Tilemap です。</param>
/// <param name="bounds">比較する範囲です。二つのタイルマップで交差する部分に制限されます。 null の場合は二つのタイルマップで交差する範囲です。</param>
/// <returns>二つのタイルマップのタイル群の配置・表示設定が等価ならば true 、他は false を返します。</returns>
public static bool SCIsSameTileBlockLayout(this Tilemap @this, Tilemap that, BoundsInt? bounds = null)
{
BoundsInt cellBounds;
if (bounds == null)
{
BoundsInt thisCellBounds = @this.cellBounds;
BoundsInt thatCellBounds = that.cellBounds;
cellBounds = @this.cellBounds;
cellBounds.ClampToBounds(that.cellBounds);
}
else
{
cellBounds = (BoundsInt)bounds; // null 許容型から値型へキャストします。
cellBounds.ClampToBounds(@this.cellBounds);
cellBounds.ClampToBounds(that.cellBounds);
}
// ループ内で戻り値を保持する変数の宣言しておきます。
TileBase thisTile, thatTile = null;
int index = 0;
Vector3Int cellPosition = new Vector3Int();
TileFlags thisTileFlags, thatTileFlags;
int thisFrameCount, thatFrameCount;
Matrix4x4 thisTransformMatrix;
Matrix4x4 thatTransformMatrix;
// それぞれのタイルマップから指定範囲のブロックの範囲のタイルを配列で取得します。
TileBase[] thisTiles = @this.GetTilesBlock(cellBounds);
TileBase[] thatTiles = that.GetTilesBlock(cellBounds);
// 同じ座標に配置されているタイルをそれぞれのタイルマップから取得し、比較します。
for (int y = 0; y < cellBounds.size.y; ++y)
{
for (int x = 0; x < cellBounds.size.x; ++x)
{
index = x + cellBounds.size.x * y;
cellPosition.Set(x, y, 0);
thisTile = thisTiles[index];
thatTile = thatTiles[index];
if (thisTile.SCIsSameReference(thatTile) == false)
{
return false;
}
// https://docs.unity3d.com/ScriptReference/Tilemaps.Tilemap.html
// GetColor, GetSprite は同一 Tile で共通の設定なので比較しません。
// GetAnimationFrame は現在の表示フレーム番号を返すものなので比較しません。
thisFrameCount = @this.GetAnimationFrameCount(cellPosition);
thatFrameCount = that.GetAnimationFrameCount(cellPosition);
if (thisFrameCount != thatFrameCount)
{
return false;
}
// GetAnimationTime は現在時間軸でどの位置のアニメーションが行われているかを得る関数なので比較しません。
// GetCellCenterLocal, GetCellCenterWorld は Tilemap.tileAnchor で確認するので比較しません。
thisTileFlags = @this.GetTileFlags(cellPosition);
thatTileFlags = that.GetTileFlags(cellPosition);
if (thisTileFlags != thatTileFlags) // 値型なので == で値が等しいか確認します。
{
return false;
}
thisTransformMatrix = @this.GetTransformMatrix(cellPosition);
thatTransformMatrix = that.GetTransformMatrix(cellPosition);
if (thisTransformMatrix.Equals(thatTransformMatrix) == false)
{
return false;
}
}
}
return true;
}
長いですが、やっていることは単純で、 Tilemap の各マスに配置しているタイルが同じであるかどうか null かどうかも含めて比較しています。
Tile Palette ウィンドウで同じタイルを選択し、タイルマップに配置した場合、タイルマップでは同一のタイルのオブジェクトが各マスに配置されるようなので、タイルが同じかどうかは参照の比較で済ませています。
また同じタイルであっても同じ座標に配置されたタイルの表示設定(移動・回転・拡大など)が一致しているかを確認しています。
チェックする全ての項目が同じならば true 、1 つでも異なれば比較の処理が中断され false が返ります。
作成したタイルマップ比較関数のテスト
次のように Grid コンポーネントに Tilemap1, Tilemap1Dup, Tilemap2 がぶら下がっているグリッドとセットになったタイルマップ群をプレハブ化して、それをテスト関数の処理でインスタンス化して比較してテスト対象の先ほど作った関数が正しく判定するかテストしました。
プレハブは次のような構成で、タイルマップ(3×3)の配置も下図の通りです。
マスごとのタイルの表示設定は、Tile Pallete ウィンドウの左側の選択ツールを使い、マスを選択すると Inspector ウィンドウの Offset, Rotation, Scale のフィールドで変更できます。
互いに異なる配置・表示設定の Tilemap1, Tilemap2 と、 Tilemap1 の複製である Tilemap1Dup の 3 個のタイルマップをテストに用います。
テスト関数のコードは以下です。
/// <summary>
/// SCTilemapExtensions の SCIsSameTileBlockLayout 関数のテストです。
/// 二つのタイルマップのタイルの配置や表示設定が同じならば true を返す、異なれば false を返すテストを行います。
/// 同じ Grid の下に用意したタイルマップ 3 つを比較対象にします。
/// Tilemap1 は同じタイルを 3x3 マスに 4 つ配置し、Move, Rotate, Scale をそれぞれ変えています。
/// Tilmeap1Dup は Tilemap 1 の複製です。そのため Tilemap1 と Tilemap1Dup の比較では true を期待します。
/// Tilemap2 は同じ 3x3 マスですが、タイルの配置位置や、表示設定が少し異なります。
/// Tilemap1 と Tilemap2 の比較では false を期待します。
/// </summary>
/// <param name="gridPrefabPath"></param>
/// <param name="tilemap1Name"></param>
/// <param name="tilemap2Name"></param>
/// <param name="constraint"></param>
[TestCase("Test/Tilemap/TestGrid2", "Tilemap1", "Tilemap1Dup", true)]
[TestCase("Test/Tilemap/TestGrid2", "Tilemap1", "Tilemap2", false)]
public void SCTilemapExtensions_SCIsSameTileBlockLayout_1(string gridPrefabPath, string tilemap1Name, string tilemap2Name, bool constraint)
{
GameObject gridPrefab = Resources.Load(gridPrefabPath) as GameObject;
GameObject gridGo = GameObject.Instantiate(gridPrefab);
GameObject tilemap1Go = gridGo.transform.Find(tilemap1Name).gameObject;
GameObject tilemap2Go = gridGo.transform.Find(tilemap2Name).gameObject;
Tilemap tilemap1 = tilemap1Go.GetComponent<Tilemap>();
Tilemap tilemap2 = tilemap2Go.GetComponent<Tilemap>();
bool actual = tilemap1.SCIsSameTileBlockLayout(tilemap2);
Assert.That(actual, Is.EqualTo(constraint));
}
このテスト関数は、 TestCase の引数で、比較する対象のタイルマップ 2 つの名前と、正解の値(タイルの配置・表示設定が同じかどうか)を渡されます。
プレハブをインスタンス化し、二つのタイルマップを取得し、それをテスト対象である SCIsSameTileBlockLayout 関数で比較し、関数の戻り値の結果と引数で与えられた正解が一致しているかをテストします。
メニュー[Window]→[General]→[Test Runner] で Test Runner ウィンドウを表示し、作成したテスト関数のテストを行うと、二つのテストケースで合格を意味する緑のチェックマークがつきました。
テストを行うと、実装した処理をある程度安心して使えるので、精神的な意味でもやって良かったです。
まとめ
今回は、Unity の Tilemap のタイルの配置や、それぞれのタイルの表示設定が等しいかどうかを判定する関数を作成し、タイルマップを含むプレハブを用いたその関数へのテストを紹介しました。
コメント