天天看點

賽車遊戲開發總結PHYSX OGRE

文章轉自:http://blog.csdn.net/tonywjd/article/details/1052346

開發這個遊戲實際上是一門遊戲設計課的作業,幾個人合作。以後大概也不會搞這方面的東西了,總結下吧。

遊戲主要用到了幾個引擎,實體引擎(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),而其他諸如油量,道具之類的與車輛本身無關的這裡不做描述。一輛車的主要結構如下:

賽車遊戲開發總結PHYSX OGRE

對于一輛車的模組化,這裡還是比較粗糙,隻是分為車輪和車體,把車輪分離出來主要是實作輪子的轉向效果。

由于本遊戲的初衷是實作多人對戰的網絡遊戲,隻是到最後沒能實作網絡,改為單機。但是以的封裝都是為多人網絡遊戲準備的。

所有車會由一個類進行統一管理,而這個類用了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中的位置,總共有七種狀态(從哪一頭進入到該位置,雖然結果位置一樣,但算為不同狀态,這樣可排除車輛一半進起跑線又傳回引起的計數錯誤)。具體狀态如下圖:

賽車遊戲開發總結PHYSX OGRE

這樣狀态間的轉換即為:

賽車遊戲開發總結PHYSX OGRE

由于隻能檢測到是哪個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

賽車遊戲開發總結PHYSX OGRE
賽車遊戲開發總結PHYSX OGRE
賽車遊戲開發總結PHYSX OGRE
賽車遊戲開發總結PHYSX OGRE
賽車遊戲開發總結PHYSX OGRE
賽車遊戲開發總結PHYSX OGRE
賽車遊戲開發總結PHYSX OGRE
賽車遊戲開發總結PHYSX OGRE

後話

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

還有一些沒搞清的地方實際上,主要是實體引擎中的一些位置角度和Ogre之間的對應。還有就是計算視角,隻能說終于湊出來了。

這些幾個人一起讨論搞出來了,雖然有些菜,記錄下也許有用,對己或對人。

遊戲能跑了,也算告一段落,以後大概也不做這些了。