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

チュートリアル

第四回 キー入力に対応する

前回、マップを表示し、組み込みFPSカメラを使ってマップをぐりぐり移動できるようにしました。
今回は、キー入力対応の部分を作りこみ、モデルをキー操作で動かせるようにしたいと思います。
前回のソースは、すでにFPS入力が組み込まれていますので、今回は第二回のソースを母体にすることにしましょう。

イベント処理のえらい人

IEventReceiver登場

Irrlichtでは、ユーザーの入力全てがイベントとして扱われます。そのイベントを取り仕切るクラスがIEventReceiverです。

IEventReceiverは、イベントを受け取りますが、そのイベントに対してどういう振る舞いをするかという点に関しては空っぽの状態になっていますので、プログラムに組み込む際には、実際にはIEventReceiverを継承した、「自前のウィンドウ専用のEventReceiver」を記述することになります。

早速記述してみましょう。第二回のソースを元にプロジェクトを作り、以下のコードを追加してください。

#pragma comment(lib, "Irrlicht.lib")

class MyEventReceiver : public IEventReceiver
{
};

#ifdef WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )

これでIEventReceiverを継承したMyEventReceiverクラスが出来上がりました。
IEventReceiverは、イベントが発生すると、内部のOnEvent()というメソッドを呼び出します。
独自のイベント処理を記述する場合、OnEvent()をオーバーライドしてやればよいわけです。
以下のコードを追加しましょう。

class MyEventReceiver : public IEventReceiver
{

public:
    int State;
    virtual bool OnEvent(SEvent event)
    {
        if (event.EventType == irr::EET_KEY_INPUT_EVENT)
        {
            if (event.KeyInput.PressedDown){
                State = 1;
            } else {
                State = 0;
            }
            return true;
        }
        return false;
    }
}
};

これで、何かキーを押している間メンバ変数Stateが1になるよう設定されました。

実体作成とIrrlichtDeviceへの結びつけ

これで一旦はMyEventReceiverの仮完成として、実際に動かすために、前回のプログラムとの結び付けを行いましょう。
メイン関数の中、CreateDevice前に以下のコードを追加してください。

#ifdef WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main()
#endif
{
    MyEventReceiver Receiver;
    Receiver.State = 0;
    IrrlichtDevice *Device = createDevice(EDT_OPENGL, dimension2d<s32>(512, 384), 16, false, false, 0);

続いて、これまで0を指定したcreateDevice()の引数receiverに今作ったMyEventReceiverの実体を指定します。

#ifdef WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main()
#endif
{
    MyEventReceiver Receiver;
    Receiver.State = 0;
    IrrlichtDevice *Device = createDevice(EDT_OPENGL, dimension2d<s32>(512, 384), 16, false, false, &Receiver);

追記:
Irrlicht0.7で、createDeviceの仕様に変更が入り、
フルスクリーン、ステンシル設定フラグの後に、VSync同期フラグが入り、引数パターンが変化しました。
0.7以降を使っている場合、以下のようにする必要があります。
(0.6以前を使用している場合も、互換性のためこうした方がいいでしょう)
申し訳ありませんが、添付ソースは未修正ですorz

#ifdef WIN32
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )
#else
int main()
#endif
{
    MyEventReceiver Receiver;
    Receiver.State = 0;
    IrrlichtDevice *Device = createDevice(EDT_OPENGL, dimension2d<s32>(512, 384), 16, false, false);
    Device->setEventReceiver(&Receiver);

これでIrrlichtプログラムとの結びつけは完了です。
以後、このcreateDeviceで作られたウィンドウに関する全てのイベントは、MyEventReceiverクラスによって処理されるわけです。

続いて、イベントに対応してモデルを動かしましょう。ここではまず、キーが押されている間アニメーションを走りパターンに、離している時は立ちパターンにしてみましょう。
まずは、初期状態のアニメーションパターンを立ちパターンに変更しましょう。

    SydneyNode->setMD2Animation(EMAT_STAND);

続いて、キーの状況によってアニメーションパターンを変えるように設定しましょう。
描画ループ中、BeginScene()前に以下のように記述しましょう。

    int lastState = Receiver.State;

    while(Device->run())
    {
        if (lastState != Receiver.State){
            if (Receiver.State){
                SydneyNode->setMD2Animation(EMAT_RUN);
            } else {
                SydneyNode->setMD2Animation(EMAT_STAND);
            }
            lastState = Receiver.State;
        }
        Driver->beginScene(true, true, SColor(0,100,100,160));

一旦実行

一旦実行してみましょう。
普段は立ち状態で、キーを押している間走りパターンになると思います。

とりあえず、基本的なキー入力のハンドリングは以上のように行います。

ゲームらしいキー入力

矢印キーで方向転換

単純なキー入力判定ができるようになったところで、もう少しちゃんとした操作系っぽくしていきましょう。
ここでは、バイオハザードっぽく、左右矢印で方向転換、上下矢印で前進後退、という形にしましょう。

まずは、OnEvent()関数中でちゃんとキーの種類を判定し、上矢印が押されたら現状のままStateを1に、下矢印が押されたら-1に、上下とも押されていなければ0に、左矢印が押されたら新しいメンバ変数Rollを-1に、右矢印であれば1に、左右とも押されていない場合は0にしましょう。
ついでに、状態メンバ変数も増えたところで、それらを初期化するInit()メンバ関数も追加しましょう。
MyEventReceiverを以下のように書き換えましょう。
(今回は、ほぼ全面的な書き換えになるので、特に強調表示は行っていません。)

class MyEventReceiver : public IEventReceiver
{
public:
    int State;
    int Roll;
    void init()
    {
        State = Roll = 0;
    }
    virtual bool OnEvent(SEvent event)
    {
        if (event.EventType == irr::EET_KEY_INPUT_EVENT)
        {
            switch(event.KeyInput.Key)
            {
                case    KEY_UP:
                case    KEY_DOWN:
                    if (event.KeyInput.PressedDown){
                        if (event.KeyInput.Key == KEY_DOWN){
                            State = -1;
                        } else {
                            State = 1;
                        }
                    } else {
                        State = 0;
                    }
                    return true;
                case    KEY_LEFT:
                case    KEY_RIGHT:
                    if (event.KeyInput.PressedDown){
                        if (event.KeyInput.Key == KEY_LEFT){
                            Roll = -1;
                        } else {
                            Roll = 1;
                        }
                    } else {
                        Roll = 0;
                    }
                    return true;
                default:
                    return false;
            }
        }
        return false;
    }
};

MyEventReceiverの実体を作った直後に、変数の初期化をしていましたが、これをInit()に置き換えましょう。

    MyEventReceiver Receiver;
    Receiver.Init();
    IrrlichtDevice *Device = createDevice(EDT_OPENGL, dimension2d<s32>(512, 384), 16, false, false, &Receiver);

続いて、描画ループ中に以下のコードを追加しましょう。

    while(Device->run())
    {
        vector3df v;
        if (lastState != Receiver.State){
            if (Receiver.State){
                SydneyNode->setMD2Animation(EMAT_RUN);
            } else {
                SydneyNode->setMD2Animation(EMAT_STAND);
            }
            lastState = Receiver.State;
        }
        if (Receiver.Roll){
            v = SydneyNode->getRotation();
            v.Y += 0.5f*Receiver.Roll;
            SydneyNode->setRotation(v);
        }

これで実行してみましょう。上下キーを押している間アニメーションパターンが変化し、左右キーを押している間方向が変化するはずです。

Nodeを使った小技あれこれ

方向ベクトルの取得

続いて、上下キーを押した時に今向いている方向(あるいはその反対)に進むようにしたいわけですが、そこでちょっとした小技を使ってみましょう。
まず、SydneyさんのNodeの子Nodeとして、空のノードを一つ作ります。
続いて、そのノードをSydney姐さんの前方ちょうど1になる位置に設定します。
以下のコードを追加してください。

    SydneyNode->setMD2Animation(EMAT_STAND);

    ISceneNode *TargetNode = Scene->addEmptySceneNode(SydneyNode);
    TargetNode->setPosition(vector3df(1,0,0));

    int lastState = Receiver.State;

続いて、描画ループ中に以下のコードを追加します。

        if (Receiver.Roll){
            v = SydneyNode->getRotation();
            v.Y += 0.5f*Receiver.Roll;
            SydneyNode->setRotation(v);
        }
        if (Receiver.State){
            v = TargetNode->getAbsolutePosition() - SydneyNode->getPosition();
            SydneyNode->setPosition(SydneyNode->getPosition() + v * Receiver.State * 0.2f);
        }
        Driver->beginScene(true, true, SColor(0,100,100,160));

何気に使っていますが、IrrlichtのVector3dクラスは、四則演算に対応しています。
+と-で各要素の加算減算、スカラーとの積で長さの変更、ベクトルとの積で外積が求められます。

これで実行してみましょう。上下キーで向いている方向に向かって前進、後退するようになるはずです。

簡単な3rd Personカメラ

Sydney姐さんが空間内を自由に歩きまわれるようになったので、カメラがそれを追いかけるようにしましょう。
まず、Cameraノードの位置などをいじることになるので、Cameraノードのポインタを変数に保存するようにしましょう。以下のコードを追加してください。

    ISceneManager *Scene = Device->getSceneManager();

    ICameraSceneNode *CameraNode = Scene->addCameraSceneNode(0, vector3df(0,10,-40), vector3df(0,0,0));

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

続いて、先程と同じ要領でSydney姐さんの後方に空ノードを作りましょう。

    TargetNode->setPosition(vector3df(1,0,0));

    ISceneNode *CameraPosNode = Scene->addEmptySceneNode(SydneyNode);
    CameraPosNode->setPosition(vector3df(-40,10,0));

    int lastState = Receiver.State;

カメラの位置を持つノードが出来上がりましたので、描画ループ中でカメラが常にSydney姐さんの方を向くように設定しましょう。

        if (Receiver.State){
            v = TargetNode->getAbsolutePosition() - SydneyNode->getPosition();
            SydneyNode->setPosition(SydneyNode->getPosition() + v * Receiver.State * 0.2f);
        }
        CameraNode->setPosition(CameraPosNode->getAbsolutePosition());
        CameraNode->setTarget(SydneyNode->getPosition());
        Driver->beginScene(true, true, SColor(0,100,100,160));

実行してみましょう。視点が常にSydney姐さんの後方に位置するようになったはずです。

常に後方に位置するようになったのはいいけど、これでは本当に進んでいるのかどうかわかりませんね。
第三回で説明した方法で、マップを表示をするようにしましょう。

    ICameraSceneNode *CameraNode = Scene->addCameraSceneNode(0, vector3df(0,10,-40), vector3df(0,0,0));

    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));

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

これでちゃんと前進、後退していることがわかるようになったと思います。
一応これで簡単な3rd Personカメラが出来上がったわけですが、今時のゲームだと、カメラがもう少し柔らかな動きをする場合が多いですよね?プレイヤーが方向を変えると、少し遅れ気味にカメラがついてきて、一瞬横顔が見えたり、マウスを動かすとカメラが動かせ、プレイヤーが方向を変えると、その位置からゆっくりとプレイヤーの後方に移動していったりとか。
そういうモダンなカメラ挙動については、次回以降で解説したいと思います。

今回はここまでにしておきましょう。今回のソースをここに置いておきます。コメントを付加し、第三回と同様にFPS表示を入れてあります。

トップページへ 第三回へ 第五回へ