プチコン3号 29日目 擬似3D(6) 迷路の中を動く敵を隠面消去つきで描画する

pc29-1.jpg

前回は隠面消去のアルゴリズムを使って迷路の壁を表示しました。
しかし、表示できるものが壁だけでは寂しいですね。
今回は前回のプログラムを拡張して、3D迷路の中に敵を表示しました。
これで、11日目の迷路探検ゲームの3D版ができます。

今回のプログラムは長いので、全体は掲載しません。
動かしてみたい方はMiiverseからダウンロードしてください。
公開キーは【BKEYX39J】です。
操作はスライドパッドで移動、L/Rで方向転換です。
敵とぶつかると目が回ってしまいます。
ゴールの黄色い壁を目指して進んでください。

この記事では、3D迷路の中に敵を表示する場合の描画処理方法について解説します。
その前にまず、迷路の中をカメラが移動する場合の処理について、簡単に解説しておきます。
迷路の中を移動する場合の処理については、7日目に記事を書きました。

プチコン3号 7日目 ドラクエ風に迷路の中を歩き回る: 楽しくやろう。

今回のプログラムでも、基本的な処理は全く同じです。
ただ、迷路の部屋やマップに割り当てるピクセル数が違います。

7日目の記事では、迷路が32×32ピクセルの部屋をつなぎ合わせたもので、その中を16×16ピクセルのスプライトを動かしていました。
今回のプログラムはスプライトは使いませんが、その代わりに3DSのスクリーンが迷路の中を向きを変えながら動くと考えます。
スクリーンの幅は400ピクセルですので、スクリーンの向きが変わることも考慮すると、400×400ピクセルのオブジェクトが迷路の中を動き回っていることになります。

従って、迷路の通路の幅は少なくとも400ピクセル以上必要です。
また、カメラの座標はスクリーンの中心だとすると、その座標は迷路の壁から少なくとも200ピクセルは離さないと、下図に示すようにスクリーンに壁の向こう側が映ってしまいます。

pc29-2.jpg

こういったことを考慮して、今回のプログラムでは、大きさに関する数値を7日目のプログラムの16倍にしました。
下図のように、迷路の1部屋は1024×1024ピクセルになります。
カメラの中心点は常に壁から256ピクセル以上離れており、スクリーンに壁の向こうが写ることはありません。

pc29-3.jpg

処理内容自体は7日目と大きく変わるところはなく、1部屋がマップで4×4マスになる点も同じです。
ただし、スプライトでは座標値はスプライトの左上の点の座標値でしたが、今回のプログラムではカメラや敵の座標は中心の点の座標値になっています。

それでは、本題の敵の表示の話に戻ります。

グラフィックスで敵を描画する際、壁の向こうに居る敵は、壁で隠さなければなりません。

前回は壁だけの描画でしたので、遠くの壁を先に、近くの壁を後に描画することで隠面消去を実現していました。
敵の描画を行う場合も基本は同じです。
敵よりも遠い壁を描いた後に、敵を描き、敵より近い壁はその後に描いていくことになります。

このためには、敵と壁を区別することなく、Z座標の大きい順に並べ替える必要があります。

前回のプログラムでは、壁の遠近の判定において、壁の中心のZ座標で壁全体を表すことで計算を簡略化しました。
簡略化の条件として、迷路の壁が「碁盤目状に配置されている」という条件がありました。
これはより詳しく言うと、壁同士が交差しない(壁の前後関係が途中で変化しない)こと、壁の大きさが全て同一であること、という条件になります。

pc29-6.gif

敵と壁を同列に扱うために、敵のZ値も、敵の中心のZ座標で敵全体を現すことにします。

敵や壁のZ値を、中心の座標だけで管理するということは、ポリゴンの向きによらず中心の座標だけで、敵と壁の前後関係が判断できなければなりません。

次の図のような状態の、敵とその手前の壁について考えます。
これは迷路を上から見た状態で、敵の形状は円柱で近似しています。
Z軸は下から上、つまり下にあるものほど手前にあるものとします。

このとき、壁が手前になるためには「壁の中央のZ値」が「敵の中心のZ値」よりも常に小さくなければなりません。
ところが実際には、敵の位置によっては壁が敵よりも遠くにあると判定されてしまいます。

pc29-4.jpg

図では青の線が壁のZ値なので、緑の円柱は壁の向こうですが、赤の円柱は壁の手前にあるという判定になります。
その結果、図右下のように、間違った前後関係で描画されてしまいます。
壁の手前半分のZ値は、壁の中央のZ値よりも実際には小さい(手前にある)のに、それが考慮されていないためにこのようなことが起こります。

これを回避するには、敵のZ値が壁のZ値より小さい時に、上の図で黄色で示したエリアに敵のポリゴンが重ならないようにします。
黄色の領域は前後関係の判定に用いる壁のZ値よりも実際のZ値のほうが小さいために、遠近が正しく判定できない領域だからです。

この条件を満たすには敵のポリゴンの「位置」と「大きさ」の制限が必要です。

pc29-5.jpg

上の図にあるように、

・敵の中心が通路の中央であること
・敵の幅が通路の幅の1/2未満であること

の2つを両方満たしている場合(緑の円)、黄色の領域と重なることはありません。
一方、赤の円柱は2番目の条件よりもサイズが大きいため、間違った前後関係で描画されてしまいます。

というわけで、だいぶ長い解説になってしまいましたが、前回のプログラムに敵の描画を追加したプログラムを以下に示します。
まずはZバッファの処理の部分です。

FOR I=0 TO 399+NUM_E+1
ZBUF[I]=POW(2,31)
W_VISIBLE[I]=-9999
NEXT

FOR I=0 TO LEN(W_BGN)-1
(略)
NEXT

FOR I=0 TO NUM_E
ZBUF[I+400]=E_RW[I]
W_VISIBLE[I+400]=-1-I
NEXT

敵のデータは以下のようになっています。

 ・NUM_E ・・・敵の数-1
 ・E_RW ・・・敵のZ座標値(スクリーン中央を原点とした座標系に変換済み)

ZBUFとW_VISIBLEのサイズは、本来はスクリーンの横方向のピクセル数に等しいのですが、これを敵の数の分だけ大きくしています。
そして、敵については、壁で隠されているか否かに関わらず、すべて「見えている」(ただし壁は隠していない)ことにして、ZBUFとW_VISIBLEに追加しています。

W_VISIBLEには「壁の番号」を記録しますので、敵0が-1、敵1が-2、敵2が-3、、、というように、敵は負の数で表して壁と区別が付くようにしています。

続いて描画処理の部分です。

GCLS

RSORT ZBUF,W_VISIBLE
W_LAST=-9999
FOR I=0 TO LEN(W_VISIBLE)-1
IF W_VISIBLE[I]==W_LAST THEN CONTINUE
W_LAST=W_VISIBLE[I]
IF W_LAST==-9999 THEN CONTINUE
IF W_LAST<0 THEN DRAW_ENEMY W_LAST:CONTINUE
P1=W_BGN[W_LAST]
P2=W_END[W_LAST]
L[0]=R_X[P1]:L[1]=R_Z[P1]
L[2]=R_X[P2]:L[3]=R_Z[P2]
IF CLIPLINE(L,X_MIN,Z_MIN,X_MAX,Z_MAX) THEN
(以下略)

RSORTによって、壁も敵も区別なく、Z値によってソートされます。
そして、スクリーンから遠い壁または敵から順に描画していきます。
見て分かるように、W_VISIBLE[I]が負の数だったら、敵の描画を行うユーザ手続きを呼び出しているだけです。

このDRAW_ENEMY関数は、中身は省略しますが、下図のように上から見ると「半径256ピクセルの円の内部」に納まるように敵を描画しています。

また、上で述べた条件を満たすように、敵は通路の中央、図の青い線の上を移動します。

pc29-7.jpg

最後に、敵との当たり判定です。
これは2点間の距離の公式

 距離=√(dx^2 + dy^2)

を使います。dxはX座標の差分、dyはY座標の差分です。

FOR I=0 TO NUM_E
D=(E_U[I]-U)*(E_U[I]-U)+(E_W[I]-W)*(E_W[I]-W)
IF D < E_R*E_R THEN
SPANIM 0,"C",1,&H00000000,-60,&HFFFF0000,-45,&H00000000,-45,1
BEEP 107,-600,96
ATTACKED=TRUE
BREAK
ENDIF
NEXT

IF ATTACKED THEN
T=T+.3
IF SPCHK(0) == 0 THEN ATTACKED=FALSE
ENDIF

変数は、

 プレイヤー: X座標 U, Z座標 W, 向き T
 敵(配列): X座標 E_U, Z座標 E_W, 半径 E_R

です。
距離の2乗を計算し(変数D)、半径の2乗より小さければ、敵とぶつかったと判定して変数ATTACKEDをTRUEにします。

スプライト0はスクリーン全体を覆う白地の図形で、通常は透明にしています。
ぶつかった場合には、このスプライトを赤い色にフェードイン/フェードアウトさせます。

フェードイン・フェードアウトのアニメーションを実行中は、プレイヤーの向きTを変化させて視点を強制的に回転させています。

以上で3D版 迷路探検ゲームの解説は終わりにします。
プログラムを作るより解説を書くほうが大変でした・・・。

コメント