|
第四回 キー入力に対応する 前回、マップを表示し、組み込み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クラスが出来上がりました。 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の仮完成として、実際に動かすために、前回のプログラムとの結び付けを行いましょう。 #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);
追記: #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プログラムとの結びつけは完了です。 続いて、イベントに対応してモデルを動かしましょう。ここではまず、キーが押されている間アニメーションを走りパターンに、離している時は立ちパターンにしてみましょう。 SydneyNode->setMD2Animation(EMAT_STAND); 続いて、キーの状況によってアニメーションパターンを変えるように設定しましょう。 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にしましょう。 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を使った小技あれこれ 方向ベクトルの取得 続いて、上下キーを押した時に今向いている方向(あるいはその反対)に進むようにしたいわけですが、そこでちょっとした小技を使ってみましょう。 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姐さんが空間内を自由に歩きまわれるようになったので、カメラがそれを追いかけるようにしましょう。 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");
これでちゃんと前進、後退していることがわかるようになったと思います。 今回はここまでにしておきましょう。今回のソースをここに置いておきます。コメントを付加し、第三回と同様にFPS表示を入れてあります。 |