这是一个课设。网上关于Delta3D的资料不算多,分享一下,算是做一点微小的工作。
本例程实现坦克在平地的加减速,并配音。大体来说下面两个视频讲得很好了,不过他的方式实现的坦克转向是瞬间转的,对开发来说好处不少,不过不太符合实际,这里简单描述一下基于Walk运动模式的实现方法。完全没基础的盆友先看视频,不然下面看不懂。
https://v.qq.com/x/page/s0357t9b3n1.html
https://v.qq.com/x/page/f036009i9h9.html
先说明一下,Walk模式带来了平滑的转弯,但我没有做碰撞检测,所以这个demo中坦克是在一个无限平面上面走。下面代码中的英文是我写代码时的注释,中文是本文添加的注释。
==============================一下两段为bug说明,看完代码再回头看,不想看就不看==============================
另外有个待解决的bug。这个Walk模式采用了KeyPressed()和KeyReleased()函数来监听按键,但实际上键盘输入的连击是存在打断的情况的,比如先按住前进,键盘连击不断输入前进信号,这时我们可以通过KeyPressed()来得知这些输入。但如果我们接着点击一下其它按键(如方向左键),前进的连击就会被打断,于是KeyPressed()函数就无法再被调用,也就无法再在函数里播放音效,引擎声随后停止。
解决这个bug的方法是使用多线程,因为坦克启动后的enabled属性是恒为true的,使用另一个线程调用model->GetEnabled()方法,监听其返回值,为true即坦克在运动,后播放音效即可。即不在KeyPressed()中播放音效,而是另起线程来播放。思路大致是这样,我并没有在下面代码中修正这个bug。
=======================================================================================================
下面开始正题。
首先配置好环境,场景模型的加载是一样的。设计好App类的成员
private:
RefPtr<Object> flatdirt; //地形
RefPtr<Object> brdm; //坦克
RefPtr<dtCore::WalkMotionModel> model; //行走模式
Sound* _sound; //引擎声音,这里加/减/匀/速均使用同一个声音
float speed; //坦克运动速度
下面在App类中重写Config()函数,初始化,绑定walk模式到坦克。
virtual void Config(){
Application::Config();
//Add tank
brdm = new Object("brdm");
brdm->LoadFile("../module/brdm.ive");
AddDrawable(brdm.get());
speed = 6.0f; //坦克初始速度
//Add map
flatdirt = new Object("flatdirt");
flatdirt->LoadFile("../module/flatdirt.ive");
AddDrawable(flatdirt.get());
//Set tank position
osg::Vec3 brdmPosition(0.0f,-25.0f,0.0f);
osg::Vec3 brdmRotation(0.0f,0.0f,0.0f);
dtCore::Transform trans;
trans.SetTranslation(brdmPosition);
trans.SetRotation(brdmRotation);
brdm->SetTransform(trans);
//Set camera
dtCore::Transform camPos;
osg::Vec3 canXYZ(0.0f,-100.0f,35.0f);
osg::Vec3 lookAtXYZ(brdmPosition);
osg::Vec3 upVec(0.0f,0.0f,1.0f);
camPos.Set(canXYZ,lookAtXYZ,upVec);
GetCamera()->SetTransform(camPos);
//Basic walk mode
model = new WalkMotionModel(GetKeyboard(),GetMouse()); //在这里初始化运动模式,允许键/鼠控制,不需要的话对应参数改为0
SetWalkMode(brdm,speed); //把绑定和速度封装到此方法中,方便在加减速中调用
model->SetEnabled(false); //先禁用model的运动,有前进/后退键输入时方启用,避免静止时左右转的情况
cout<<"Congif finished, walk speed="<<model->GetMaximumWalkSpeed()<<endl; //config结束,在控制台提示
}
void SetWalkMode(RefPtr<Object> obj ,float _speed){
model->SetTarget(obj); //绑定对象(坦克对象)
model->SetMaximumWalkSpeed(_speed); //设置坦克前进速度
}
至此,坦克已经可以在场景中作匀速运动(当然,还要先执行model->SetEnabled(true);)。
下面实现加减速和音频的播放。调速逻辑是:坦克必须先启动,后方可加减速。启动后坦克以怠速6.0f运动,按住左Ctrl加速,后按住左Shift可减速(有最高/最低速度限制)。加减速键一旦释放则马上停止加减速,保持当前速度运动。一旦前进/后退被释放,坦克马上静止,速度属性回到怠速,引擎声音停止。再次启动时重新以怠速匀速前进。
于是需要设置函数监听按键的按下、抬起,并获取指定按键状态,delta3D已经封装好了函数,我们需要稍作重写。首先是按键按下的监听:
virtual bool KeyPressed(const Keyboard* keyboard, int kc){ //有方向键按下 if(keyboard->GetKeyState(osgGA::GUIEventAdapter::KEY_Up)||keyboard->GetKeyState(osgGA::GUIEventAdapter::KEY_Down)||keyboard->GetKeyState(osgGA::GUIEventAdapter::KEY_Left)||keyboard->GetKeyState(osgGA::GUIEventAdapter::KEY_Right)){ if(!_sound->IsPlaying() && model->IsEnabled()){ //音效未播放且车在运动,则播放音效 _sound->Play(); } } //Must start moving first if(keyboard->GetKeyState(osgGA::GUIEventAdapter::KEY_Up)||keyboard->GetKeyState(osgGA::GUIEventAdapter::KEY_Down)){ model->SetEnabled(true); //前进或后退已被按下,允许坦克启动
if(keyboard->GetKeyState(osgGA::GUIEventAdapter::KEY_Control_L)){//if(kc==osgGA::GUIEventAdapter::KEY_Control_L){if(speed<30.0f){speed += 0.5f;SetWalkMode(brdm,speed); }}//左Shift减速,最小速度6.0fif(keyboard->GetKeyState(osgGA::GUIEventAdapter::KEY_Shift_L)){//if(kc==osgGA::GUIEventAdapter::KEY_Shift_L){if(speed>6.0f){speed -= 0.5f;SetWalkMode(brdm,speed);}}return true;}//Any other button is pressedelse {return false;}//左Ctrl加速,最大速度30.0f
接下来监听按键抬起事件:virtual bool KeyReleased(const Keyboard* keyboard,int kr){ if(kr==osgGA::GUIEventAdapter::KEY_Up||kr==osgGA::GUIEventAdapter::KEY_Down){ model->SetEnabled(false); if(_sound->IsPlaying()){ _sound->Stop(); //停止音效 } speed=6.0f; SetWalkMode(brdm,speed); //回到怠速 } return true; }
当然,我们的音效还没有初始化,也没有设计好释放逻辑。在App的构造和析构函数中进行这些任务:
App(){ //Sound init AudioManager::Instantiate(); AudioManager::GetInstance().LoadFile("../Image.wav"); _sound=AudioManager::GetInstance().NewSound(); _sound->LoadFile("../Image.wav"); } ~App(){ AudioManager::GetInstance().FreeSound(_sound); AudioManager::Destroy(); }
最好在main()函数中启动场景即可int main() { App app; app.Config(); app.Run(); return 0; }
完整代码见我的github:pigcage的github