Redpoll's 60
 Home / 3Dプログラミング入門 / 第2章 $§$2-18
第2章 2D空間におけるオブジェクトの運動
$§$2-1 オブジェクトの初期状態$§$2-17 衝突判定 5
$§$2-2 行列による変換の詳細 1$§$2-18 初期状態における頂点情報の取得について
$§$2-3 行列による変換の詳細 2$§$2-19 衝突判定 6 (軸平行な長方形同士の衝突)
$§$2-4 自転と公転$§$2-20 衝突判定 7 (円盤 vs 長方形)
$§$2-5 一体化したオブジェクトの運動 1$§$2-21 衝突判定 8 (回転した長方形同士の衝突)
$§$2-6 一体化したオブジェクトの運動 2$§$2-22 衝突判定 9 (「倉庫番」プログラムの作成)
$§$2-7 一体化したオブジェクトの運動 3$§$2-23 衝突判定 10 (円盤 vs 三角形)
$§$2-8 指定方向へのオブジェクトの移動 1$§$2-24 衝突判定 11 (直線 vs 長方形、円盤、直線)
$§$2-9 指定方向へのオブジェクトの移動 2$§$2-25 円と直線による補間曲線 1
$§$2-10 指定方向へのオブジェクトの移動 3$§$2-26 円と直線による補間曲線 2
$§$2-11 指定方向へのオブジェクトの移動 4 (連射プログラムの実装)$§$2-27 その他の重要事項 1 (UnityのTransformクラスによる記述 ; カメラ移動の基本)
$§$2-12 指定方向へのオブジェクトの移動 5$§$2-28 その他の重要事項 2 (画面に表示されるXY平面の範囲 ; ミニマップの実装)
$§$2-13 衝突判定 1 (点 vs 円盤、長方形 ; ローカル座標からワールド座標への変換 2D)$§$2-29 その他の重要事項 3 (スクリーン座標からワールド座標への変換 2D; スクリーンショットの撮影範囲)
$§$2-14 衝突判定 2$§$2-30 課題 1
$§$2-15 衝突判定 3$§$2-31 課題 2
$§$2-16 衝突判定 4

$§$2-18 初期状態における頂点情報の取得について


2-15節では一般のオブジェクトとの衝突判定は、そのオブジェクトを構成する複数の三角形との衝突判定に帰結することについて見てきた。一般のオブジェクトとの衝突判定の際に必要となるのは、オブジェクトを構成する全ての三角形の頂点座標であるが、それは初期状態における座標であった。
今までのプログラムではオブジェクトを構成する全ての三角形の(初期状態の)頂点座標は、オブジェクトのプロパティ initTriangleCoords から取得していたが、このプロパティに含まれるデータは講義のために事前に用意したものであり、講義で使用するオブジェクト以外には対応していない。たとえば読者が新しいオブジェクトを作成し、そのオブジェクトを使って衝突判定を行う場合にはオブジェクトを構成する三角形の各頂点座標を新たに調べる必要がある。モデリングツールなどで各頂点の座標を1つ1つ調べるのは手間がかかるため、プログラムによって効率的に取得できるようにすることが望ましい。
本節では、Unityにおいてオブジェクトを構成する三角形の各頂点座標を取得する方法について簡単に解説する。

(本節において取得する頂点座標は、オブジェクトを構成する全ての三角形の頂点座標であるが、上でも述べたように初期状態における座標である(いわゆるメッシュの座標)。プログラム実行中の座標ではない。プログラム実行中におけるオブジェクトの頂点座標の計算については 2-13節、あるいは 4-12節を参照)


A) 三角形に分割されたオブジェクトの各頂点座標をプログラムから取得する

右図1はオブジェクトTriangleの初期状態である。
図1 Triangle 初期状態
図に示されるように Triangleの3つの頂点はそれぞれ $(0,\ 0)$、$(-1,\ 2)$、$(2,\ 0)$ である。

「開始前の準備」第1節でも述べたが、プログラムにおいてオブジェクトを使うためにはUnityに読み込んだオブジェクト(Project Window内のオブジェクト)を Hierarchy Window までドラッグする必要がある。
たとえば、このTriangleをプログラムで使用するためには下図2、図3に示されるように Project Window にあるTriangleを Hierarchy Window までドラッグする (この講義で使用するオブジェクトは全てFBXファイルの形で保存されているが、それらのオブジェクトをProject Windowに置くためには、オブジェクト(が保存されているFBXファイルあるいはフォルダ)を直接Project Windowまでドラッグすればよい ; 「開始前の準備」第1節参照)。

図2 Project WindowにおいてオブジェクトTriangleを選択する
図3 TriangleをProject WindowからHierarchy Windowへドラッグする

Hierarchy Window にセットされたオブジェクトはプログラムから次のように取得して操作することができる。以下のプログラムは Triangleをx軸プラス方向に $5$ だけ平行移動させるものである。
GameObject obj = GameObject.Find("Triangle");
obj.transform.localPosition = new Vector3(5, 0, 0);

1行目の GameObject.Find(..) の引数は Hierarchy Window にセットされたオブジェクトの名前を指定する。ここでは Hierarchy Window内にある Triangle を使うので引数は "Triangle" としている。

オブジェクトを構成する三角形の各頂点座標を取得するプログラムを以下に示す。
GameObject obj = GameObject.Find("Triangle");
MeshFilter objMF = obj.GetComponent<MeshFilter>();
Mesh objMesh = objMF.mesh;

int i0 = objMesh.triangles[0];
int i1 = objMesh.triangles[1];
int i2 = objMesh.triangles[2];
Vector2 c0 = objMesh.vertices[i0];
Vector2 c1 = objMesh.vertices[i1];
Vector2 c2 = objMesh.vertices[i2];

Debug.Log(c0+", "+c1+", "+c2);

2行目では obj.GetComponent<MeshFilter>() によって、Triangleの MeshFilter というコンポーネントを取得している。この MeshFilter には mesh というプロパティが用意されているが、この mesh プロパティを経由してオブジェクトの頂点情報やオブジェクトを構成する三角形についての情報を取得することができる。
プログラム中では3行目においてローカル変数 objMesh にこのプロパティをセットし、objMesh を使って各データを取得しているがそれらは次のような意味を持っている。

vertices
  :  オブジェクトを構成する全ての頂点座標の配列 (MeshクラスのVector3[]型のプロパティ)。

triangles
  :  オブジェクトを構成する全ての三角形の頂点インデックス配列 (Meshクラスのint[]型のプロパティ)。オブジェクトが1つの三角形で構成されていれば、triangles の要素数は $3$ である。10個の三角形で構成されていれば triangles の要素数は $3 \times 10 = 30$ であり、100個の三角形で構成されていれば要素数は $3 \times 100 = 300$ である。つまり、triangles の要素数はオブジェクトを構成する三角形の個数の$3$倍に等しい。

たとえば、今回のオブジェクトTriangleの場合は3つの頂点によって構成されているので、プログラム中の objMesh.vertices の要素数は $3$ である。8行目から10行目では、3つの頂点座標を取得しているが、図4の出力結果では確かに Triangle の3つの頂点 $(0.0,\ 0.0)$、$(-1.0,\ 2.0)$、$(2.0,\ 0.0)$ が出力されている。

図4 上記プログラム12行目のLog文の出力結果
図5 Triangleの各頂点座標と頂点インデックス

Meshクラスのプロパティ triangles を先程「オブジェクトを構成する全ての三角形の頂点インデックス配列」と書いたが、この点について以下に説明する。
今回のオブジェクトTriangleの vertices の要素数は$3$であるので、そのインデックスは $0$ から $2$ までを使用することができる (すなわち vertices[0]vertices[1]vertices[2] が使える)。図5では各頂点の座標の近くに青い数字が付されているが、この数字は頂点座標配列verticesのインデックスを表している。つまり、青い数字の $0$ は vertices[0]、$1$ は vertices[1] という意味である。
そして、上で述べたように triangles の要素数はオブジェクトを構成する三角形の個数の$3$倍であり、Triangleの場合は三角形1つで構成されているので triangles の要素数は $3$ である。プログラムの5行目から7行目では objMesh.triangles の各要素を取得しているが、それらの値は $0$、$1$、$2$ である。これは、つまり
「オブジェクトTriangleは1つの三角形で構成されており、その三角形の各頂点のインデックスは $0$、$1$、$2$ である」
ということを意味している。

Triangleのようにただ1つの三角形で構成されているオブジェクトでは、頂点インデックス配列の役割は理解しにくいかもしれないが、次のように複数の三角形で構成されているオブジェクトを例にとればその役割はより理解しやすい。

図6 Pentagon 初期状態
図7 Pentagonの各頂点座標と頂点インデックス

図6は五角形のオブジェクトPentagonであり、図7は Pentagon を三角形に分割表示し、各三角形の頂点座標を表示したものである。先程のTriangleと同じように、Pentagonについてもその mesh プロパティから頂点情報やPentagonを構成する三角形の情報について取得してみよう。まずは、Pentagonの全頂点を取得してその値を表示する。
GameObject obj = GameObject.Find("Pentagon");
MeshFilter objMF = obj.GetComponent<MeshFilter>();
Mesh objMesh = objMF.mesh;

Vector2 c0 = objMesh.vertices[0];
Vector2 c1 = objMesh.vertices[1];
Vector2 c2 = objMesh.vertices[2];
Vector2 c3 = objMesh.vertices[3];
Vector2 c4 = objMesh.vertices[4];

Debug.Log(c0 + "\n" + c1 + "\n" + c2 + "\n" + c3 + "\n" + c4);

図8  11行目のLog文の出力結果
プログラム内の objMesh.vertices はPentagonの頂点座標配列であり、Pentagonは5つの頂点を持っているので要素数は$5$である。実行結果(図8)に見られるように、ここに出力されている各頂点座標は図7のものと同じ値である。
また、図7には各頂点座標の近くに青い数字が付されているが、この数字は先程のTriangleの場合と同じく頂点座標配列verticesのインデックスを表すものである。

次に、Pentagonを構成する三角形の情報について出力する。
GameObject obj = GameObject.Find("Pentagon");
MeshFilter objMF = obj.GetComponent<MeshFilter>();
Mesh objMesh = objMF.mesh;

// 第1の三角形
int i0 = objMesh.triangles[0];    
int i1 = objMesh.triangles[1];
int i2 = objMesh.triangles[2];
// 第2の三角形
int i3 = objMesh.triangles[3];
int i4 = objMesh.triangles[4];
int i5 = objMesh.triangles[5];
// 第3の三角形
int i6 = objMesh.triangles[6];
int i7 = objMesh.triangles[7];
int i8 = objMesh.triangles[8];

Debug.Log(  "("+i0 + ", " + i1 + ", " + i2 + "), " +
            "("+i3 + ", " + i4 + ", " + i5 + "), " +
            "("+i6 + ", " + i7 + ", " + i8 + ")");

図9  18行目のLog文の出力結果
Pentagonは3つの三角形で構成されているので頂点インデックス配列trianglesの要素数は $9$ である。プログラムの6行目から16行目で objMesh.triangles の各要素を取得し、それらの値を18行目において出力しているが、図9はその出力結果である。
その内容は $(0, 1, 2)$、$(1, 3, 2)$、$(1, 4, 3)$ となっているが、これは次のことを意味している。
「オブジェクトPentagonは3つの三角形で構成されており、それらの三角形の各頂点のインデックスは第1の三角形が$(0, 1, 2)$、第2の三角形が$(1, 3, 2)$、第3の三角形が$(1, 4, 3)$ である」。

図7において頂点インデックス$(0, 1, 2)$の三角形は左の三角形であり、頂点インデックス$(1, 3, 2)$の三角形は中央の三角形、頂点インデックス$(1, 4, 3)$の三角形は右の三角形である。実際にこれらのインデックスから各頂点座標を出力してみよう。
GameObject obj = GameObject.Find("Pentagon");
MeshFilter objMF = obj.GetComponent<MeshFilter>();
Mesh objMesh = objMF.mesh;

Vector2 c0 = objMesh.vertices[0];
Vector2 c1 = objMesh.vertices[1];
Vector2 c2 = objMesh.vertices[2];
Vector2 c3 = objMesh.vertices[3];
Vector2 c4 = objMesh.vertices[4];

Debug.Log(c0 + ", " + c1 + ", " + c2);  // 左の三角形
Debug.Log(c1 + ", " + c3 + ", " + c2);  // 中央の三角形
Debug.Log(c1 + ", " + c4 + ", " + c3);  // 右の三角形

図7 Pentagonの各頂点座標と頂点インデックス
図10 実行結果

実行結果(図10)に示されるように、最初に出力されている3つの座標は図7の左の三角形の各頂点座標であり、次に出力されている3つの座標は中央の三角形の各頂点座標、最後に出力されている3つの座標は右の三角形の各頂点座標に一致している。

たとえば、あるオブジェクトが10個の三角形によって構成されており、それらの三角形の各頂点座標を出力するには次のように記述すればよい (以下の1行目の SomeObject は10個の三角形によって構成されているオブジェクトであるとする)。
GameObject obj = GameObject.Find("SomeObject");
MeshFilter objMF = obj.GetComponent<MeshFilter>();
Mesh objMesh = objMF.mesh;

for (int i = 0; i < 10; i++)
{
    int idx = i * 3;    
    int i0 = objMesh.triangles[idx];    
    int i1 = objMesh.triangles[idx + 1];
    int i2 = objMesh.triangles[idx + 2];

    Vector2 c0 = objMesh.vertices[i0];
    Vector2 c1 = objMesh.vertices[i1];
    Vector2 c2 = objMesh.vertices[i2];
    
    Debug.Log(c0 + ", "+ c1 + ", " + c2);
}    


5行目のfor文は10回繰り返されるが、1回のループにおいて1つの三角形の各頂点座標を出力する。プロパティ trianglesint[]型の頂点インデックス配列であり、先頭要素から3つずつ順番に1つの三角形を定義している。


ここではオブジェクトの mesh プロパティから、そのオブジェクトの頂点情報やオブジェクトを構成する三角形の情報を取得する方法について見てきたが、オブジェクトの mesh プロパティを使用する際には注意が必要である。それは mesh プロパティから verticestriangles などへアクセスするには事前に1つ設定をしなければならないことがあるためである。
設定自体は簡単である。

  • 図2 Project WindowにおいてオブジェクトTriangleを選択する
  • 図11 Triangleを選択したときのInspectorの内容
  • 図12 Inspector内の「Read/Write Enabled」にチェックを入れる

例えば、上で使用したオブジェクトTriangleであれば、まず Project Window の Triangle を選択する (図2 ; Hierarchy Window のTriangleではなく Project Window のTriangleを選択する)。選択すると Inspector の内容が図11のように切り替わるが、その中の「Read/Write Enabled」という項目のチェックボックスにチェックを入れる (図12 ; これはデフォルトではチェックが入っていない)。

図13 ダイアログが表示されるので「Apply」をクリックする
図14 「meshのverticesへのアクセスは許可されていない」というエラーが表示される

チェックを入れてから他の操作をすると図13に示されるダイアログが表示されるので、ダイアログ内の「Apply」をクリックする。
事前に設定することはこれだけである。この設定によって mesh プロパティから verticestriangles へアクセスすることができるようになる。もし、この設定をせずに上記のようなプログラムを実行すると図14に示されるエラーが発生するので注意 (meshプロパティのverticestrianglesへのアクセスがデフォルトでは行えないようになっているのは、実行中の負荷軽減のためである。特にそれらのデータを必要としない限り上記のチェックボックスはOFFにしておくべきである)。














© 2020-2024 Redpoll's 60 (All rights reserved)