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

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

這樣狀態間的轉換即為:

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








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