出自: http://blog.csdn.net/tonywjd/archive/2006/08/11/1052346.aspx
游戲主要用到了幾個引擎,物理引擎(PhysicsX SDK 2.3.2,即NovedeX的新版本),圖形渲染引擎(OGRE 1.2.0,包括人機界面的CEGUI部分),聲音引擎(Direct Sound),網(wǎng)絡(luò)引擎(RakNet,可惜由于時間等原因,加入失敗,大大降低游戲可玩性),建模用Maya和3dsMax。
模型的導(dǎo)出
OGRE有自己的3D模型格式(.mesh格式)。導(dǎo)出可以直接出OGRE官方網(wǎng)站下載導(dǎo)出插件。不過導(dǎo)出插件有好些Bug。對于建模方式的不同導(dǎo)出也不同,Maya的導(dǎo)出插件不能導(dǎo)出非封閉的曲面,而3dsMax可以,所以可以通過它導(dǎo)成.mesh。
另外,在物理引擎中要表現(xiàn)精確的碰撞,最好能得到模型的網(wǎng)格結(jié)構(gòu),即模型的點面信息,而不是用簡單的規(guī)則形式來包裹。我們的游戲中地圖用的是前者,而車輛道具等用的是后者。
但PhysicsX并不支持.mesh。所以我們是通過導(dǎo)出插件導(dǎo)出的.mesh.xml格式文件中讀取點面信息(僅此),再寫到自定義的二進制文件格式中,為物理引擎使用。
OGRE與PhysicsX之間的耦合
PhysicsX和OGRE場景管理比較類似,都有對應(yīng)的類。一個可以移動的物體,在OGRE中可以用SceneNode實現(xiàn),并加入到場景管理
中,在PhysicsX中則可以用NxActor對應(yīng)。NxActor中PhysicsX中的一個物理單位,它可以把一個物體用簡單的幾個規(guī)則幾何形狀包
裹起來,所以的重力、碰撞等物理作用都作用在NxActor中,并產(chǎn)生相應(yīng)的物理反應(yīng)。得到相應(yīng)的物理數(shù)據(jù)之后,再通過設(shè)置SceneNode相應(yīng)屬性,
就可以實現(xiàn)逼真的物理效果。
事實上,地圖場景也是作為普通的3D物體來實現(xiàn)。
而OGRE與PhysicsX之間的映射并不是對于每一個物體進行映射封裝實現(xiàn)。而是把這兩部分封裝成獨立的兩個模塊,但這兩部分中所有的物體都是
一一對應(yīng)的。在主程序中,再把從PhysicsX部分計算出的結(jié)果,傳遞給OGRE部分,進行繪制。這樣也為網(wǎng)絡(luò)的加入提供了好的嵌入點。因為只要服務(wù)端
進行全部物理運算,再把運算的結(jié)果發(fā)給客戶端OGRE進行渲染。
OGRE中的frameStated(const FrameEvent &evt)作為程序的主線程,在其中調(diào)用
m_NxScene->simulate(evt.timeSinceLastFrame); // m_NxScene, instance of (NxScene*)
m_NxScene->flushStream();
m_NxScene->fetchResults(NX_RIGID_BODY_FINISHED);
再把運算的物理結(jié)果給OGRE繪制。
這樣OGRE與PhysicsX結(jié)合在一起,而兩者的內(nèi)部實現(xiàn)是互不影響的,可以獨立編程,只要處理好兩者物體的一一對應(yīng)關(guān)系。
地圖場景的實現(xiàn)
對于OGRE來說有專門的室外地圖場景管理。但是由于其高度圖很難導(dǎo)入到PhysicsX中,除非通過建模時得到點面信息,但在具體操作中很難做到
高度圖與網(wǎng)格點面信息的一致。我們還試過在程序初始化時通過OGRE的Ray取得地圖上M*N個點的坐標(biāo),組成2*M*N個三角形面片,使用到物理引擎
中,近似的實現(xiàn)物理地圖。但這種實現(xiàn)所有的三角形在XZ平面上的投影都是一樣的,面片太少可能不精確,太多又會增加不必要的開銷,總的來說不夠理想。
PhysicsX中也有專門用于處理地圖場景的NxActor,可以在創(chuàng)建NxActor前通過
terrainDesc.heightFieldVerticalAxis = NX_Y; // terrainDesc is a NxTriangleMeshDes
// Default: NX_NOT_HEIGHTFIELD
terrainDesc.heightFieldVerticalExtent = -1000.0f;
進行設(shè)置。這樣可以大大的提高效率。這本來應(yīng)該是一個理想的做法。但由于我們建模時模型導(dǎo)出有些誤差的原因,會出現(xiàn)某些面片為垂直,物理碰撞的效果
在這些地方過于激烈,表現(xiàn)在屏幕上就是車會突然被撞得飛起來很高。我們試了很久都沒找到合適的模型導(dǎo)出方法避免這一現(xiàn)象。所以只有所地圖場景也作為一個普
通的NxActor進行處理,這樣,雖然克服不了建模導(dǎo)出時的這個問題,但也不易被玩家發(fā)覺。
這樣,地圖場景就有了實現(xiàn)的方法了:在OGRE中作為普通的.mesh對待,創(chuàng)建SceneNode和Entity;在PhysicsX中,作為普通的NxActor對待,只是用NxCooking進行處理,具體沒去細(xì)究,可能是為了提高性能。
車輛的實現(xiàn)
車輛的實現(xiàn)是本游戲的重點。
當(dāng)然也分OGRE和PhysicsX兩部分實現(xiàn)。
OGRE部分還包括視角即(Camera),而其他諸如油量,道具之類的與車輛本身無關(guān)的這里不做描述。一輛車的主要結(jié)構(gòu)如下:

對于一輛車的建模,這里還是比較粗糙,只是分為車輪和車體,把車輪分離出來主要是實現(xiàn)輪子的轉(zhuǎn)向效果。
由于本游戲的初衷是實現(xiàn)多人對戰(zhàn)的網(wǎng)絡(luò)游戲,只是到最后沒能實現(xiàn)網(wǎng)絡(luò),改為單機。但所以的封裝都是為多人網(wǎng)絡(luò)游戲準(zhǔn)備的。
所有車會由一個類進行統(tǒng)一管理,而這個類用了Singleton設(shè)計模式。在其上有另一個封裝OGRE場景和車輛的總類。
PhysicsX部分,車體由十幾簡單的面片進行包裹,而車輪是關(guān)鍵。
車輛通過構(gòu)造輪子的層次結(jié)構(gòu)實現(xiàn),車由車體和四個車輪組成,實現(xiàn)運動主要由車輪控制。
所有車輛可控的運動(比如碰撞為不可控)都由車輪帶動。這也符合實現(xiàn)的物理。
車輪的實現(xiàn)主要是通過NxWheelShape實現(xiàn)。下面是部分對NxWheelShape的分析。
NxWheelShape屬性:
radius:Range: (0,inf) 車輪半徑
suspensionTravel:Range: [0,inf),suspension的作用距離
virtual void setLongitudalTireForceFunction (NxTireFunctionDesc tireFunc)=0
設(shè)定動力對正向的加速度等的影響
virtual void setLateralTireForceFunction (NxTireFunctionDesc tireFunc)
設(shè)定動力對側(cè)向的加速度等的影響,可以實現(xiàn)側(cè)滑之類的
axleSpeed:Range: (-inf,inf)
NOTE: NX_WF_AXLE_SPEED_OVERRIDE flag must be raised for this to have effect
An overridden axle speed of course renders the axle motor and brake torques ineffective
用setAxleSpeed時,直接設(shè)定速度,這種模式不再受motorTorque和brakeTorque等影響
brakeTorque:Range: [0,inf)
剎車力矩
inverseWheelMass:設(shè)定動力對加速度等的影響,越大作用產(chǎn)生的效果越強
motorTorque:Range: (-inf,inf)
動力矩,使車前進
steerAngle:Range: (-PI,PI) 車輪的偏向角,以弧度表示
virtual void NxWheeleShape::setSuspension(NxSpringDesc spring) [pure virtual]
與其他物體的聯(lián)接有關(guān)
上面這部分是以前寫的總結(jié),雖然不完整,現(xiàn)在也不想再細(xì)下去了,到此。
不過有一點要提的是,雖然車輪的摩擦力,彈性系數(shù)什么的
(restitution,staticFriction,dynamicFriction)可以設(shè)置,就像其他基本形狀一樣,但似乎沒什么作用,就跟全
為0一般,我試了很久,都是這樣,不知什么原因。這樣要使車表現(xiàn)出一定的阻力效果,可以通過把剎車力設(shè)為一定值實現(xiàn)。實現(xiàn)上可控地去影響輪子運動速度的只
有動力和剎車力(setMotorTorque,setBrakeTorque),而影響車輪角度的是(setSteerAngle),另外當(dāng)然也可以直
接設(shè)置輪子的前進速度和轉(zhuǎn)動的角速度,NxWheelShape提供了這種接口。不過為了逼真性,最好不要調(diào)用這兩個接口,因為自己實現(xiàn)物理效果可能有一
堆公式轉(zhuǎn)化,而只有在車輛初始化或使用什么道具的時候調(diào)用。
要實現(xiàn)側(cè)滑之類的效果,只要調(diào)用setLateralTireForceFunction即可。
另外,用現(xiàn)成的NxWheelShape還是有缺點的,就是很難調(diào)手感,可能也較難實現(xiàn)復(fù)雜的效果。我調(diào)了很久,最后只能將就了。
PhysicsX不需視角處理。
PhysicsX類之間的結(jié)構(gòu)與OGRE部分基本上一模一樣。
賽道圈數(shù)的判斷實現(xiàn)
不知道像極品飛車之類的游戲賽道圈數(shù)是怎么實現(xiàn)的,應(yīng)該與我們的不同,因為它可以每時每刻的判斷車輛是不是在往回走。我們想過可能的方法,比如賽道內(nèi)某一點,判斷其到車位置的矢量和速度矢量的夾角。沒試過。
我們用的是另一種只能判斷當(dāng)前圈數(shù)的辦法,對實現(xiàn)這點來說很準(zhǔn)確,無論倒開,車體一半穿過起跑線再返回什么的都能準(zhǔn)確判斷。這利用了PhysicsX的Trigger。
在賽道起跑線位置放置兩個相隔很近的很薄但很高且與起跑線等長的長方體NxActor。該NxActor的形狀屬性為:
BoxDesc.shapeFlags |= NX_TRIGGER_ENABLE; // NxBoxShapeDesc BoxDesc;
這樣任何NxActor碰到了這個物理都會觸發(fā)一個onTrigger過程。這里只要定義一個類的對象(如mItemTrigger)為接受觸發(fā)事件,而這個類繼承NxUserTriggerReport,并實現(xiàn)
virtual void onTrigger(NxShape& triggerShape, NxShape& otherShape, NxTriggerFlag status)方法。再
mScene->setUserTriggerReport(mItemTrigger); // NxScene* mScene;
就可能實現(xiàn)。
每次觸發(fā)都會調(diào)用一次onTrigger,在onTrigger中可以判斷是剛進入該物體還是離開。實際上對于車在兩個長方體NxActor中的位
置,總共有七種狀態(tài)(從哪一頭進入到該位置,雖然結(jié)果位置一樣,但算為不同狀態(tài),這樣可排除車輛一半進起跑線又返回引起的計數(shù)錯誤)。具體狀態(tài)如下圖:

這樣狀態(tài)間的轉(zhuǎn)換即為:

由于只能檢測到是哪個NxActor,而車輛對應(yīng)的類(這里NxVehicle)是自己封裝的,里面包含一個NxActor,所以得到哪個
NxVehicle可以充分利用NxActor中的一個public成員void*
userData的作用,讓它指向它所在的NxVehicle對象(this)即可。
CEGUI的實現(xiàn)
引用Lzx一段:
“利用OGRE引擎和CEGUI進行了基本的場景和GUI布局進行了編寫。
CEGUI讀取2DGUI布局的配置文件,這個文件是用xml來編寫的。這樣可以將程序編寫和UI的設(shè)計相對分開,使得程序設(shè)計和UI美工設(shè)計可以更好的分開。讀取后在3D場景中進行繪制。”
這個實際與游戲過程部分是獨立的。由于時間緊迫,我們并沒進行很好的封裝實現(xiàn),只在一味在寫在類(class
CuteCarFrameListener : public ExampleFrameListener, public
MouseMotionListener, public MouseListener)中。
這部分具體不是我做的,也沒去細(xì)究。而且是在網(wǎng)絡(luò)加入失敗后做的,所以并沒考慮網(wǎng)絡(luò)方面的接口,這里便不詳述了。
另外,小地圖和車速表的實現(xiàn)也是這部分內(nèi)容。
小地圖實現(xiàn)先貼一張賽道圖,在從物理引擎得到車輛的位置,算出在地圖上的相當(dāng)位置(百分比),映射顯示到小地圖上即可。
車速表則更簡單,讀出速度,畫個圖就可。
玩家視角Camera的實現(xiàn)
在車輛實現(xiàn)中已有描述,這里只是一種實現(xiàn)方案。
只創(chuàng)建一個Camera對象,注意它不能被多個SceneNode來attach,所以轉(zhuǎn)換視角時,要先detatch。這個當(dāng)時探索了好些時間,不過都是Ogre基本知識,做起來還是簡單的,不說罷。
提一下,在實際車輛行馳中,人的視線跟隨通常會比車輛轉(zhuǎn)彎的動作稍晚一點點,這里就加了一個緩沖,也就是每次看到的東西都是30幀之前的場景,以實現(xiàn)更好的效果。這個通過一個鏈表(數(shù)組)就很容易實現(xiàn)。
另外車輛左右擺動時,人的視角是不會跟著左右擺的,雖然現(xiàn)實中可能會一些,但在游戲中會給人過于晃動的感覺,這里就把左右擺動去掉了,也就是Camera向上向量始終與y軸平時,這通過一些數(shù)學(xué)運算便可,說實話,我是湊出來了,花好長時間。
聲音的實現(xiàn)
也不是我做的,同學(xué)Lzx做的,引用一段:
“音頻模塊支持3D和非3D的播放模式。考慮到播放時多次讀音頻文件會提高音頻模塊的CPU占用率而影響游戲進行,所以針對一個音頻文件,每次把音頻文件全部讀取到緩存中而不是多次讀取,每次讀取一部分。這是個用內(nèi)存換CPU效率的選擇。”
聲音的加入,對原先的程序基本無影響。因為使用的DirectSound和上面這些基本算獨立,Lzx封裝得也相當(dāng)不錯。聲音播放時自動會開線程,對游戲其它模塊無任何影響。
有關(guān)網(wǎng)絡(luò)引擎加入失敗
原計劃網(wǎng)絡(luò)引擎采用RakNet,也封裝了一些東西,也提供了不錯的接口。但由于當(dāng)初游戲的整體設(shè)計不充分,游戲框架設(shè)計成一個Ogre作為主線
程,而網(wǎng)絡(luò)部分需要單獨開辟一個線程來進行數(shù)據(jù)傳輸和處理,如果并在一個線程里面,會使線程阻塞,使數(shù)據(jù)傳送相當(dāng)緩慢,甚至不成功,導(dǎo)致游戲的無法繼續(xù).
由于時間的關(guān)系,框架更新時間不足,只好放棄這塊,做成單機版。
由于網(wǎng)絡(luò)引擎需要額外的線程和緩沖,而OGRE并不提供這種功能。最好的辦法是創(chuàng)建一個主線程,而把OGRE中循環(huán)作為一個子線程來處理。但這樣做,原先我們封裝的系統(tǒng)是不能夠支持,大量改變代碼,時間已不夠,所以無奈只好放棄。
在網(wǎng)絡(luò)上花的精力太少,一開始不夠重視也是個原因啊。而事實上我們對于網(wǎng)絡(luò)上的編程毫無經(jīng)驗。
貼些游戲截圖,美工還蠻PP








后話
調(diào)試就用OGRE提供的Log好了,不能一步步進行調(diào)試。還有一個好辦法就是生成.map文件,再根據(jù)windows中出錯提示框中通常提示運行到
哪個虛擬地址出錯,在.map文件中找出代碼的具體出錯行。這實際上對于已經(jīng)給玩家使用的程序根據(jù)玩家獲得的出錯信息找Bug很有幫助