邏輯服務(wù)器的設(shè)計(jì):
邏輯服務(wù)器的設(shè)計(jì)思路其實(shí)很簡單,我們把游戲里的對象分為2大類,城市和人。所有的游戲邏輯操作都是圍繞這這兩個(gè)對象進(jìn)行的。其中城市對象又還可以細(xì)分為多個(gè)子對象,這些對象在結(jié)構(gòu)上是屬于城市的,它們分別是碼頭、船廠、交易所、銀行、酒館。
在游戲進(jìn)行的時(shí)候,玩家要進(jìn)行某些操作的時(shí)候(購買貨物)會向服務(wù)器端發(fā)送游戲消息,這時(shí)候,服務(wù)器在收到消息后,會通過消息玩家的ID知道玩家當(dāng)前在那個(gè)城市里,然后再由這個(gè)城市對象去處理這條游戲消息。在城市對象收到游戲消息后,首先是對一級分類進(jìn)行判斷,這個(gè)級別的分類標(biāo)準(zhǔn)是邏輯上的分類,這點(diǎn)在前面已經(jīng)說過了。這時(shí)候如果消息的邏輯分類是交易所,城市對象,會再調(diào)用城市對象里面的交易所對象去執(zhí)行該條游戲消息,這時(shí)候,城市對象就要根據(jù)消息的二級分類來判斷玩家到底是要做什么事情了。這時(shí)候,如果消息是BuyReq 者說明玩家是打算購買一類貨物,交易所對象,再從消息里附帶的結(jié)構(gòu)體變量讀出要購買貨物的編號和數(shù)量。這時(shí)候交易所首先要判斷,這類貨物在本交易所里時(shí)候存在,并且能夠賣,如果可以,再調(diào)用用戶對象的購買貨物函數(shù),由用戶對象進(jìn)行下一步的處理(看看時(shí)候有足夠的錢,玩家的船是否還能裝得下這個(gè)貨物等等..)如果可以,則由用戶類返回成功,否則,返回錯(cuò)誤的信息。在交易所收到用戶類返回的信息后,再根據(jù)返回的結(jié)果,生成相應(yīng)的游戲消息發(fā)還給用戶。
整個(gè)過程很簡單,所以,在項(xiàng)目開始的時(shí)候,我就先那這個(gè)系統(tǒng)開刀,在分配項(xiàng)目任務(wù)的時(shí)候,我也就有限考慮自己做簡單的東西,然后把復(fù)雜的地圖服務(wù)器仍給了另外一位同學(xué)^_^。不過雖然這個(gè)模塊簡單,不過涉及到的東西很多,難度雖然不大,但是很煩瑣,一個(gè)小小的問題就可能造成今后游戲存在的嚴(yán)重bug,所以在進(jìn)行沒有邏輯事件的處理都是時(shí)候,我都是很小心的在做,剛開始的時(shí)候,還寫過一定的測試用例去測試系統(tǒng),不過現(xiàn)在看來,當(dāng)時(shí)寫的測試用例存在有一定的問題:
1。是在完成代碼后才寫測試,XP方法要求測試用例要優(yōu)先與代碼的編寫。
2。只寫了測試過程,沒有編寫自動化的結(jié)果判斷。有測試,就必須要有預(yù)期的測試結(jié)果,當(dāng)時(shí)寫的測試用例大多只是看看那個(gè)函數(shù)能夠正確運(yùn)行而已,所有結(jié)果都是直接輸出到屏幕上,讓我自己來判斷,在測試用例寫過一段時(shí)間后,要測試的部分太多,有些看不過來了,所以寫了測試輸入,一定要想好輸出結(jié)果,并且要讓測試代碼自己把函數(shù)返回結(jié)果與想好的輸出對結(jié)果進(jìn)行判斷,從而減少人工判斷的錯(cuò)誤性。
3。測試用例過少,因?yàn)闀r(shí)間的關(guān)系,只是在前面的階段寫過一定的測試用例,到后來進(jìn)度緊張后,就沒空去進(jìn)行測試了。歸結(jié)起來,應(yīng)該是沒有一個(gè)良好的測試框架與測試機(jī)制,有好的框架,可以減少測試用例的編寫時(shí)間,從而有更多的時(shí)間去編寫代碼。同時(shí)因?yàn)轫?xiàng)目不是什么正規(guī)的項(xiàng)目,所以對測試的要求也不是太嚴(yán)格。
除了測試存在不足外,程序的整個(gè)結(jié)構(gòu)上也存在著嚴(yán)重的不足:
1。當(dāng)初因?yàn)榭紤]得簡單,每個(gè)玩家的游戲消息都會有一個(gè)與它相對應(yīng)的游戲消息,所以,我在程序里大膽的采用了函數(shù)返回游戲消息指針的方式,在系統(tǒng)完成游戲消息處理后,產(chǎn)生一條游戲消息,并且把改游戲消息以返回消息內(nèi)存首地址指針的方式返回給調(diào)用處理的函數(shù)。這樣一層層的返回,一直返回到socket的OnRead()函數(shù),然后再在這個(gè)函數(shù)里send數(shù)據(jù)給客戶端。這樣的設(shè)計(jì)在剛開始的時(shí)候,并沒有覺得什么不托,但是實(shí)際到后期才發(fā)覺,這樣的設(shè)計(jì)有一個(gè)很大的不方便,有些從客戶端來的游戲消息,它的返回消息可能不只一個(gè),所以在一個(gè)函數(shù)的返回值里就不太好處理了。這個(gè)問題我開始的時(shí)候沒有太注意,因?yàn)楹芏嘞⒍贾皇怯幸粭l返回消息而已,哪知到后來系統(tǒng)的擴(kuò)展,很多函數(shù)處理都要返回多條數(shù)據(jù),所以,在后來的不得不在邏輯處理里面增加消息發(fā)送函數(shù)。不過這樣給我的感覺就不是很舒服了,邏輯處理里面就不應(yīng)該存在其它的東西,現(xiàn)在增加的消息的發(fā)送,就不是很好了。不過一直到現(xiàn)在,我都沒有找到合適的結(jié)局方法,所以,如果在一開始的時(shí)候先想好消息該怎么發(fā)送,就不會存在這樣的混亂了。
2。在前面的代碼里,遺漏有創(chuàng)建游戲消息的代碼(交易所),后來感覺不爽,因?yàn)橐獎(jiǎng)?chuàng)建的東西很多,而且很多都是重復(fù)的,所以就用一個(gè)模版來處理創(chuàng)建過程。再到了后來,覺得邏輯代碼里存在很多游戲邏輯消息的變量賦值過程,這樣的代碼也不是很好看,所以有進(jìn)一步把這個(gè)東西獨(dú)立出來做個(gè)函數(shù),這樣邏輯處理代碼就好看很多了。這個(gè)過程是一個(gè)不斷的發(fā)展過程,也都是看到不足后才進(jìn)行處理,所以,如果當(dāng)初就和上面的游戲數(shù)據(jù)發(fā)送一起考慮好,就不會留下很多垃圾代碼(這些代碼我現(xiàn)在還沒有處理掉)。
地圖服務(wù)器的設(shè)計(jì):
對于地圖服務(wù)器的設(shè)計(jì),在網(wǎng)上關(guān)于它的文章一直都比較多,說得最多的就是如何實(shí)現(xiàn)讓客戶端與服務(wù)器進(jìn)行同步。所以,在項(xiàng)目開始的時(shí)候,這個(gè)部分能夠參考的文章很多,不過也因?yàn)檫@個(gè)系統(tǒng)是交給一個(gè)完全沒有做過游戲的同學(xué)來制作,所以很多時(shí)候,很多概念性的東西需要理解,在理解的時(shí)候會造成一定的偏差,所以系統(tǒng)在實(shí)現(xiàn)的時(shí)候不是很完善。不過也因?yàn)檫M(jìn)行開發(fā)的這位同學(xué)的實(shí)力不同一般,所以地圖服務(wù)器還是能夠運(yùn)行的,只不過^_^
當(dāng)初設(shè)計(jì)的時(shí)候,考慮到游戲里存在著2種移動場景,一個(gè)是海上,一個(gè)是城市。城市的特點(diǎn)是有邊界,海上地圖的特點(diǎn)是超大無縫地圖,所以在設(shè)計(jì)的時(shí)候,我們有限考慮的是海上場景的地圖設(shè)計(jì)。城市地圖可以看做是海上地圖的一個(gè)特例,地圖的四周只有少量或者根本沒有相關(guān)的連接地圖。
超大地圖的設(shè)計(jì)是采取對地圖進(jìn)行分塊處理,把整個(gè)海洋世界分解為多塊小地圖,每塊地圖的大小和一個(gè)游戲屏幕(800×600)的大小差不多。服務(wù)器里存放有所有地圖的信息,當(dāng)一位玩家參數(shù)移動的時(shí)候,會把自己的移動請求信息發(fā)送到相關(guān)的9塊地圖上(自己所在的地圖,以及以自己為中心鄰接的8張地圖),這樣每個(gè)客戶端都能接收到來自自己周圍9張地圖上其他玩家的移動信息,從而實(shí)現(xiàn)了系統(tǒng)的互動。
概念上說起來是很簡單,但是到實(shí)踐的時(shí)候要考慮的地方就很多了,例如移動方向,阻擋,最麻煩的就是地圖切換了,需要通知相關(guān)地圖的玩家進(jìn)入以及相關(guān)地圖上的玩家退出等游戲事件,而且還存在則跨越地圖后的移動處理等問題。總之因?yàn)槲抑皇翘峁┓桨刚撸唧w編碼不是我來做,所以我對里面的具體細(xì)節(jié)了解得不是很詳細(xì),只能大概的理一下他的思路。
服務(wù)器還是基于面向?qū)ο蟮脑O(shè)計(jì)思路來做的,整個(gè)系統(tǒng)里分了3個(gè)對象,玩家、地圖、地圖管理。玩家對象和我上面的概念一樣,是存儲玩家信息,并存在著一定的于地圖相關(guān)的處理函數(shù)。地圖對象存放的是一小張地圖的信息,和與當(dāng)張地圖操作相關(guān)的操作。地圖管理可以看做是超大地圖類,它的作用就是管理無數(shù)個(gè)地圖對象,讓它們在邏輯上拼接成為一張超大的地圖。
同時(shí)除了這3個(gè)對象外,還存在著一個(gè)狀態(tài)機(jī)的過程。我們對這個(gè)狀態(tài)機(jī)的理解很簡單,就是每隔一定的周期計(jì)算一次玩家的新位置,讓他們看起來是在連續(xù)不斷的運(yùn)動。這個(gè)東西不是很難理解,但是處理起來確看起來有些讓人困擾,首先是服務(wù)器端的在虛擬機(jī)執(zhí)行時(shí)間中斷的時(shí)間間隔與客戶端的不同,設(shè)計(jì)的時(shí)候,服務(wù)器端是每隔0.5s執(zhí)行一次,而客戶端如果也按照服務(wù)器端的設(shè)計(jì)出處理,明顯會讓玩家感覺地圖上的人物一卡一卡的在進(jìn)行跳躍運(yùn)動,所以客戶端在虛擬機(jī)的時(shí)間間隔勢必要比服務(wù)器短很多,我們的設(shè)計(jì)是0.1S執(zhí)行一次。這樣的短的間隔就會造成客戶端的移動數(shù)據(jù)與服務(wù)器端的移動數(shù)據(jù)不一致。解決這個(gè)不同步的問題采用了多種方案,其中包括定期發(fā)送玩家的位置的同步信息。不過也因?yàn)闀r(shí)間關(guān)系,這個(gè)同步方案沒有完全的實(shí)現(xiàn),所以系統(tǒng)還是存在著不同步的現(xiàn)象。
不過地圖服務(wù)器里的注解還是比較詳細(xì)的,所以這里要說明的不是很多,理解它架構(gòu)就好理解了。理解架構(gòu)后再看一下“地圖服務(wù)器工作日志.doc”會對這個(gè)開發(fā)過程有一定的了解。