タイトルの通りなのですが、絵にするとこんな感じです。
以下のような2次元配列があった時に
以下のようにある部分を範囲選択して切り取り新しい2次元配列を作成する処理になります。
以下コードですが最新版なら普通のC#でもUnityでもどちらでも行けます。
実装コード
早速実装例を紹介したいと思います。
ArrayUtilityクラス
まず、2次元配列に対する操作を行うUtilityクラスを以下の通り作成します。
冒頭の図のような処理を行うのは中ほどにあるCutメソッドです。
中心座標を指定してその周囲を切り取るCutByCenterも併せて実装しました。
// 配列に対する汎用機能を提供します。 public static class ArrayUtility { // 指定した2次元配列を複製します。 public static T[,] Clone<T>(T[,] src) { int h = src.GetLength(0); int w = src.GetLength(1); var map = new T[h, w]; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { map[y, x] = src[y, x]; } } return map; } // 指定した2次元配列の位置 (x, y) から (w to h) の範囲を切り取ります。 // (w, h)の 指定が src の範囲を超える場合 src の範囲内で切り取りを行います。 public static T[,] Cut<T>(T[,] src, int x, int y, int w, int h) { // コピー元の配列の大きさ int ymax = src.GetLength(0); int xmax = src.GetLength(1); // 入力値が範囲内に収まるかどうか if (x < 0 || x > xmax || y < 0 || y > ymax) { string msg = $"Parameter is out of range. src[y={ymax},x={xmax}], x={x}, y={y}"; throw new ArgumentOutOfRangeException(msg); } if (w == 0 || h == 0) { throw new ArgumentException($"Invalid parameter. w={w}, h={h}"); } // コピー先の配列の大きさ int rh = y + h <= ymax ? h : h - (y + h - ymax); int rw = x + w <= xmax ? w : w - (x + w - xmax); // 元の大きさからコピーする var map = new T[rh, rw]; for (int my = 0, _y = y; my < rh; my++, _y++) { for (int mx = 0, _x = x; mx < rw; mx++, _x++) { map[my, mx] = src[_y, _x]; } } return map; } // 2次元配列の中心位置 (cx, cy) から指定した半径 (rw, rh) の範囲を切り取ります。 // (rw, rh) が src の範囲を超える場合 src の範囲内で切り取りを行います。 // // 戻り値のタプルには src から切り取った範囲の値が設定されます。 public static (T[,] map, Rect2Di rect) CutByCenter<T>(T[,] src, int cx, int cy, int rw, int rh) { int xmax = src.GetLength(1); int ymax = src.GetLength(0); if (cx < 0 || cx > xmax || cy < 0 || cy > ymax) { string msg = $"Parameter is out of range. src[y={ymax},x={xmax}], x={cx}, y={cy}"; throw new ArgumentOutOfRangeException(msg); } // 切り取る範囲の最大・最小値を取得 int min_x = cx - rw; int max_x = cx + rw; int min_y = cy - rh; int max_y = cy + rh; if (min_x < 0) { min_x = 0; } if (max_x >= xmax) { max_x = xmax - 1; } if (min_y < 0) { min_y = 0; } if (max_y >= ymax) { max_y = ymax - 1; } var map = new T[max_y - min_y + 1, max_x - min_x + 1]; for (int my = 0, _y = min_y; _y <= max_y; my++, _y++) { for (int mx = 0, _x = min_x; _x <= max_x; mx++, _x++) { map[my, mx] = src[_y, _x]; } } return (map, new Rect2Di(min_x, max_x, min_y, max_y)); } // [デバッグ用] 指定した配列の内容を文字列に変換します。 public static string TostringByDebug<T>(T[,] src) { var a = new StringBuilder(); var b = new StringBuilder(); int ymax = src.GetLength(0); int xmax = src.GetLength(1); for (int y = 0; y < ymax; y++) { for (int x = 0; x < xmax; x++) { b.Append($" {src[y,x]},"); } a.Append(b.ToString().Trim(' ', ',')); a.Append(Environment.NewLine); b.Clear(); } return a.ToString(); } } // 一時的なデータの入れ物 public readonly struct Rect2Di { public int XMin { get; } public int YMin { get; } public int XMax { get; } public int YMax { get; } public Rect2Di(int xmin, int ymin, int xmax, int ymax) { this.XMin = xmin; this.YMin = ymin; this.XMax = xmax; this.YMax = ymax; } }
使い方
上記実装の使用方法です。
public static void Main(string[] args) { int[,] _m = new int[,] { { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 2, 2, 2, 2, 2 }, { 2, 2, 2, 2, 2, 3, 3, 3, 3, 3 }, { 3, 3, 3, 3, 3, 4, 4, 4, 4, 4 }, { 4, 4, 4, 4, 4, 5, 5, 5, 5, 5 }, { 5, 5, 5, 5, 5, 6, 6, 6, 6, 6 }, { 6, 6, 6, 6, 6, 7, 7, 7, 7, 7 }, { 7, 7, 7, 7, 7, 8, 8, 8, 8, 8 }, { 8, 8, 8, 8, 8, 9, 9, 9, 9, 9 }, { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }, }; // 左上基準に範囲を切り取る var map1 = ArrayUtility.Cut(_m, 0, 0, 5, 5); Console.WriteLine(map1.ToStringByDebug()); Console.WriteLine(""); // 0, 0, 0, 0, 0 // 1, 1, 1, 1, 1 // 2, 2, 2, 2, 2 // 3, 3, 3, 3, 3 // 4, 4, 4, 4, 4 // 中心座標を指定して範囲を切り取る var (map, rect) = ArrayUtility.CutByCenter(_m, 4, 4, 2, 2); Console.WriteLine(map.ToStringByDebug()); Console.WriteLine(""); // (4, 4)を中心に上下左右に2ずつ(縦横5x5)の範囲を切り取る // 2, 2, 2, 3, 3 // 3, 3, 3, 4, 4 // 4, 4, 4, 5, 5 // 5, 5, 5, 6, 6 // 6, 6, 6, 7, 7 }
ArrayExtension:拡張メソッド版
上記の処理を2次元配列の拡張メソッドとして定義したいと思います。
コード例は以下の通りです。
先ほどのUtilityの処理を中で呼び出すようにしています。
// 配列に対する拡張機能を提供します。 public static class ArrayExtension { // <see cref="ArrayUtility.Clone{T}(T[,])"/> と同じ機能を持つ拡張メソッド public static T[,] Clone<T>(this T[,] src) => ArrayUtility.Clone(src); // <see cref="ArrayUtility.Cut{T}(T[,], int, int, int, int)"/> と同じ機能を持つ拡張メソッド public static T[,] Cut<T>(this T[,] src, int x, int y, int w, int h) { ArrayUtility.Cut(src, x, y, w, h); } // <see cref="ArrayUtility.CutByCenter{T}(T[,], int, int, int, int)"/> と同じ機能を持つ拡張メソッド public static (T[,] map, Rect2Di rect) CutByCenter<T>(this T[,] src, int cx, int cy, int rw, int rh) { ArrayUtility.CutByCenter(src, cx, cy, rw, rh); } // <see cref="ArrayUtility.TostringByDebug{T}(T[,])"/> と同じ機能を持つ拡張メソッド public static string ToStringByDebug<T>(this T[,] src) { ArrayUtility.TostringByDebug(src); } }
使い方
上記実装例の使用方法です。
ほぼ同じですが、2次元配列のメソッドとして使用できるようになっています。多分こっちの方がすっきりすると思います。
public static void Main(string[] args) { int[,] _m = new int[,] { { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 2, 2, 2, 2, 2 }, { 2, 2, 2, 2, 2, 3, 3, 3, 3, 3 }, { 3, 3, 3, 3, 3, 4, 4, 4, 4, 4 }, { 4, 4, 4, 4, 4, 5, 5, 5, 5, 5 }, { 5, 5, 5, 5, 5, 6, 6, 6, 6, 6 }, { 6, 6, 6, 6, 6, 7, 7, 7, 7, 7 }, { 7, 7, 7, 7, 7, 8, 8, 8, 8, 8 }, { 8, 8, 8, 8, 8, 9, 9, 9, 9, 9 }, { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }, }; // 左上基準に範囲を切り取る var map1 = _m.Cut(0, 0, 5, 5); Console.WriteLine(map1.ToStringByDebug()); Console.WriteLine(""); // 0, 0, 0, 0, 0 // 1, 1, 1, 1, 1 // 2, 2, 2, 2, 2 // 3, 3, 3, 3, 3 // 4, 4, 4, 4, 4 // 中心座標を指定して範囲を切り取る var (map, rect) = _m.CutByCenter(4, 4, 2, 2); Console.WriteLine(map.ToStringByDebug()); Console.WriteLine(""); // (4, 4)を中心に上下左右に2ずつ(縦横5x5)の範囲を切り取る // 2, 2, 2, 3, 3 // 3, 3, 3, 4, 4 // 4, 4, 4, 5, 5 // 5, 5, 5, 6, 6 // 6, 6, 6, 7, 7 }
こちらも最初のコードと同じように切り取ることができました。
コードばかりになってしまいましたが以上です。