ホーム
チュートリアル
第一回
第二回
第三回
第四回
第五回
第六回
     
チュートリアル番外編
第一回
第二回
第三回
第四回
拡張ソース
TrueType表示クラス
(チュートリアル第六回参照)
掲示板
メール
(-nospamを外してください)

チュートリアル

第三回 マップを出して似非FPS

前回、md2形式のモデルを表示するまでを行いましたが、今回はこれにBSPマップを表示し、Quake風のカメラ移動を取り入れて、FPSっぽくマップの中を歩き回れるようにしたいと思います。

BSPマップって何?

BSPマップとは、Quake、HalfLife等のFPS系シューティングで主に使用される3Dマップのフォーマットで、もともと視界外のモデル表示を省略するBSPというアルゴリズムが使用されていたことからBSPマップと呼ばれています。
一口にBSPマップと言っても、実際にはゲームの種類によってフォーマットがそれぞれ異なり、IrrlichtではQuake3形式と呼ばれるマップ形式に対応しています。

BSPマップを作成するには通常のモデリングソフトではなく、専用のマップ作成ツールを使って作成するのが一般的です。実際のマップの作成に関しては、番外編という形で別途解説しています。

マップNodeを追加する

それでは、前回のソースを元に、マップを表示するようにしてみましょう。
新しくプロジェクトを作り、前回使ったmain.cppのコピーをプロジェクトに追加してください。

マップデータを用意する

まずはマップデータを用意しなければなりません。
各自マップツールを使って自分で作りましょう。というのもなんですから、ここに用意しておきましたのでダウンロードして、プロジェクトのディレクトリに置いてください。
二部屋しかない単純なマップですが、テストには十分でしょう。
ダウンロードしてもらったマップデータは、pk3形式で圧縮されています。pk3形式は単なるzipファイルですので、zip対応の解凍ツールで解凍できますが、ここでは解凍せずにそのまま置いてください。

アーカイブからファイル取り出し

Irrlichtは、zip(pk3)形式で圧縮された書庫ファイルから、解凍せずにファイルをロードすることが可能です。

main.cppに以下のように追加してください。

        ISceneManager *Scene = Device->getSceneManager();

        Device->getFileSystem()->addZipFileArchive("simplemap.pk3");

        Scene->addCameraSceneNode(0, vector3df(0,10,-40), vector3df(0,10,0));

addZipFileArchive()関数を使って、Irrlichtのファイルシステムにzip形式の書庫ファイルを追加すると、ファイルをロードする際に、追加した書庫ファイル内を検索するようになります。

io::IFileSystem * irr::IrrlichtDevice::getFileSystem ()
IrrlichtDeviceが内包するIFileSystemオブジェクトを取得し、そのポインタを返します。
bool io::IFileSystem::addZipFileArchive (
    const c8 *                          filename,
    bool                                ignorecase = true
    bool                                ignorepath = true
)
ファイルシステムのファイル探索経路にzip書庫ファイルを追加します。成功するとtrueが、ファイルが見つからない等の理由で失敗するとfalseを返します。
c8 * filename 追加するzip書庫のファイル名を指定します。
bool ignorecase この値がtrueだと、書庫内のファイル名の大文字小文字を区別しません。
bool ignorepath この値がtrueだと、ファイル名の取り出しに完全なパスを指定する必要がありません。

マップNodeを作る

続いて、マップファイルを読み込んで、ISceneManagerにNodeとして追加しましょう。

        ISceneManager *Scene = Device->getSceneManager();

    Device->getFileSystem()->addZipFileArchive("simplemap.pk3");

    IAnimatedMesh *MapMesh = Scene->getMesh("simplemap.bsp");
    ISceneNode *MapNode = Scene->addOctTreeSceneNode(MapMesh);
    MapNode->setMaterialFlag(EMF_LIGHTING, false);

    Scene->addCameraSceneNode(0, vector3df(0,10,-40), vector3df(0,10,0));

simplemap.pk3の中にsimplemap.bspというマップデータがありますので、それをgetMesh()関数でロードして、IAnimatedMeshオブジェクトを作ります。.bsp形式のファイルを読み込むと、内部で使用しているテクスチャファイルも自動的に読み込まれます。

続いて、今読み込んだIAnimatedMeshをISceneManagerに登録するのですが、マップを登録する際には通常のモデルとは違ったNodeとして登録します。そのため、前回使用したaddAnimatedMeshSceneNode()ではなく、addOctTreeSceneNode()関数を使用します。
この関数を使用することにより、登録されたNodeは、視界に入らないNodeはOctTreeと呼ばれるアルゴリズムによって描画を省略され、処理が軽くなるのです。
とりあえずは、
BSPマップを登録する場合はaddOctTreeSceneNodeで、
それ以外のモデルはaddAnimatedMeshSceneNodeで登録する。
と覚えておいてください。

ISceneNode * irr::scene::ISceneManager::addOcttreeSceneNode (
    IAnimatedMesh *                     mesh,
    ISceneNode *                        parent = 0,
    s32                                 id = -1,
    s32                                 minimalPolysPerNode = 128
)
ISceneManagerにIAnimatedMeshをOctTree対応の新しいNodeとして追加します。
OctTreeを内部で構築することで、視界外にあるポリゴンの描画を省略でき、巨大なモデルを高速に表示することができます。
画面に入りきらないような巨大なポリゴン(マップとか)をNode登録するのに適しています。
IAnimatedMesh * mesh Nodeとして設定するIAnimatedMeshを指定します。
ISceneNode *parent このNodeの「親」となるNodeを指定します。特に指定しない場合は0とします。
「親」として設定されたNodeが移動するとそれにあわせてこのNodeも移動します。
マップは親Nodeを持たない場合も多いはずなので、この場合0でよいでしょう。
s32 id コリジョン判定の時使いますので、詳しくはその時説明します。今はデフォルトでOKです。
s32 minimalPolysPerNode 子OctTreeノードを生成する最小ポリゴン数を指定します。
OctTreeノード中に設定値以上のポリゴンが入ると、OctTreeノードの分割処理を行います。

続いて、今回のプログラムでは光源を登録していないので、setMaterialFlag()関数で光源処理をOFFにしておきます。これは前回説明したので詳細は省略します。

マップ位置の調整

とりあえずこれでマップの表示はできるはずですので、一度実行してみましょう。

めでたくマップは表示されましたが、Sydney姐さんが腰まで埋まってて気持ち悪いですね。視点も低いので妙な感じです。マップの位置を下げましょう。以下のコードを追加してください。

    Device->getFileSystem()->addZipFileArchive("simplemap.pk3");

    IAnimatedMesh *MapMesh = Scene->getMesh("simplemap.bsp");
    ISceneNode *MapNode = Scene->addOctTreeSceneNode(MapMesh);
    MapNode->setMaterialFlag(EMF_LIGHTING, false);

    MapNode->setPosition(vector3df(0,-24,0));

    Scene->addCameraSceneNode(0, vector3df(0,10,-40), vector3df(0,0,0));

setPosition()関数はNodeの位置を設定します。この関数には以後たくさんお世話になると思います。

void irr::scene::ISceneNode::setPosition (
    const core::vector3df &             newpos
)
ISceneNodeの位置を変更します。
vector3df newpos 新しい位置をベクトル形式で指定します。

再度実行して、マップの位置が適切になったかどうか確認してください。

FPSっぽくグリグリと

FPS風カメラ

めでたくマップも出たことですし、マップの中をグリグリと動けるようにしましょう。

Irrlichtには、FPS系のゲームを実現するため、Quake風の入力に対応したカメラがあらかじめ用意されていますから、今回はそれを使用することにしましょう。

main.cppの中のこの部分を

    MapNode->setPosition(vector3df(0,-24,0));

    Scene->addCameraSceneNode(0, vector3df(0,10,-40), vector3df(0,0,0));

    IAnimatedMesh *SydneyMesh = Scene->getMesh("sydney.md2");

以下のように書き換えてください。

    MapNode->setPosition(vector3df(0,-24,0));

    ICameraSceneNode *Camera = Scene->addCameraSceneNodeFPS();
    Camera->setPosition(vector3df(0,10,-40));
    Device->getCursorControl()->setVisible(false);

    IAnimatedMesh *SydneyMesh = Scene->getMesh("sydney.md2");

addCameraSceneNodeFPS()関数を使ってカメラNodeを追加すると、以後Quake風の操作でカメラが動かせるようになります。↑で前進、↓で後退、←→で左右平行移動(カニ歩き)、マウスでカメラ方向の移動です。
マウスポインタが動かなくなるので、ALT+F4でプログラムを終了してください。

ICameraSceneNode irr::scene::ISceneManager::addCameraSceneNodeFPS (
    ISceneNode *                      parent = 0,
    f32                               rotateSpeed = 100.0f,
    f32                               moveSpeed = 500.0f,
    s32                               id = -1,
    SKEYMAP *                         KeyMapArray = 0,
    s32                               KeyMapSize = 0
)
ISceneManagerにQuake風の入力に対応したFPS風カメラNodeを登録します。
ISceneNode parent このカメラNodeの「親」となるNodeを指定します。特に指定しない場合は0とします。
「親」として設定されたNodeが移動するとそれにあわせてこのNodeも移動します。
カメラNodeの場合、普通は0でよいでしょう。
f32 rotateSpeed マウスによるカメラ方向の移動速度を指定します。
f32 moveSpeed キーによるカメラ位置の移動速度を指定します。
s32 id このノードを識別するための番号が必要であれば、ここで指定できます。
内部で特に使われているわけではないので、使用者が任意に使用することができます。
SKEYMAP *KeyMapArray 移動に使われるキー構成をカスタマイズしたい場合、そのカスタマイズ設定を格納したSKEYMAP構造体配列のポインタを指定します。これについては次回以降で解説したいと思いますので、当面は0でよいでしょう。
s32 KeyMapSize KeyMapArrayを設定した場合、その要素数を指定します。当面は0でよいでしょう。

FPSカメラが作られた直後は、カメラ位置が(0,0,0)になっていますので、setPosition()関数を使って前回のカメラ位置のあたりに持ってきましょう。

マウスポインタがどうせ動かなくなるなら、マウスポインタを消してしまいましょう。
getCursorControl()->setVisible()関数でマウスポインタの表示を制御できます。

gui::ICursorControl * irr::IrrlichtDevice::getCursorControl ()
IrrlichtDeviceが内包するICursorControlオブジェクトを取得し、そのポインタを返します。
void irr::gui::ICursorControl::setVisible (
    bool                             visible
)
マウスカーソルの表示、非表示を制御します。
bool visible trueで表示、falseで非表示になります。

それでは実行してみてください、カメラが動かせるはずです。

とはいえ、まだ当たり判定を取っていませんので、カメラが壁を突き抜けてずんずん進んでしまいますね。
当たり判定については次回以降で解説したいと思います。

FPSっぽいところで、FPSを表示してみる

あれこれ機能も載ってきたところですし、FPS(Frame Per Second)でも表示してみましょう。
描画ループ近辺に、以下のコードを追加してください。

    SydneyNode->setMD2Animation(EMAT_RUN);

    int lastFPS = -1;
    int lastPrims = -1;

    while(Device->run())
    {
        Driver->beginScene(true, true, SColor(0,100,100,160));
        Scene->drawAll();
        Driver->endScene();

        int fps = Driver->getFPS();
        int prims = Driver->getPrimitiveCountDrawn();
        if (lastFPS != fps || lastPrims != prims)
        {
            wchar_t tmp[1024];
            swprintf(tmp, 1024, L"Irrlicht A GoGo - Tutorial (fps:%d) Triangles:%d", fps, prims);
            Device->setWindowCaption(tmp);
            lastFPS = fps;
            lastPrims = prims;
        }

    }

getFPS()関数でFPSを、getPrimitiveCountDrawn()関数で描画した三角形の数を取得しています。
その後、それを文字列化しますが、ここで、
Irrlicht中で扱う文字列は全てワイド文字である
ということに注意してください。だからここではswprintf関数を使用しています。
そして、得られたワイド文字列をsetWindowCaptionでウィンドウタイトルに設定します。

s32 irr::Video::IVideoDriver::getFPS ()
現在のFPS値を返します。
s32 irr::Video::IVideoDriver::getPrimitiveCountDrawn ()
先の描画処理で、どれだけの数のプリミティブ(三角形)が描画されたのかを返します。
void irr::IrrlichtDevice::setWindowCaption (
    wchar_t *                           text
)
ウィンドウタイトルを設定します。
wchar_t * text 新しいウィンドウタイトルをワイド文字列で指定します。

これでFPSが表示されるはずです。モデルやマップを画面外に追い出すなりして、fpsとTrianglesがどのように変化するか見てください。このマップは小さすぎてOctTreeの効果がわかりにくいかもしれませんので、Irrlichtのサンプルに付属のマップファイルに差し替えてみるのもいいでしょう。

ここまでのソースをこちらに置いておきます。結構長くなってきたので、コメント等を入れてあります。

トップページへ 第二回へ 第四回へ