引き続き、本節も読者用の課題である。
4-16節では指定のキーが押された際に、レコードプレーヤーを構成する各オブジェクトを自動的に動かして、停止状態から再生状態へ、あるいはその逆に再生状態から停止状態へ変化させるプログラムを作成した。
本節で作成するプログラムもまた同じように、ある基準を設定し、その基準に達した際に一連のオブジェクトに決められた運動を行わせるものである。
以下の図1~図5は本節で使用するオブジェクトの初期状態である。図6はこれらのオブジェクトの階層構造である。
図1の Airplane は一番上の親オブジェクトであり、初期状態では x軸マイナス方向を向いている。図2、図3の UpperLeg、LowerLeg はAirplaneの車輪部分を構成するオブジェクトである。初期状態においては両方とも x軸に沿って置かれている。この2つのオブジェクトに対しては回転を行うが初期状態における回転軸はいずれも z軸である。
図4、図5の DoorA、DoorB は車輪を格納するための扉であり、この2つは初期状態において向きが反対なだけで形、大きさは同じである。DoorAが x軸プラス側に、DoorBが x軸マイナス側に置かれており、初期状態においてはDoorA、DoorBともにXZ平面の上側にある。また、DoorA、DoorBに対しても回転を行うが初期状態における回転軸はいずれも z軸である。
階層構造の図に示されるようにAirplaneの子オブジェクトとしてDoorA、DoorB、UpperLegがあり、さらにUpperLegの子オブジェクトとしてLowerLegがあるが、これらのオブジェクトは(Airplane以外は)いずれも3個ずつある。車輪部分を構成する UpperLeg、LowerLeg 及びそれらを格納する扉 DoorA、DoorB を合わせて1セットであり、それが3セットあるということである。
車輪部分は格納された状態と展開された状態の2つの状態がある。初期状態の各オブジェクトを親オブジェクトにアタッチすると(各オブジェクトを初期状態の位置からそれぞれのアタッチポジションまで平行移動させると)、図7に示される状態になるがこれは車輪部分が格納された状態である。
図8はAirplaneを下側から見たときのものであるが、3つの扉が閉まった状態になっている。この扉の内側に車輪が格納されているのである。
図9も同様にAirplaneを下側から見たときのものであり、図中の「A」「B」は格納扉 DoorA、DoorB を表している。これらは3セットあるが、いずれにおいてもDoorAが機体前方側に位置し、DoorBが機体後方側に置かれる。
車輪部分を展開された状態にするには、各オブジェクトを親オブジェクトへ アタッチする前に指定の角度だけの回転を実行する。各オブジェクトに対して行う回転は、具体的には以下のとおり。
DoorA : z軸周りに $-180^\circ$ の回転 (図10)
DoorB : z軸周りに $+180^\circ$ の回転 (図11)
UpperLeg : z軸周りに $+90^\circ$ の回転 (図12)
LowerLeg : z軸周りに $-180^\circ$ の回転 (図13)
(z軸周りに$+180^\circ$回転させても$-180^\circ$回転させても結果は同じであるが、この角度設定は車輪を展開する過程、あるいは格納する過程において意味を持ってくる)
以下の図は初期状態の各オブジェクトに対して上記の回転を実行したものである。
DoorA(図10)は x軸マイナス側、XZ平面の下側に移り、DoorB(図11)は x軸プラス側、XZ平面の下側に移る。UpperLeg(図12)は直立した状態(y軸に沿った状態)になる。
LowerLegの回転は z軸周りの $-180^\circ$ の回転であり、下図13は初期状態の LowerLeg に対してこの回転を実行したものである。展開された状態では車輪は下を向いて直立した状態になっていなければならないが、この図では初期状態から向きが逆になっただけで x軸に沿って置かれたままである。
しかし、LowerLegをUpperLegにアタッチした状態でこの回転を行うと、目的通りの直立した状態になる。図14はUpperLegに対して z軸周りの $90^\circ$ の回転が行われており、そのUpperLegに対してLowerLegをアタッチしたときのものである。この図におけるLowerLegにはアタッチポジションまでの平行移動だけが行われており回転は行われていない (LowerLegとUpperLegが折りたたまれた状態になっている)。
図15は同じUpperLegに対してLowerLegをアタッチしたときのものであるが、この図におけるLowerLegは z軸周りの $-180^\circ$ の回転を行ってからアタッチポジションまでの平行移動を実行したものである。
図に示されるように UpperLeg、LowerLeg が直立した状態になっている。
各オブジェクトに対して上記の回転を行い、それぞれのアタッチポジションまで平行移動を実行すると、下図16に示されるように車輪部分が展開された状態になる。図17のアニメーションは車輪部分が格納された状態から展開された状態へ(あるいはその逆へ)変化していく過程である。
一方からもう一方の状態への変化の過程では、実際には各オブジェクトに対して行われる回転の回転角度が変化しているに過ぎない。
# Code1
ここでの読者の課題はAirplaneの車輪部分を展開及び格納するプログラムを作成することであるが、その展開と格納は指定の状況において行われるようにしなければならない。
まず、その点について説明する。
今回のプログラムではAirplaneが自動的に決められた運動を行う。
以下はその流れである。
Airplaneの運動は滑走路を直進することから始まる (図18)。加速しながら進んでいき、一定の速度に達すると離陸が始まる(図19)。離陸後は一定時間上昇を行う(図20)。
一定の高さにまで達すると、指定の経路を旋回する (図21)。旋回飛行中に再び滑走路の上空に戻ってくる (図22)。その後、すぐに滑走路への下降が始まる (図23)。
一定時間下降した後、着陸し (図24)、しばらく滑走路を走って停止する (図25)。
上記のAirplaneの運動はいくつかの過程に分かれるが、その中に「離陸後の上昇」と「滑走路への下降」といった過程がある。車輪部分の展開及び格納は、この「離陸後の上昇」と「滑走路への下降」のそれぞれの過程において行われるようにしなければならない。
つまり、離陸後の上昇中に車輪部分を格納し、滑走路への下降中に車輪部分を展開するということである。
プログラムの作成はCode1に行うものとする。Code1は最初の段階では以下のコードが記述がされている。
[Code1]
if (!i_INITIALIZED)
{
// 0:開始地点 1:離陸前 2:着陸前
SetStartPosition(0);
i_INITIALIZED = true;
}
if (Input.GetKeyDown(KeyCode.S))
{
i_MOVE = !i_MOVE;
}
if (!i_MOVE) { return; }
Matrix4x4 localAirplane = GetAirplaneMatrix();
int state = GetAirplaneState();
if (state == c_ASCENT)
{
}
else if (state == c_DESCENT)
{
}
Airplaneの運動は Sキーを押すことによって始まる。9~14行目で使われている
bool型インスタンス変数
i_MOVE は初期値が
falseであり、プログラム実行後は Sキーを押さなければ14行目以降に処理が進まない。また、Airplaneの運動中に Sキーを押すと運動が一時停止する (再度キーを押せば再び運動が再開される)。
このプログラムでは3つの補助メソッドが用意されている。
初期化ブロック内の
SetStartPosition(int id) はAirplaneの運動をどこから始めるかを設定するためのメソッドである。引数に設定できる値は $0$、$1$、$2$ である (これ以外の値はすべて $0$ として扱われる)。$0$ をセットした場合はAirplaneの運動は一番最初の開始地点(滑走路のスタート地点)から始まり、$1$ をセットした場合は離陸前の段階から始まる。$2$ をセットした場合は着陸前の段階から始まる。
16行目の
GetAirplaneMatrix() は毎フレームにおけるAirplaneのローカル行列を取得するためのものである。上でも述べたが、今回のプログラムではAirplaneは自動的に運動を行うだけであり、プログラムからAirplaneを操作することはできない。言い換えれば、Airplaneに対してはどのような変換も実行することはできない (Airplaneという名前の変数も用意されていない)。
Airplaneと一体化した運動を行うためにはAirplaneのローカル行列があればよいので、そのための補助的なメソッドである (Airplaneは一番上の親オブジェクトであるため、Airplaneのローカル行列はワールド行列でもある)。
18行目の
GetAirplaneState() はAirplaneが上昇中か下降中か、あるいはそのいずれでもないかを調べるためのものである。
Airplaneが離陸し、上昇を開始してから少し経つと、このメソッドはしばらくの間
c_ASCENT という定数を返すようになる。
c_ASCENT はAirplaneが上昇中であることを意味するもので、この定数が返される時間は 800フレームである。つまり、Airplaneの上昇中において
GetAirplaneState() は800フレームの間
c_ASCENT という定数を返す。車輪部分の格納はこの800フレームの間に行われるようにしなければならないということである。上記のプログラムでは20行目に
c_ASCENT であるかどうかの
ifブロックがあるが、この
ifブロックは格納処理のためのものである。
Airplaneが滑走路への下降を開始してから少し経つと、このメソッドはしばらくの間
c_DESCENT という定数を返すようになる。
c_DESCENT はAirplaneが下降中であることを意味するもので、この定数が返される時間も 800フレームである。つまり、Airplaneの下降中において
GetAirplaneState() は800フレームの間
c_DESCENT という定数を返すが、車輪部分の展開はこの800フレームの間に行われるようにしなければならないということである。上記のプログラムでは24行目に
c_DESCENT であるかどうかの
else ifブロックがあるが、この
else ifブロックは展開処理のためのものである。
上昇中でも下降中でもないそれ以外の状況では、このメソッドは上記の2つの定数とは異なる値 $-1$ を返すだけである (Airplaneの運動中のほとんどの時間は $-1$ が返される)。
今回のプログラムで用意されているインスタンス変数及び定数は以下のとおり。
i_degUpperLeg
: UpperLegの回転角度を表す
float型インスタンス変数。
i_degLowerLeg
: LowerLegの回転角度を表す
float型インスタンス変数。
i_degDoorA
: DoorAの回転角度を表す
float型インスタンス変数。
i_degDoorB
: DoorBの回転角度を表す
float型インスタンス変数。
c_attachPos_UpperLeg[]
: 3つのUpperLegのそれぞれのアタッチポジションを表す
Vector3型の定数配列。
c_attachPos_LowerLeg
: LowerLegのアタッチポジションを表す
Vector3型の定数。
c_attachPos_DoorA[]
: 3つのDoorAのそれぞれのアタッチポジションを表す
Vector3型の定数配列。
c_attachPos_DoorB[]
: 3つのDoorBのそれぞれのアタッチポジションを表す
Vector3型の定数配列。
UpperLegは3つあるが、状態変化の過程では3つのUpperLegはすべて同じタイミングで変化する。したがって、3つのUpperLegに実行される回転の回転角度は常に同じ値であり、3つのUpperLegのそれぞれに回転角度用の変数を用意する必要はなく、1つだけを用意すればよい。
i_degUpperLeg はUpperLegの回転角度を表すインスタンス変数であるが、これは3つのUpperLegで共通である。
i_degLowerLeg、
i_degDoorA、
i_degDoorB も同様で、これらは3つのLowerLeg、3つのDoorA、3つのDoorBで共通である。
3つのUpperLegはそれぞれアタッチポジションが異なるので各アタッチポジションは
c_attachPos_UpperLeg[i] にセットされている。これは、DoorA、DoorBも同じである。しかし、3つのLowerLegはアタッチポジションが同じであるので、1つの定数として用意されている。
プログラム中で使用するオブジェクトは4種類であるが、すべて要素数 3 の配列である。すなわち、
UpperLeg[i]、
LowerLeg[i]、
DoorA[i]、
DoorB[i] の4種類である ($i = 0, 1, 2$)。
上でも述べたが、車輪部分の展開された状態と格納された状態の違いは、各オブジェクトに実行する回転角度の違いに過ぎない。格納された状態では UpperLeg、LowerLeg、DoorA、DoorB に実行する回転の回転角度は $0^\circ$ である。展開された状態では回転角度はそれぞれ異なり、UpperLegが $+90^\circ$ の回転、LowerLegが $-180^\circ$ の回転、DoorAが $-180^\circ$ の回転、DoorBが $+180^\circ$ の回転であり、いずれも回転軸は z軸である。
右のアニメーションは展開された状態と格納された状態との間を行き来するものであるが、格納された状態では各オブジェクトの回転角度は $0^\circ$ であり、展開された状態では各オブジェクトの回転角度はそれぞれの上限値あるいは下限値である。
格納された状態から展開された状態にするには、回転角度を $0^\circ$ からそれぞれの上限値あるいは下限値まで変化させればよいだけである。例えば、UpperLegであれば $0^\circ$ から $+90^\circ$ へ、DoorAであれば $0^\circ$ から $-180^\circ$ へ毎フレーム 少しずつ変化させていけばよい。展開された状態から格納された状態にするにはその逆である。回転角度をそれぞれの上限値あるいは下限値から $0^\circ$ まで変化させればよい。
また、展開された状態における各オブジェクトの回転角度には $\pm$ の符号がついているが、これは展開する際に毎フレーム回転角度を増加させるのか減少させるのかを意味している。例えば、展開する際にはDoorAであれば $-180^\circ$ まで毎フレーム 減少させ、DoorBであれば $+180^\circ$ まで毎フレーム 増加させるようにするということである。
(プログラムの解答例については Sec418_Ans.txt を参照)