TOP > Programming > おじょーのプログラミング研究誌 > 第2回 疑似3D迷路に関する技術解説
疑似3D迷路に関する技術解説
3D迷路をご存知でしょうか。 古くはWizardlyやファンタシースター,近年では世界樹の迷宮やLegend of Grimrockなんかで登場します。
(前者の歴史上のゲームについては私おじょーもやったことがないです)
もしくは,昔のWindowsに標準でついていたあのスクリーンセーバーのほうが記憶にある方もいるかもしれませんね。


図1:こんなの

今回の趣旨としては,こんな感じの3D迷路の画面を,3D機能を使わずに疑似3Dで作ってしまおう!ということになります。
この技術解説は,以下のような方にお勧めです。
それではさっそく解説に移りましょう。
実装1
とりあえず3Dで表示してみたいだけの方は,このコード→[リンク]でできます。お疲れさまでした。
※実装はC言語で,「DXライブラリ→[リンク]」を使用しています。
※実行には「block.png→[リンク]」が必要です。こちらも合わせてお使いください。
解説1:物体はカメラからの距離に反比例して表示される
これだけだとさすがに何をやっているかわからないと思うので,解説します。


図2:箱,自分の位置

まず,上の図のように箱がおかれているとします。
「↑」が自分のいる位置だと思ってください

自分がいる位置を,(sgt_x,sgt_z)=(0,0)と定義しました。
右手側をsgt_x=プラス,左手側をsgt_x=マイナス,また奥にいくにつれsgt_zの値が増えていきます。
問題はこの(sgt_x,sgt_z)におかれた箱を,画面上のどこにどれだけの大きさで表示するか,となります。

最も基本的な考え方は,奥にある物体ほど小さく表示されるということになります。簡単ですね。
もう少し数学的ないい方をすると,物体の描かれるサイズは,カメラ奥行方向の長さ[sgt_z]に反比例するとなります。


図3:カメラおよび面

ここで,自分の真後ろにカメラを置きます。(亀ではありません,カメラです)
カメラはsgt_z=-1の位置にいると仮定します。
自分とカメラの間にある以下の面を表示することを考えます。(線にしか見えませんが,面です)
カメラからこの面までの距離を1として,画面いっぱい640×480のサイズで表示することにしましょう。


図4:面が少し奥に行った

では,図4の面はどれくらいのサイズで描かれるでしょうか。
カメラから面までの距離は2ですので,640,480をそれぞれ2で割って,320×240のサイズで表示することになります。


図5:さらに奥の面

こちらはどうなるでしょう。
もうお分かりですね。
640,480をそれぞれ4で割って,160×120のサイズで表示することになります。

さきほどのコード→[リンク]に戻ります。
void DrawWall1(int sgt_x, int sgt_z)関数にこんな記述がありますね。
int w1 = 640 / (sgt_z + 1);		//手前側の面の幅を決めます
int h1 = 480 / (sgt_z + 1);		//手前側の面の高さを決めます
こちらで,今解説したとおり,「面の表示サイズ」を計算してやっているわけです。
解説2:面の表示位置を決める
解説1で,面の表示サイズがわかるようになりました。
続いて,面の表示位置を決めていきます。


図6:自分の正面に位置する面

まず,sgt_x=0の位置にある面の表示位置から考えていきます。
これらは自分から向かって正面の位置にあるので,画面中央に表示されることが想像できますね。
w×hのサイズの長方形を,640×480のサイズの画面に中央ぞろえで表示できればいいわけです。


図7:中央ぞろえで表示の考え方

まず横方向からいきましょう。w=320とします。
上の図のように,中央からそれぞれ160ずつ左右に広げれば320になりますね。
画面中央の位置は,640/2=320なので,左端は320-160=160,右端は320+160=480となります。
縦方向に関しても考え方は全く一緒です。

より一般的な書き方をすると,以下のようになります。
左端のX座標=320-w/2=(640-w)/2
右端のX座標=320+w/2=(640+w)/2
上端のY座標=240-h/2=(480-h)/2
下端のY座標=240+h/2=(480+h)/2


図8:sgt_x=-1に位置する面

続いて,sgt_x=-1に位置する面はどうでしょうか。
先ほどの中央の面から左にwだけずらして表示すればいいことになります。
すなわち,
左端のX座標=(640-w)/2-w
右端のX座標=(640+w)/2-w

続いて,sgt_x=1に位置する面の場合,右にwだけずらして表示すればいいことになります。
すなわち,
左端のX座標=(640-w)/2+w
右端のX座標=(640+w)/2+w

sgt_xを用いて,
左端のX座標=(640-w)/2+sgt_x*w
右端のX座標=(640+w)/2+sgt_x*w

さきほどのコード→[リンク]を見てください。
ここまで説明した上下左右X,Y座標を用いて,面の左上,左下,右上,右下の角の座標を指定しています。
//------手前側の面の左上,左下,右上,右下の角の座標------
scr[0] = setVector((640 - w1) / 2 + sgt_x * w1, (480 - h1) / 2);
scr[1] = setVector((640 - w1) / 2 + sgt_x * w1, (480 + h1) / 2);
scr[2] = setVector((640 + w1) / 2 + sgt_x * w1, (480 - h1) / 2);
scr[3] = setVector((640 + w1) / 2 + sgt_x * w1, (480 + h1) / 2);
//------奥側の面の左上,左下,右上,右下の角の座標------
scr[4] = setVector((640 - w2) / 2 + sgt_x * w2, (480 - h2) / 2);
scr[5] = setVector((640 - w2) / 2 + sgt_x * w2, (480 + h2) / 2);
scr[6] = setVector((640 + w2) / 2 + sgt_x * w2, (480 - h2) / 2);
scr[7] = setVector((640 + w2) / 2 + sgt_x * w2, (480 + h2) / 2);
これらの点は,以下の図の通り,箱のそれぞれの角に対応しています。


図9:scrと箱の対応

解説3:画面に表示する
それではいよいよ,実際の描画を行っていきましょう。


図10:手前,奥の面

まず,箱の手前,奥に位置する面です。
こちらは簡単ですね。
手前:scr[0],scr[1],scr[2],scr[3]
奥:scr[4],scr[5],scr[6],scr[7]
の範囲内に描画すればいいことになります。


図11:側面

続いて側面ですが,図9とも照らし合わせながら,
左側面:scr[0],scr[1],scr[4],scr[5]
右側面:scr[2],scr[3],scr[6],scr[7]
の範囲内に描画すればいいことになります。

コード→[リンク]のこの部分で実際の描画を行っています。
//------以下で箱を表示します------
DrawModiGraph(scr[4].x, scr[4].y, scr[6].x, scr[6].y, scr[7].x, scr[7].y, scr[5].x, scr[5].y, block, true);
DrawModiGraph(scr[0].x, scr[0].y, scr[4].x, scr[4].y, scr[5].x, scr[5].y, scr[1].x, scr[1].y, block, true);
DrawModiGraph(scr[2].x, scr[2].y, scr[6].x, scr[6].y, scr[7].x, scr[7].y, scr[3].x, scr[3].y, block, true);
DrawModiGraph(scr[0].x, scr[0].y, scr[2].x, scr[2].y, scr[3].x, scr[3].y, scr[1].x, scr[1].y, block, true);
おわりに
説明が長くなりましたが,ここまでの技術解説で,疑似的な3D表示はできるようになったと思います。お疲れさまでした。
ですが,まだブロックの位置を直接指定して表示しているだけなので,例えば迷路の中を歩き回ることはまだできません。
次回(気が向いたら),迷路の中を歩き回れるようになるまでを解説していきます。ご期待ください。

TOP > Programming > おじょーのプログラミング研究誌 > 第2回 第2回 疑似3D迷路に関する技術解説