Redpoll's 60
第5章 変換のオブジェクト化

$§$5-9 課題


本節は読者用の課題である。
前節までにおいて、Unityの標準的な変換方法によるプログラムや独自のTransformクラスによるプログラムを見てきたが、どちらのプログラムもその記述の仕方は同じものであった。
本節における課題は前節までに学習したいわば標準的な変換方法による、一体化したオブジェクトを運動させるプログラムの作成である。その課題の対象となるオブジェクトは扇風機である。
扇風機というオブジェクトはその名前から受ける印象とは異なり、3Dプログラミングの世界では非常に重要な役割を持っている。複数のオブジェクトからなる階層構造や、それぞれのオブジェクトが独立した運動をする点、そして実装の難易度が初学者向きであるという点、さらにそのオブジェクト及びオブジェクトの運動自体がよく知られたものであるという点では、とても稀な存在である。そういった意味では、扇風機というオブジェクトは3Dプログラミングにおける最初の’ボスキャラ’なのである。




では、プログラム作成に必要となるいくつかの準備から始める。
ここで実装する扇風機は以下の5つのオブジェクトによって構成されている。

  • 図1 BaseStand 初期状態
  • 図2 Guard 初期状態
  • 図3 RotStand 初期状態

  • 図4 SlidePipe 初期状態
  • 図5 Fan 初期状態
  • 図6 すべてのオブジェクトを一体化した状態

図1から図5の5つのオブジェクト BaseStand、Guard、RotStand、SlidePipe、Fanが今回の扇風機を構成するオブジェクトである。図6は、各オブジェクトをそれぞれの親オブジェクトへアタッチし、すべてのオブジェクトを一体化した状態である。

以下、各オブジェクトの運動を1つ1つ見ていく。

(1) BaseStand
このプログラムにおける扇風機の運動は下図7に示される青い領域の上で行うものとする。この青い領域はBaseStandの底面と同じ形、大きさであり、その中心座標は $(12, 0, -2)$ である。
BaseStandは初期状態でその中心が原点に置かれているので、この青い領域の上に移動させるには、ただ $(12, 0, -2)$ だけの平行移動を行えばよい 。

図7 青い領域 (中心の座標は (12, 0, -2))
図8 (12, 0, -2)だけの平行移動によってBaseStandは青い領域の上に移動する

(2) Guard
図9 Guardの初期状態においては、x軸が左右両端の突起を貫通している
Guardは初期状態においては図2や図9に示されるように、左右両端の突起(の中央)をx軸が貫通している。Guardの運動はこの左右両端の突起(の中央)を貫通する軸周りの回転である。
初期状態では左右両端の突起を貫通しているのはx軸であるから、Guardは図10に示されるようにx軸周りの回転になるが、Guradの回転は$-45$°から$45$°の間に制限されている。
図11はすべてのオブジェクトを一体化した状態で、Guardのみが運動している様子である。このとき Guardはアタッチポジションにおいて左右両端の突起を貫通する軸の周りに$-45$°から$45$°の回転を行っている (図11における Guardの回転は初期状態での回転から平行移動させて行っているだけなので、回転軸はx軸に平行である)。

図10 BetaA 実行結果
図11

以下のプログラムは初期状態の Guardに、x軸周りの$-45$°から$45$°の回転を実行するものである。
[BetaA]  (実行結果 図10)
i_shm++;
float deg = 45.0f * Mathf.Sin(i_shm * Mathf.Deg2Rad);
Matrix4x4 R = TH3DMath.GetRotation4x4(deg, Vector3.right);
Guard.SetMatrix(R);

2行目の deg は$-45$から$45$の間を往復する数値で、単振動の計算によって算出している。i_shmは単振動の計算で使用する角度を表すインスタンス変数である。


(3) RotStand
RotStandは初期状態においては図3に示されるように、その中央をy軸が貫通しているが、RotStandの運動はこのy軸周りの回転である。ただし、RotStandも回転角度に制限があり、y軸周りに$-75$°から$75$°の範囲で回転を行う。
また、RotStandにはアタッチのための移動が必要ない。RotStandは親子関係の設定をする必要はあるが、親オブジェクトにアタッチするための平行移動は必要ない。すでに初期状態において、アタッチポジションに配置されている。したがって、RotStandにはy軸周りの回転の回転だけを行うことになる。
図12は初期状態の位置において、RotStandが$-75$°から$75$°の回転を行っているときの様子である。
図13はすべてのオブジェクトを一体化した状態で、RotStandのみが運動している様子である。図12、図13における RotStandの位置は同じである。図13において RotStandは親子関係が設定されているが、親座標系の中ではy軸周りの回転だけを行っているのである。

図12 BetaB 実行結果
図13

以下のプログラムは初期状態の RotStandに、y軸周りの$-75$°から$75$°の回転を実行するものである。
[BetaB]  (実行結果 図12)
i_shm++;
float deg = 75.0f * Mathf.Sin(i_shm * Mathf.Deg2Rad);
Matrix4x4 R = TH3DMath.GetRotation4x4(deg, Vector3.up);
RotStand.SetMatrix(R);

先程のコードとほとんど同じものである。ここでも、$-75$から$75$を往復する数値は単振動の計算によって求めている。


(4) SlidePipe
SlidePipeは初期状態においては図4に示されるように円柱形状のオブジェクトで内部は中空である。その中央をy軸が貫通しているが、SlidePipeの運動は回転ではなく、y軸方向の上下運動である。
具体的には、図14、図15に示される運動である。図14では SlidePipeが初期状態の位置から$0.6$だけy軸方向へ上昇し、さらにその位置から初期状態の位置まで下降する($0.6$だけ下降する)運動を繰り返している。
SlidePipeも RotStandと同じく、アタッチのための平行移動を必要としない。SlidePipeにも親子関係を設定する必要はあるが、親オブジェクトにアタッチするための平行移動は必要ない。すでに初期状態において、アタッチポジションに配置されている。
図15はすべてのオブジェクトを一体化した状態で、SlidePipeのみが運動している様子であるが、このときの SlidePipeは親座標系の中でy軸方向に$0.6$だけ上昇し、$0.6$だけ下降するという運動を繰り返している。
なお、親オブジェクトにアタッチした時点での SlidePipeの位置は、全く上昇をしていない位置、すなわち最も下の位置である。この位置から$0.6$の上昇を開始するのである (すべてのオブジェクトを一体化した状態である図6では、SlidePipeは親座標系の中で動いていないので、このときも最も下の位置であり、この位置から$0.6$の上昇を始めるのである)。

図14 BetaC 実行結果
図15

以下のプログラムは初期状態の SlidePipeに、y軸方向に$0.6$上昇し、そこから$0.6$下降する運動を繰り返すものである。
[BetaC]  (実行結果 図14)
i_shm++;
float A = 0.6f;
float y = 0.5f * A * (Mathf.Sin(i_shm * Mathf.Deg2Rad) + 1.0f);
Matrix4x4 T = TH3DMath.GetTranslation4x4(0, y, 0);
SlidePipe.SetMatrix(T);

この運動もまた単振動の計算を利用している。3行目は$0$から$0.6$の間を往復する数値の計算である。


(5) Fan
今回の扇風機の運動においては、Fanは常に回転しているものとする。
Fanは初期状態においては図5に示されるように、z軸が羽の根元に位置する芯を貫通している。Fanの運動は、この羽の根元にある芯を貫通する軸周りの回転であり、初期状態においてはそれはz軸周りの回転に等しい。
図16は初期状態の位置において Fanをz軸周りに回転させたときの様子である。
図17はすべてのオブジェクトを一体化した状態で、Fanのみが運動している様子である。このとき Fanはアタッチポジションにおいて、羽の根元にある芯を貫通する軸周りの回転を行っている (図17における Fanの回転は初期状態での回転から平行移動させて行っているだけなので、回転軸はz軸に平行である)。
Fanもまた、親子関係を設定するオブジェクトではあるが、アタッチのための平行移動は必要ではない。RotStandやSlidePipeと同じく、初期状態においてアタッチポジションに配置されている。

図16
図17

以下のプログラムは初期状態の Fanにz軸周りの回転を実行するものである。
[BetaD]  (実行結果 図16)
i_degFan -= 12;
Matrix4x4 R = TH3DMath.GetRotation4x4(i_degFan, Vector3.forward);
Fan.SetMatrix(R);

i_degFanは Fanの回転角度を表すインスタンス変数であり、ここでは毎フレーム$12$ずつ減少している (Fanは初期状態ではz軸マイナス方向を向いており、図16ではカメラの位置もz軸のマイナス側に設置されている。その位置から原点に置かれているFanの回転を見ているため、毎フレーム$12$°ずつ減少するz軸周りの回転は図に示されるように時計周りである)。


以上が各オブジェクトの運動であるが、今回の実装においてはこれらのオブジェクトの運動を、キー操作によって変化させることができるようにする。具体的な、キー操作と各運動の対応は以下のとおり。
(長押しに対応するキー操作)
J    :  Guardの回転角度の減少 (下限 $-45$)。
K    :  Guardの回転角度の増加 (上限 $45$)。
L    :  SlidePipeの高さの減少 (下限 $0.0$)。
H    :  SlidePipeの高さの増加 (上限 $0.6$)。

(長押し非対応のキー操作)
R    :  RotStandの回転用スイッチ。RotStandが静止している状態で押すと、RotStandが(親座標系の)y軸周りに $-75^\circ$ から $75^\circ$ の範囲で往復回転を行う (再度押されるまでこの回転は続く)。


プログラム中で使用できるインスタンス変数、及び 定数は以下のとおり。

i_degGuard
  :  Guardの回転角度を表すfloat型インスタンス変数 ($-45^\circ$ から$45^\circ$ の範囲で回転)。

i_HORIZONTAL_ROTATION
  :  RotStandを回転あるいは停止させる際のスイッチの役割をするbool型インスタンス変数。

i_shmRotStand
  :  RotStandは $-75^\circ$ から $75^\circ$ の範囲で往復運動を行うが、その $-75$ から $75$ の数値の往復は単振動によって計算する。i_shmRotStand はその単振動計算における角度として使われるfloat型インスタンス変数である。

i_heightSlidePipe
  :  SlidePipeの高さを表すfloat型インスタンス変数 ( $0.0$ から $0.6$ の範囲で上昇下降する)。

i_degFan
  :  Fanの回転角度を表すfloat型インスタンス変数。上でも述べたとおり、今回の扇風機の運動でFanは常に回転し続けるのでこの変数の値は毎フレーム変化する。

c_attachPos_Guard
  :  Guardのアタッチポジションを表すVector3型定数(アタッチの際にアタッチポジションへの平行移動が必要となるのはGuardのみである)。





以上が、プログラムを作成するにあたって読者に与えられる情報のすべてである。
今までのプログラムでは、一体化するオブジェクトに関しては親子関係を表す階層構造についてもあらかじめ説明されていたが、この課題においては上記をもとにして親子関係も読者自らで設定しなければならない (扇風機の運動は広く知られたものであり、この程度の手掛かりでも開発者が実装する際の充分な'仕様書'となるはずである)。

プログラムはソースファイル Sec509.cs内のCode1に記述するものとする。そして、このプログラムの実装は行列ではなくUnity標準の変換方法、すなわち localPositionlocalRotation による方法で実装しなければならない。
Code1は最初の段階では空の初期化ブロックのみが用意されており、Code1に何も記述しないでそのまま実行すると、上図7の青い領域のみが表示される (各オブジェクトは初期状態のまま原点付近に置かれている)。

親子関係の設定について再度述べておこう。
5-4節で扱ったプログラムでは、プログラムの先頭部分に初期化ブロックが用意され、そこに親子関係が設定されたが、今回のプログラムも同様に初期化ブロックが用意されているのでそこで設定すればよい。
if(i_INITIALIZED){
    // 親子関係の設定        
    Obj5.transform.SetParent(Obj4.transform);
    Obj4.transform.SetParent(Obj3.transform);
    Obj3.transform.SetParent(Obj2.transform);
    Obj2.transform.SetParent(Obj1.transform);

    i_INITIALIZED = true;
}


この課題は今までに学習したことをよく理解していれば、数分で片付く仕事である。
ただし、学習者はプログラムを作成するだけで満足してはいけない。今回実装した扇風機なども、モデリングツールを使って、自身で一から作成し、各部品のデザイン、それぞれの初期状態の位置の設定、外部ファイルへの出力、Unityへのロード、そして Unityでロードしたそれらのオブジェクトを自身のプログラムで使えるようにするという一連の過程を経験することが何よりも重要である。
図18 Code1 実行結果
特に、初学者の場合はオブジェクトのモデリングは苦痛の種になるであろう。この講義で使われている単純形状のソリッドカラーのオブジェクトの作成でさえ、不慣れな者にとっては数分どころか数時間かけても終わらない仕事である。しかし、そういった経験が重要なのである。
読者はそこで、オブジェクトを動かすよりもオブジェクトを作るほうが遥かに時間がかかることを痛感するであろう。

(解答例については Sec509_Ans.txt内の Code1 を参照。なお、Sec509_Ans.txt内にはTHTransformクラスによる実装も含まれている。詳しくは同ファイル内 Code1_THTransform() を参照)














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