calc84maniacさんのRaycastingライブラリ(その1:ライブラリ紹介)
calc84maniacさんが公開している「Spooky Maze」、DOOMのような画面を実現している。
最近ではバグタローさんの「GIVERS-P3D」にも驚いたが、この「Spooky Maze」もすごい。
(後々はP3Dライブラリの解説もしてみたいです。バグタローさんご本人が解説されなければ、ですが。)
ソースを読むとレイキャストアルゴリズムを使っているとあり、ゲーム本体と分離されたレイキャストライブラリが付いていた。
これは割と使うのが簡単そうなので、ライブラリのコメントを元に使い方を書いてみたい(自分で作ってみるのはまた今度)。
▼レイキャストアルゴリズムについて
レイキャストアルゴリズムとは、3次元視点を実現するアルゴリズムの一つで、簡単に言うと、二次元のマップ上に自分がいてどちらかを向いているとすると、その視点の方向にマップを検索していって、壁などにぶつかると、それが自分が視線上に見えるものである、という考え方で画面を描写するものだ。
プチコンでのレイキャストアルゴリズムに基づいた3次元のゲームは、実は初代プチコンの時代から存在したようだが、現在確認できるのはmkII用のもので、プチコンまとめwikiに掲載されている。
RAYCAST MAZE
もしプチコンmkIIを持っているようであればぜひQRコードを読み込んで遊んでみよう。当時からかなり高速な3次元視点ゲームが存在していたことに驚くと思う。
この作者(Hepebellさん)は丁寧にもレイキャストアルゴリズムの考え方を図で書いてくれている。非常に参考になる。
アルゴリズム解説
calc84maniacさんの実装も、アルゴリズムとしてはこの考え方に基づいているようだ。
▼calc84maniacさんのRaycastingライブラリ
まずはcalc84maniacさん作の「Spooky Maze」をダウンロードしてみよう。
Spooky Maze (Raycaster)
そして遊んでみよう。どんなものかわかったろうか。
これと同じような画面を付属のライブラリを使うと簡単に作れるようなのだ。
Raycastingライブラリは、ダウンロードされたプロジェクトの中で「RAYCAST.LIB」という名前で保存されている。
(現状はバージョン0.1.00 Alphaと書かれている。)
ライブラリ冒頭部分のコメントの意訳を以下に載せる。
(筆者には大した英語力はないので、その正確性は期待しないで自分で読んだ方がよい。)
なお、以下で「環境」という言葉で表されているのは壁などの静的なブロックで構成されたマップを表し、「オブジェクト」は猫やスケルトンなどのマップ上に位置するその他の物を表す。
概要: このライブラリは、2次元のマップ配列、テクスチャー、光源の色、カメラ位置/アングルといった情報に基づいて、ブロックで区切られた3次元環境を描画するものだ。 また、この3次元世界にスプライトベースのオブジェクトを表示するユーティリティも含んでいる。 その環境自体もスプライトによって構成されており、それらの描画処理は単純なCALL SPRITE命令を必要とする。 描画速度と品質は、環境に配置されたスプライトの数に直接的に影響を受ける。
ライブラリの読み込み方: このライブラリはいずれかの好きなスロットにロードすることができる。 例えば、スロット1に置いて使いたい場合は次のようにする: LOAD "PRG1:RAYCAST.LIB",FALSE USE 1
環境を描画するには: ライブラリの準備のために以下の4つのステップを必要とする: 1. INITRAYLIB命令 この命令は、スプライト管理番号の開始番号と終了番号を引数としてレンダラーに設定する。 また表示するウィンドウの座標も引数とし、全てのスプライトが表示される範囲を限定する。 (訳注:SPCLIPの座標を指定するということ。) 2. SETTEX命令 この命令は、ブロックのテクスチャーがあるスプライトページ上のU,V,W,H座標を格納した2次元配列が引数となる。 配列は [テクスチャ数,4] で宣言する。 この配列においてブロック#1のテクスチャーは0番からとなる。なぜなら、マップ上での 0(ゼロ) は何もない場所を表しているからだ。 この配列の中身は、再度SETTEX命令を呼び出すことなく、修正してもよい。 3. SETMAP命令 この命令は、マップデータを格納した2次元配列と、配列の幅と高さを引数とする。 マップデータは行が先に来る形式であることに注意する。すなわち、1次元目が行(y座標)を表し、2次元目が列(x座標)を表す。 0以外の値はテクスチャー番号に対応するブロックを表す。0は何もない空間を表す。 マップは、その外側が見えないように、ブロックで囲われていなければならない。 この配列の中身は、再度SETMAP命令を呼び出すことなく、修正してもよい。 4. SETCAM命令 この命令は、カメラの(X,Y,Z)位置を引数とし、(X,Y)はマップ内の位置、Zは0〜1の範囲の値をとる。 さらに、視点の角度(ラジアン指定)、視点の上下移動量(ピクセル指定)、視野角(ラジアン指定)も引数とする。 カメラ位置を変更したフレームには、CALL SPRITEの前に、必ずこの命令を呼ぶ必要がある。 最後に、これらのステップが実行された後に、描画のために CALL SPRITE を実行する。 (訳注:各命令を再度呼び出すことなく配列の値を更新してもよい、と書かれているのは、各命令では配列の値をコピーしているのではなく、配列の参照をコピーしているから。)
スプライトベースオブジェクトを描画するには: ライブラリは、カメラ位置に基づいた3次元空間上に、スプライトを位置設定・縮尺設定・ライティングするコールバック命令を提供する。 スプライトの位置と縮尺を設定するには、SPOFSとSPSCALEを使う代わりに、SPVARでスプライト変数に設定する。 変数0,1,2はX,Y,Z位置をそれぞれ表し、変数3はスプライトの高さ(1.0がマップ上のブロックのいっぱいの高さである)を表す。 変数4〜7はあなたが自由に使える。 スプライトのホーム位置は設定済のものが使われることに注意すること。 2048番以降のスプライト定義は、センタリングされたホーム位置になっていることが多いので、それをそのまま使うか、あるいは自分でSPHOMEを使って設定すること。 もしライティング処理を書き換えたい場合には、色の設定はSPANIMを使うとよい。 管理番号がSP%であるスプライトにこの機能を適用するには、単に SPFUNC SP%,"SPWORLD" を実行すればよく、すると、CALL SPRITEが実行された時に自動的にスプライトの位置が再設定される。 もしあなたがすでに自作のスプライト関数(訳注:スプライト関数=SPFUNCでスプライトに割り当てた関数)を使っている場合には、その関数の中で直接SPWORLDを呼び出すようにしてもよい。
上級者向け機能/命令について: * SETLIGHT命令 この命令はRGB値を引数として、環境およびオブジェクトへカメラからの距離に応じた配色を行う。 SPCOLORの制限によって、#WHITEを引数として送ったとしても、元の色よりも明るくすることはできない。 * BEEP3DおよびSPBEEP3D命令 カメラとの相対位置に応じた効果音を発生する。 BEEP3D命令はX,Y,Z座標を引数とし、SPBEEP3D命令はスプライト座標を使用する。 その他に、BEEP命令における効果音番号、周波数、ボリュームを引数とする。 実際のボリュームとパンポットは、カメラから座標までの相対位置によって補正される。 * MAPCHK関数 この関数を使うと、out-of-boundsエラーを心配することなく、正方形領域とマップの衝突を簡単に検出できる。 関数名、3D位置、正方形領域の半径(訳注:正方形の一辺の長さの半分のことだと思われる)を渡すと、マップ配列、整数化されたX/Y、およびZを引数として、必要な回数だけ関数を呼び出してくれる。 このライブラリのサンプルに含む関数COLLIDEを使ってブロックとの衝突数をカウントするには、以下のように書く。 COUNT=MAPCHK("COLLIDE",X,Y,Z,R) (訳注:この説明を読むより、ライブラリのMAPCHKの箇所を読む方が何をやりたいかわかりやすいと思う) * SETVIEWCALLBACK関数 この命令は、マップ上のブロックが画面上に表示される度に呼び出したい命令名を引数として取る。 この命令はマップ配列とX,Y座標もまた引数として取る。 この命令を使うと、ミニマップを自動生成するのに便利である。 ただし、この命令はフレーム毎に何度も呼び出されるので、呼び出される命令においてブロックを処理するのに時間がかかるようであれば、すでに見たブロックは無視するようすることをおすすめする。 * RAYCAST命令 この命令はこのライブラリの中核となるものである。 X,Yおよび向きで与えられる位置から光線を発して、マップ上の非0であるブロックに当たるまで伸ばしていく。そして、衝突した点とブロック面の位置(左から右に向かって0〜1の値をとる)を返す。 このスライスの戻り値は、テクスチャーマッピング以外の用途ではあまり有効ではないので、通常の使い方においては無視して構わない。 * CANSEE関数 RAYCASTを使った有用な例。 この関数は、与えられた位置が、他のもう一つの位置から見えるかを、レイキャストに基づいて判定する。
実際に使ってみるのはまた次回以降です!