|
第五回 あれこれ動かしてみる 前回、キー入力に応じて3rdPersonビューでプレイヤーが移動するプログラムを作りました。 挙動に関するえらい人 挙動担当、ISceneNodeAnimator 前回のプログラムは、プレイヤーを移動させるために、描画ループ中でプレイヤーの座標等を操作していました。 Irrlichtには、挙動を管理するためのクラス、ISceneNodeAnimatorというクラスが存在します。 看板娘登場 まずはSydney姐さんの周りにオプションを出してみましょう。 SydneyNode->setMD2Animation(EMAT_STAND); // アニメーションパターン設定する
// オプションをBillboardとして作り、Sydneyの子Nodeとする
IBillboardSceneNode *OptionNode = Scene->addBillboardSceneNode(SydneyNode, core::dimension2d<f32>(20,20),vector3df(0,15,25));
OptionNode->setMaterialFlag(video::EMF_LIGHTING, false);
OptionNode->setMaterialTexture(0, Driver->getTexture("portal1.bmp"));
OptionNode->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
// 進行方向を取得するための空ノードを作る
ISceneNode *TargetNode = Scene->addEmptySceneNode(SydneyNode); // 空ノードを作る
まず、Sydneyの子Nodeとして、IBillboardSceneNodeを一つ作ります。 とりあえず実行してみましょう。Sydney姐さんの横に青い光が浮いていれば成功です。
組み込みアニメータを使う 昔ながらのテクスチャアニメーション 続いて、オプションをアニメーションさせてみましょう。いよいよISceneNodeAnimatorを使用します。 まずは以下のコードを追加してください。 SydneyNode->setMD2Animation(EMAT_STAND); // アニメーションパターン設定する
// オプションをBillboardとして作り、Sydneyの子Nodeとする
IBillboardSceneNode *OptionNode = Scene->addBillboardSceneNode(SydneyNode, core::dimension2d<f32>(20,20),vector3df(0,15,25));
OptionNode->setMaterialFlag(video::EMF_LIGHTING, false);
OptionNode->setMaterialTexture(0, Driver->getTexture("portal1.bmp"));
OptionNode->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
// オプションをテクスチャ切替アニメーションさせる。
array<ITexture *> textures;
// createTextureAnimatorでオプションが明滅するテクスチャアニメータを作り、Nodeに割り当てる
ISceneNodeAnimator *anim = Scene->createTextureAnimator(textures, 30);
OptionNode->addAnimator(anim);
anim->drop();
// 進行方向を取得するための空ノードを作る
ISceneNode *TargetNode = Scene->addEmptySceneNode(SydneyNode); // 空ノードを作る
ISceneManagerには、いくつか定型パターンになるISceneNoneAnimatorを自動で作ってくれるサービスがいくつか存在します。createTextureAnimator()関数もその一つで、ITextureの入った動的配列(arrayクラス)を渡すと、指定された時間でそれらのテクスチャ切替を行ってくれます。 続いて、アニメーションさせるためのarrayクラスにITextureのポインタを挿入します。以下のコードを追加してください。 SydneyNode->setMD2Animation(EMAT_STAND); // アニメーションパターン設定する
// オプションをBillboardとして作り、Sydneyの子Nodeとする
IBillboardSceneNode *OptionNode = Scene->addBillboardSceneNode(SydneyNode, core::dimension2d<f32>(20,20),vector3df(0,15,25));
OptionNode->setMaterialFlag(video::EMF_LIGHTING, false);
OptionNode->setMaterialTexture(0, Driver->getTexture("portal1.bmp"));
OptionNode->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
// TextureAnimatorのために、arrayを準備する
array<ITexture *> textures;
for (int i = 1; i < 8; i++)
{
char tmp[64];
sprintf(tmp, "portal%d.bmp", i);
video::ITexture* t = Driver->getTexture(tmp);
textures.push_back(t);
}
// createTextureAnimatorでオプションが明滅するテクスチャアニメータを作り、Nodeに割り当てる
ISceneNodeAnimator *anim = Scene->createTextureAnimator(textures, 30);
OptionNode->addAnimator(anim);
anim->drop();
// 進行方向を取得するための空ノードを作る
ISceneNode *TargetNode = Scene->addEmptySceneNode(SydneyNode); // 空ノードを作る
これで実行してみましょう。Sydney姐さんの横の光が明滅するはずです。
いつもより余計に回(略) 続いて、Sydney姐さんの周りをオプションが回転するようにしましょう。 // createTextureAnimatorでオプションが明滅するテクスチャアニメータを作り、Nodeに割り当てる
ISceneNodeAnimator *anim = Scene->createTextureAnimator(textures, 30);
OptionNode->addAnimator(anim);
anim->drop();
// createFlyCircleAnimatorで特定座標を中心に回転するアニメータを作り、Nodeに割り当てる
anim = Scene->createFlyCircleAnimator(vector3df(0,15,0),25,0.003f);
OptionNode->addAnimator(anim);
anim->drop();
// 進行方向を取得するための空ノードを作る
ISceneNode *TargetNode = Scene->addEmptySceneNode(SydneyNode); // 空ノードを作る
この例のように、Nodeには同時に複数のISceneNodeAnimatorを割り付けることが可能です。 これで実行してみましょう。Sydney姐さんの周囲をオプションが回るようになるはずです。
弾丸を出す前に 続いて、スペースキーを押したらSydney姐さんから弾丸が飛ぶようにしたいと思います。 // 今回のプログラム用のイベントレシーバ
class MyEventReceiver : public IEventReceiver
{
protected:
int Trigger;
public:
int State;
int Roll;
int Fire;
MyEventReceiver() : IEventReceiver()
{
Fire = Trigger = Roll = State = 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; // Stateを-1に
} else { // そうでないなら
State = 1; // Stateを1に
}
} else { // そうでないなら(離上イベントなら)
State = 0; // Stateを0に
}
return true;
case KEY_LEFT: // 左矢印か
case KEY_RIGHT: // 右矢印なら
if (event.KeyInput.PressedDown){ // 押下イベントなら
if (event.KeyInput.Key == KEY_LEFT){ // 左矢印なら
Roll = -1; // Rollを-1に
} else { // そうでないなら
Roll = 1; // Rollを1に
}
} else { // そうでないなら(離上イベントなら)
Roll = 0; // Rollを0に
}
return true;
case KEY_SPACE:
if (event.KeyInput.PressedDown){ // 押下イベントなら
if (Trigger == 0){
Trigger = 1;
Fire = 1;
}
} else { // そうでないなら(離上イベントなら)
Trigger = 0;
}
default:
return false;
}
}
return false;
}
};
これで、スペースが押された瞬間にFireメンバが1になるはずです。 今回、変数の初期化をこっそりInit()関数からコンストラクタに切り替えていますので、初期化時にInit()の呼び出しをはずしてください。 MyEventReceiver Receiver;
// Receiver.Init(); // この行削除
// IrrlichtDeviceを確保して、IVideoDriver、ISceneManagerを取得する
弾丸発射 キー入力に対応できたところで、実際に弾丸を出してみましょう。 // 前進・後退
if (Receiver.State){ // 前進もしくは後退であったら
// 移動目標ノードとプレイヤーの現在位置から進行方向ベクトルを得る
v = TargetNode->getAbsolutePosition() - SydneyNode->getPosition();
// 進行方向ベクトルに移動速度と進行方向を乗算する
SydneyNode->setPosition(SydneyNode->getPosition() + v * Receiver.State * 0.2f);
}
// 弾丸発射
if (Receiver.Fire){
Receiver.Fire = 0;
vector3df pos = SydneyNode->getPosition();
pos.Y += 15;
ISceneNode *BuilletNode = Device->getSceneManager()->addBillboardSceneNode(0, core::dimension2d<f32>(10,10),pos);
BuilletNode->setMaterialFlag(video::EMF_LIGHTING, false);
BuilletNode->setMaterialTexture(0, Device->getVideoDriver()->getTexture("portal1.bmp"));
BuilletNode->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
}
// カメラ位置の設定
とりあえず実行してみると、スペースを押すたびにSydney姐さんの腰位置あたりに弾丸が発生し、そのまま中に浮いているはずです。
この弾丸は発射すると消えることなく、そのまま宙に浮き続けます。この程度であればそう簡単にはメモリーオーバー等にはならないと思いますが、それでも何千、何万と出し続ければいつかはメモリーオーバーになると思いますから、一定時間後に消えるようにしましょう。以下のコードを追加してください。 BuilletNode->setMaterialTexture(0, Device->getVideoDriver()->getTexture("portal1.bmp"));
BuilletNode->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
anim = Device->getSceneManager()->createDeleteAnimator(2000);
BuilletNode->addAnimator(anim);
anim->drop();
}
// カメラ位置の設定
CameraNode->setPosition(CameraPosNode->getAbsolutePosition());
createDeleteAnimator()関数は与えられた時間後に自動的にそのノードを消滅させるアニメーターです。 では、次にこの弾丸を前に飛ばしてみましょう。以下のコードを追加してください。 // 弾丸発射
if (Receiver.Fire){
Receiver.Fire = 0;
vector3df pos = SydneyNode->getPosition();
pos.Y += 15;
ISceneNode *BuilletNode = Device->getSceneManager()->addBillboardSceneNode(0, core::dimension2d<f32>(10,10),pos);
BuilletNode->setMaterialFlag(video::EMF_LIGHTING, false);
BuilletNode->setMaterialTexture(0, Device->getVideoDriver()->getTexture("portal1.bmp"));
BuilletNode->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
vector3df pos2 = TargetNode->getAbsolutePosition() - SydneyNode->getPosition();
pos2 *= 400;
pos2 += pos;
pos2.Y = pos.Y;
ISceneNodeAnimator *anim = Device->getSceneManager()->createFlyStraightAnimator(pos,pos2,2000);
BuilletNode->addAnimator(anim);
anim->drop();
anim = Device->getSceneManager()->createDeleteAnimator(2000);
BuilletNode->addAnimator(anim);
anim->drop();
}
createFlyStraightAnimatorは、与えられた視点から終点へ定められた時間でまっすぐに移動するアニメーターです。 自前でアニメータを作ってみる プレイヤー移動をアニメーター化 これまで、キー入力に対応したSydneyさんの移動は描画ループ中で行っていました。これもアニメータにしてしまいましょう。 といったメリットが得られます。まあ、よくわからないという人は まず、描画ループの中の移動に関する処理を全て抜き去ってください。 // 描画ループ
while(Device->run()) // IrrlichtDeviceが有効な間ループ
{
// この間ごっそり削除
// カメラ位置の設定
CameraNode->setPosition(CameraPosNode->getAbsolutePosition());
// カメラ位置ノードの位置をカメラ位置に
それでは、これまでの仕様を踏襲したオリジナルのアニメータを作ります。 // プレイヤー移動に関するオリジナルアニメータ
class MyPlayerAnimator : public ISceneNodeAnimator
{
};
続いて、コンストラクタを作りましょう。プレイヤー移動なのでコンストラクタ中でEventReceiverを受け取って設定しておくことにします。進行方向を得るノードもここで作ることにしてしまうので、初期化中のターゲットを作る処理を取り払っておきましょう。 // createFlyCircleAnimatorで特定座標を中心に回転するアニメータを作り、Nodeに割り当てる
anim = Scene->createFlyCircleAnimator(vector3df(0,15,0),25,0.003f);
OptionNode->addAnimator(anim);
anim->drop();
// ↓ここから削除
// 進行方向を取得するための空ノードを作る
// ISceneNode *TargetNode = Scene->addEmptySceneNode(SydneyNode); // 空ノードを作る
// TargetNode->setPosition(vector3df(1,0,0)); // プレイヤーの前方に配置
// ↑ここまで
// カメラ位置設定用の空ノードを作る
ISceneNode *CameraPosNode = Scene->addEmptySceneNode(SydneyNode);
// プレイヤー移動に関するオリジナルアニメータ
class MyPlayerAnimator : public ISceneNodeAnimator
{
protected:
IrrlichtDevice *Device;
MyEventReceiver *Receiver;
u32 lastTime;
int lastState;
IAnimatedMeshSceneNode *Player;
ISceneNode *Target;
public:
MyPlayerAnimator(IrrlichtDevice *device,MyEventReceiver *receiver,IAnimatedMeshSceneNode *node) : ISceneNodeAnimator()
{
Device = device;
Receiver = receiver;
lastTime = Device->getTimer()->getTime();
lastState = Receiver->State; // プレイヤーの状態変化を初期化
Player = node;
// 進行方向を取得するための空ノードを作る
Target = Device->getSceneManager()->addEmptySceneNode(Player); // 空ノードを作る
Target->setPosition(vector3df(1,0,0)); // プレイヤーの前方に配置
}
};
いよいよ移動ロジック本体を記述します。移動ロジックはISceneNodeAnimatorのメンバ関数AnimateNode()をオーバーライドすることで実現します。以下のコードを追加してください。基本的にこれまで描画ループ中に書かれていたのと同じロジックです。 // プレイヤー移動に関するオリジナルアニメータ
class MyPlayerAnimator : public ISceneNodeAnimator
{
protected:
IrrlichtDevice *Device;
MyEventReceiver *Receiver;
u32 lastTime;
int lastState;
IAnimatedMeshSceneNode *Player;
ISceneNode *Target;
public:
MyPlayerAnimator(IrrlichtDevice *device,MyEventReceiver *receiver,IAnimatedMeshSceneNode *node) : ISceneNodeAnimator()
{
Device = device;
Receiver = receiver;
lastTime = Device->getTimer()->getTime();
lastState = Receiver->State; // プレイヤーの状態変化を初期化
Player = node;
// 進行方向を取得するための空ノードを作る
Target = Device->getSceneManager()->addEmptySceneNode(Player); // 空ノードを作る
Target->setPosition(vector3df(1,0,0)); // プレイヤーの前方に配置
}
virtual void animateNode(ISceneNode *node,u32 timeMs)
{
u32 now = Device->getTimer()->getTime();
s32 span = now - lastTime;
lastTime = now;
vector3df v;
// アニメーションパターン設定
if (lastState != Receiver->State){ // 「立ち」と「走り」状態に変化があったら
if (Receiver->State){ // 「走り」なら
Player->setMD2Animation(EMAT_RUN); // アニメーションを「走り」に
} else { // そうでなければ
Player->setMD2Animation(EMAT_STAND); // アニメーションを「立ち」に
}
lastState = Receiver->State; // 状態変化を保存
}
// 方向転換
if (Receiver->Roll){ // 方向変化があったなら
v = Player->getRotation(); // プレイヤーの現在方向を得る
v.Y += 0.08 * span * Receiver->Roll; // Y軸角を動かす
Player->setRotation(v); // プレイヤーの方向をセット
}
// 前進・後退
if (Receiver->State){ // 前進もしくは後退であったら
// 移動目標ノードとプレイヤーの現在位置から進行方向ベクトルを得る
v = Target->getAbsolutePosition() - Player->getPosition();
// 進行方向ベクトルに移動速度と進行方向を乗算する
Player->setPosition(Player->getPosition() + (v * (Receiver->State * span * 0.06f)));
}
// 弾丸発射
if (Receiver->Fire){
Receiver->Fire = 0;
vector3df pos = Player->getPosition();
pos.Y += 15;
ISceneNode *BuilletNode = Device->getSceneManager()->addBillboardSceneNode(0, core::dimension2d<f32>(10,10),pos);
vector3df pos2 = Target->getAbsolutePosition() - Player->getPosition();
pos2 *= 400;
pos2 += pos;
pos2.Y = pos.Y;
BuilletNode->setMaterialFlag(video::EMF_LIGHTING, false);
BuilletNode->setMaterialTexture(0, Device->getVideoDriver()->getTexture("portal1.bmp"));
BuilletNode->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
ISceneNodeAnimator *anim = Device->getSceneManager()->createFlyStraightAnimator(pos,pos2,2000);
BuilletNode->addAnimator(anim);
anim->drop();
anim = Device->getSceneManager()->createDeleteAnimator(2000);
BuilletNode->addAnimator(anim);
anim->drop();
}
}
};
最後に、今作ったアニメータをSydneyさんのNodeに関連づけます。 SydneyNode->setMD2Animation(EMAT_STAND); // アニメーションパターン設定する
MyPlayerAnimator playeranim(Device,&Receiver,SydneyNode);
SydneyNode->addAnimator(&playeranim);
// オプションをBillboardとして作り、Sydneyの子Nodeとする
IBillboardSceneNode *OptionNode = Scene->addBillboardSceneNode(SydneyNode, core::dimension2d<f32>(20,20),vector3df(0,15,25));
ここまで終わったらビルドして実行してみましょう。以前と違って、FPSがどう変わろうが移動速度は変わらないはずです。(スクリーンショットを取ってみましたが、見た目は全然変わらないので面白くないですねorz)
今回はここまでにしておきましょう、ここまでのソースをここに置いておきます。 |