agoTLで何回か見たことある「すえなみさん」「焼肉」ってこの人の事か。14日目はShiroKuroさん(@taku_nishimu)の「UGUIでアニメーションを使ってNoCodingで画面遷移を作る」でした。Calenderに書く事を急遽思いつきました。・・・そしてUnityの記事のフリして実はゲームプログラム系記事です。Unityなネタおもいつきませんでしたあぁぁぁ!!!シーングラフって?簡単に言ってしまえばゲームオブジェクト(transform)の親子関係の事です。今回の記事では「シーングラフ」はこの構造を指します。親のtransform情報に変更が有れば全ての子供に影響が有り、子供の情報が変われば親には影響が無く、その子供の全ての子供に影響がでます。例を上げますと、人型キャラの上半身には胴体が有り、その子供として腕がくっ付いていてその子供に~と続けていく構造で、腕からさらに先にある親指が動いても親である上半身に影響を与えませんが、胴体が動いたら指の位置もそれに合わせて基準が動く構造の事です。文章で説明するよりもゲームオブジェクトに親子関係を持たせて適当に動かした方が意味が解りやすいとは思います。この記事ではシーングラフの基本の基本に当たる構造の実装をUnityを使って雑に説明していきます。[注意事項]筆者はUnityの内部実装がどうなっているのかを知らない為、今回紹介するUnityを例にした解説や、実装方法は一般的な解説と実装方法をUnityを使う側からの想像で当てはめています。Unityの仕様/実装方法、では無いのでご注意ください。言い訳えー、サンプルはありません!!メンドクs・・・急遽書く事を決めた為時間が取れませんでした!決してHearthStoneやったり、Catanやってたとかじゃありませんよ?座標空間のおさらいシーングラフの実装に入る前に、まずは座標系のおさらいを軽くします。3dを扱う際、段階的に違う座標系で計算を行います。Spaceそれぞれについて軽く説明します。spaceオブジェクト自身を変形する座標です。Space”と言う時も有ります。localScaleにあたります。Spaceの設定を行っています。spaceの情報に外的要因(親のオブジェクト等)を加えた変形を行った後の座標系です。rotationにあたります。space内のオブジェクトにカメラの情報(view行列)を適用させ、変形した後の座標系です。viewの中にはカメラの位置や向きなどが入っています。matrix)を適用させ変形した後の座標です。投影の種類はUnityのカメラ設定でも見るOrthogonalとPerspectiveです。space最終的に画面に描画すると時に使用する座標になります。dxライブラリやxna等のライブラリでで2dゲームを作る際にはスクリーン座標で直接する事が多いです。unityではUIを作る際にスクリーン座標と同じように設定をすることもあります、多分。座標系の説明は以上です。直接は関係無いので個々に行われる変換はスルーしました、。シーングラフの実装ではlocalとworldを扱います。シーングラフ上記の通り、シーングラフはオブジェクトに親子関係を持たせるので、木構造を使用します。この全体の仕組みをシーングラフと呼び、各オブジェクトをシーンノードと呼びます。unity内でのルート(最初のノード)はシーンファイル内のゲームオブジェクトを配置している場所(hierarchy)と同じだと思ってください。シーンノード実装それでは実装の説明に入ります。今回はシーングラフの解説がメインなので、コンポーネントに分けず、このクラス内にtransform情報を宣言します。このノードがくっ付いてる親のシーンノード。このノードにくっ付いている子供(シーンノード一覧)。OnRemoved(){}クラスの内部にはまずGameObjectに有る様な名前とActiveを持たせています。その後にある宣言はTransformに有る内容になります。OnAdded()メソッドはノードが別のノードにアタッチされた時に呼ばれ、OnRemoved()はノードが切り離された時に呼ばれます。この記事ではOnAdded()は中身を特に実装しませんが、何か処理を足したくなったり、継承したクラスで使いたくなった時の為に乗せておいています。構造的な処理を実装して行きます。最初にノードを子供として追加する処理を実装します。やる事は簡単で、子供として追加するノードの親を自身にし、子供一覧に追加した後にOnAdded()を呼びます。true;}次に子供を切り離す処理を実装します。これは単純にListクラスのRemove()を呼んでいます。m_childs.Remove(toRemove);}次に子供とその子供を全て切り離して消していく処理を実装します。実装は再帰で構造の底まで行き、そこから全ての子供を消していっています。m_childs.Clear();}RemoveAllChildren()内で全ての子供のRemoveAllChildren()を呼んで行き、その後に通常のRemove()同様OnRemoved()で切り離された時に処理を呼び、後はリストを空にしていっています。子供を消す処理を作りましたので、今度はノードの終了処理を呼びます。これも今回は単純に全ての子供に再帰的に処理を呼び出し、上で実装したRemoveAllChidren()を呼び出して行きます。おまけに終了処理の実装が出来ました。前準備が長くなりましたが、これから変形情報の更新処理を実装して行きます。最初に行う事はLocalTransformの行列を計算します。やる事としましては、位置、回転、スケールを一つの行列に組み合わせます。準備段階として位置とスケールのVector3をMatrix4x4に変換する処理をまず実装します。ret;}両方のメソッドで1.0fになっている所はwの値で、デフォルトにしてあります(Identityで初期化しているので必要ないはずですが、念の為)。準備出来ましたので、更新処理でLocalTransformを計算します。...続くこれでLocalTransformを計算出来ました。これが自身の情報ですので、これを基準にしてWorldTransformを計算して行きます。補足:行列のおさらい上記で行列の計算を行っておりますので、次に進む前に行列に関しての補足情報です。行列の掛け算を行う際、計算の順番により結果が変わりますので、計算順を間違えない事が大事になります。a]これを踏まえたうえで移動、回転、スケール情報を一つの行列に組み合わせる時も計算順を考える必要があります。スケール]になります。ちなみに、Unityの行列は行優先(Column-major)らしいので、もし列優先(row-major)の場合は下記になります。移動]Worldの計算では本題に戻ってWorldの計算に入ります。親のWorld]で計算出来ます。Worldは親から受けた影響を自身に与え、その結果がまた子供のWorldの計算に使われていくイメージです。親がいない場合(自身がシーングラフのルートの場合)、自身のLocalがWorldになります。LocalTransform;}これでLocalもWorldも計算出来ましたので、後は子供の更新処理を呼んで行きます。子供も更新処理の中で同じ計算を行って生きますので、これにより親の変形情報がツリー上の全ての子供に影響を与えるデータ構造を実装出来ます。Update()ノードの実装基本的な実装が出来ました!シーングラフ実装では次にルートノードを保持するシーングラフクラスを作成していきます。シーングラフクラスはイメージ的には記事の最初の方に有った、Unityのヒエラルキー部分に当たります。ゲームオブジェクトの子供でなく、ヒエラルキーの直下に置いてあるオブジェクトが、ルートにアタッチされているノードと言うイメージです。今回のシーングラフの実装自体は凄く簡単で、中にルートのノードを保持させ、それを主に操作していきます。}次に更新処理を実装します。今回はノードのUpdate()を呼ぶだけです。}}次に終了処理を実装します。今回はノードの終了処理だけを呼び出しています。null;}次はノードを追加する処理を実装します。今回はアタッチする親を指定出来る様にしますが、もしその親がルート以下に居なかった場合は、追加をしない様にします。先に指定したノードを検索する処理をノード内に実装します。null;}検索する処理は今までと同じ様に全ての子供を再起的に検索していき、指定したノードが居たら返す様にしています。ノードを見つける機能を実装しましたので、ノードを追加する処理をシーングラフ内に実装します。targetNode.AddChild(toAdd);}実装は以上になります。後はシーングラフを作って、ノードをくっつけて行き、更新処理を呼んで行くイメージです。実装する時に流用出来ます。シーングラフは更新の影響がツリーの一番上から一番下に流れていきますが、BVHを実装する時はツリーの一番下から上に影響を与えていく(大きさを合体させていく)ので、変形の更新処理後に挟むと、実装するには調度言い場所になります。終わりだらだらと書いて解りづらかったとは思いますが、Transformの親子関係の内部構造を妄想するヒントになったり(する人は居ないとは思いますが・・・)、自前のライブラリを作ってる人の参考にでもなれば幸いです。説明しましたので、何か質問が有ればコメントをください。使用に同意したとみなされます。