BoxCollider2Dに必要な中心座標、サイズをを取得したいが、直接迷路の画像から取得するのは難しい。なので、まずデータ取得用の画像(ColliderMap)を作成し、そこから必要なデータを取得するようにします。この取得したデータをもとに迷路の画像にBoxCollider2Dをアタッチします。
ColliderMapの作成
ここではClipStudioを使用していますが、下記の機能が使えるものなら何でもよいです。
- ベクターレイヤー。
- グリッドの表示、グリッドにスナップ。
迷路の画像のレイヤーの上からBoxCollider2Dの半分の大きさの短形を描いていきます。ベクターレイヤーで矩形をブラシサイズ1pxで描画し、ラスターレイヤーで適当な色で塗りつぶしていきます。このとき、適当なサイズでグリッドを表示し、グリッドにスナップするようにしておくと楽です。
BoxCollider2Dそのものの短形にしないは、BoxCollider2Dが隣接しているとうまく座標を取得できないからです。
作成した画像はRead/Write Enableを有効にしておくこと。そうしないとスクリプトから画像を読み込めません。
ColliderMapから矩形を取得
以下の手順で矩形を取得していきます。
- ColliderMapをTexture2Dとして読み込ます。
- 原点(左下)から水平方向に、連続してある不透明な(α値が0でない)点を取得します。
- 垂直方向に、2で取得した直線と同じ長さの不透明な直線があるか精査し、あれば取得します。
- 2, 3を精査済みの点を飛ばしながら繰り返します。
MazeUtilityクラスでColliderMapの読み込み、精査、矩形の取得をします。取得した矩形をRectangleクラスで保存します。
public class MazeUtility : MonoBehaviour
{
private readonly List<Vector2> visited = new List<Vector2>();
private Color[] pix;
private int width;
private int height;
public List<Rectangle> GetRectangles(Texture2D sourceTex)
{
visited.Clear();
width = sourceTex.width;
height = sourceTex.height;
pix = sourceTex.GetPixels(0, 0, width, height);
var rects = new List<Rectangle>();
for (int y = 0; y < height; y += Rectangle.Unit.y)
{
for (int x = 0; x < width; x += Rectangle.Unit.x)
{
var p = new Vector2(x, y);
if (visited.Contains(p)) continue;
int i = y * width + x;
if (pix[i].a > 0)
{
var rec = GetRectangle(p);
rects.Add(rec);
}
}
}
return rects;
}
private Rectangle GetRectangle(Vector2 p)
{
var rec = new Rectangle();
var points = GetLinePoints(p);
visited.AddRange(points);
rec.BottomLeft = points[0];
rec.Width = points.Count * Rectangle.Unit.x; ;
int i;
do
{
p.y += Rectangle.Unit.y;
if (p.y >= height) break;
points = GetLinePoints(p);
if (rec.Width != points.Count * Rectangle.Unit.x)
{
break;
}
else
{
visited.AddRange(points);
}
i = (int)p.y * width + (int)p.x;
} while (pix[i].a > 0);
rec.Height = p.y - rec.BottomLeft.y;
return rec;
}
private List<Vector2> GetLinePoints(Vector2 p)
{
var points = new List<Vector2>();
int i = (int)p.y * width + (int)p.x;
while (p.x < width && pix[i].a > 0)
{
points.Add(p);
p.x += Rectangle.Unit.x;
i = (int)p.y * width + (int)p.x;
}
return points;
}
}
public class Rectangle
{
public static readonly Vector2Int Unit = new Vector2Int(2, 2);
public Vector2 BottomLeft { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public float Width2 { get => Width * 2f; }
public float Height2 { get => Height * 2f; }
public Vector2 Center
{
get
{
var cx = BottomLeft.x + Width / 2f;
var cy = BottomLeft.y + Height / 2f;
return new Vector2(cx, cy);
}
}
public Vector2 Center2
{
get
{
var cx = BottomLeft.x + Width;
var cy = BottomLeft.y + Height;
return new Vector2(cx, cy);
}
}
}
BoxCollider2Dをアタッチ
MazeオブジェクトにMazeUtilityをアタッチし、ColliderMapをセットします。Mazeオブジェクトが読み込まれたときBoxCollider2Dをアタッチするようにします。
MazeUtilityで得た矩形は必要な矩形の半分の大きさなので2倍した値を使います。また、迷路の画像はPixels Per Unitを8pxにしているので8で割ります。
public class Maze : MonoBehaviour
{
[SerializeField] private Texture2D boxColliderMap;
private MazeUtility mazeUtility;
private void Start()
{
var boxes = mazeUtility.GetRectangles(boxColliderMap);
boxes.ForEach(b =>
{
var c = b.Center2 / 8f;
var w = b.Width2 / 8f;
var h = b.Height2 / 8f;
var size = new Vector2(w, h);
AddBoxCollider2D(c, size);
});
}
private void Awake()
{
mazeUtility = GetComponent<MazeUtility>();
}
private void AddBoxCollider2D(Vector2 offset, Vector2 size)
{
var box = gameObject.AddComponent<BoxCollider2D>();
box.offset = offset;
box.size = size;
}
}