基於Delta3D的walk運動模式實現
這是一個課設。網上關於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); //前進或後退已被按下,允許坦克啟動
//左Ctrl加速,最大速度30.0f
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;}
接下來監聽按鍵擡起事件:
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