《球球大作戰》原始碼解析:移動演算法
《球球大作戰》原始碼解析——(1)執行起來
《球球大作戰》原始碼解析:伺服器與客戶端架構
《球球大作戰》原始碼解析:移動演算法
《球球大作戰》原始碼解析(6):碰撞處理
《球球大作戰》原始碼解析(7):遊戲迴圈
《球球大作戰》原始碼解析(8):訊息廣播
服務端處理了遊戲的各種遊戲邏輯,怎樣讓小球移動是重點之一。若想做服務端運算的遊戲,這一部分程式碼是很值得參考的。
SetInterval
nodejs中的setInterval是定時器,如下的程式碼表示每隔1秒執行一次myfunc方法。
- setInterval(myfunc,1000);
- function myfunc(){
- console.log("hehe");
- };
3個定時器
原始碼中設定了3個定時器,分別是moveloop、gameloop和sendUpdates。其中moveloop每秒執行60次,它主要處理小球的移動計算。
- setInterval(moveloop, 1000 / 60);
- setInterval(gameloop, 1000);
- setInterval(sendUpdates, 1000 / c.networkUpdateFactor);
moveloop
Moveloop方法的程式碼如下,它對每個玩家執行tickPlayer,處理每個小球的移動。
- function moveloop() {
- for (var i = 0; i < users.length; i++) {
- tickPlayer(users[i]);
- }
- for (i=0; i < massFood.length; i++) {
- if(massFood[i].speed > 0) moveMass(massFood[i]);
- }
- }
moveMass
moveMass就是移動質量,小球可以噴射身體的一部分出去,噴出去的部分變成了食物。每個噴射出去的“質量”都會被儲存在massFood列表裡。moveMass會處理這些“質量”的運動軌跡,先是噴射出去、然後減速、停止。
moveMass的程式碼比較簡單,先看看這一部分。Mass的運動分是個減速過程,噴射時Mass帶有較高的初速度,然後每一幀減速0.5,直到停止。程式碼後半部分還對Mass撞上邊緣的情況做處理。
- function moveMass(mass) {
- var deg = Math.atan2(mass.target.y, mass.target.x);
- var deltaY = mass.speed * Math.sin(deg);
- var deltaX = mass.speed * Math.cos(deg);
- mass.speed -= 0.5;
- if(mass.speed < 0) {
- mass.speed = 0;
- }
- if (!isNaN(deltaY)) {
- mass.y += deltaY;
- }
- if (!isNaN(deltaX)) {
- mass.x += deltaX;
- }
- var borderCalc = mass.radius + 5;
- if (mass.x > c.gameWidth - borderCalc) {
- mass.x = c.gameWidth - borderCalc;
- }
- if (mass.y > c.gameHeight - borderCalc) {
- mass.y = c.gameHeight - borderCalc;
- }
- if (mass.x < borderCalc) {
- mass.x = borderCalc;
- }
- if (mass.y < borderCalc) {
- mass.y = borderCalc;
- }
- }
tickPlayer
tickPlayer是處理玩家移動的核心部分,它處理了小球的移動、分身、吞食,程式碼也比較長,整體程式碼結構如下所示,首先做心跳判斷,如果客戶端太久沒有傳送心跳協議,那麼向客戶端傳送kick協議,斷開連線。其後呼叫movePlayer,處理玩家移動。
分身的過程
movePlayer計算了小球各個分身的移動過程,在遊戲中,玩家可以讓小球分身,具體過程如下。
1、玩家點選按鈕後,大球分身,分出來的小球沿著移動方向彈射
2、彈射過後,分身們逐步靠近,最終連在一起
3、經過一段時間,分身合併,又成為一個大球
分身的過程分為彈射、靠近、緊靠、合併四個階段。
怎樣表示小球
既然小球有可能分身,那麼它便不能單一的由x,y座標和質量(半徑)表示。該專案採用這樣的表示方法(虛擬碼)。如果小球沒有分身,那麼cells陣列長度為1,表示只有1個“細胞”,如果分裂成多個,則cells陣列長度增加。player的x和y表示所有cell的中心座標,由程式計算得出,target是目的地的座標,即玩家滑鼠指向的位置。每個cell都有質量,而cell的半徑由質量換算而來,計算公式為radius=4+Math.sqrt(mass)*6。
- class player
- {
- var x; //中心點座標x
- var y; //中心點座標y
- var target.x //目的地座標
- var target.y
- var[] cells//分身陣列
- }
- class cells
- {
- var x;
- var y;
- var mass; //質量
- var radius //半徑,由mass換算而來
- }
計算每個cell的目的地
由於每個cell都是獨立運動的,每一個cell都有自己的移動目的地,計算每個cell目的地的程式碼如下。
- function movePlayer(player) {
- var x =0,y =0;
- for(var i=0; i<player.cells.length; i++)
- {
- var target = {
- x: player.x - player.cells[i].x + player.target.x,
- y: player.y - player.cells[i].y + player.target.y
- };
- ……
- }
- ……
具體來說,每個cell的目的地就是總目的地的座標加上它自己相對中心點的偏移,如下圖所示。
計算移動速度
分身過程中,每個階段都有不同的速度,一開始彈射的時候速度很快,然後勻加速減少,減到一定程度後速度沿著對數函式曲線減小。
- var dist = Math.sqrt(Math.pow(target.y, 2) + Math.pow(target.x, 2));
- var deg = Math.atan2(target.y, target.x);
- var slowDown = 1;
- if(player.cells[i].speed <= 6.25) {
- slowDown = util.log(player.cells[i].mass, c.slowBase) - initMassLog + 1;
- }
- ……
- if(player.cells[i].speed > 6.25) {
- player.cells[i].speed -= 0.5;
- }
- ……
- var deltaY = player.cells[i].speed * Math.sin(deg)/ slowDown;
- var deltaX = player.cells[i].speed * Math.cos(deg)/ slowDown;
如果接近目的地,cell距離目的地只有50+player.cells<i>.radius的距離。那麼調整速度,越接近移動速度越慢。
- var dist=Math.sqrt(Math.pow(target.y,2)+Math.pow(target.x,2));
- ……
- if(dist<(50+player.cells<i>.radius)){
- deltaY*=dist/(50+player.cells<i>.radius);
- deltaX*=dist/(50+player.cells<i>.radius);
- }
在計算完y方向的移動速度deltaY和x方向的移動速度deltaX後,給cell的位置賦值,完成移動操作。
- if (!isNaN(deltaY)) {
- player.cells[i].y += deltaY;
- }
- if (!isNaN(deltaX)) {
- player.cells[i].x += deltaX;
- }
合併處理
若分身經過一定時間(其實是以距離來判斷),便需要合併,在下面的程式碼中,它會遍歷其他所有的cell,這裡分為兩種情況。(1)if(distance<radiusTotal)(2)distance<radiusTotal/1.75
- for(var j=0; j<player.cells.length; j++) {
- if(j != i && player.cells[i] !== undefined) {
- var distance = Math.sqrt(Math.pow(player.cells[j].y-player.cells[i].y,2) + Math.pow(player.cells[j].x-player.cells[i].x,2));
- var radiusTotal = (player.cells[i].radius + player.cells[j].radius);
- if(distance < radiusTotal) {
- ……(1)
- }
- else if(distance < radiusTotal / 1.75) {
- ……(2)
- }
- }
- }
- }
如果distance<radiusTotal,也就是兩個小球相互包含了,這裡又分為兩種情況(1)如果是還沒到合併時間,那麼讓他們遠離一點點。(2)如果到了合併時間,那就合併吧。
- if(player.lastSplit > new Date().getTime() - 1000 * c.mergeTimer) {
- if(player.cells[i].x < player.cells[j].x) {
- player.cells[i].x--;
- } else if(player.cells[i].x > player.cells[j].x) {
- player.cells[i].x++;
- }
- if(player.cells[i].y < player.cells[j].y) {
- player.cells[i].y--;
- } else if((player.cells[i].y > player.cells[j].y)) {
- player.cells[i].y++;
- }
- }
如果到了合併時間,且distance<radiusTotal/1.75(這個條件一定會滿足,因為前面有distance<radiusTotal的判斷),那麼就合併。合併部分的程式碼如下,其中cells表示刪除某個cell,合併了,那就兩個刪成一個。
- player.cells<i>.mass+=player.cells[j].mass;
- player.cells<i>.radius=util.massToRadius(player.cells<i>.mass);
- player.cells.splice(j,1);
邊界處理
moveplayer後面的程式碼便是做邊界處理,保證cell在邊界之內,程式碼如下所示。
- if(player.cells.length > i) {
- var borderCalc = player.cells[i].radius / 3;
- if (player.cells[i].x > c.gameWidth - borderCalc) {
- player.cells[i].x = c.gameWidth - borderCalc;
- }
- if (player.cells[i].y > c.gameHeight - borderCalc) {
- player.cells[i].y = c.gameHeight - borderCalc;
- }
- if (player.cells[i].x < borderCalc) {
- player.cells[i].x = borderCalc;
- }
- if (player.cells[i].y < borderCalc) {
- player.cells[i].y = borderCalc;
- }
- x += player.cells[i].x;
- y += player.cells[i].y;
- }
重新計算中心點
player.x和player.y是所有cell的中心點,重新計算。
- player.x = x/player.cells.length;
- player.y = y/player.cells.length;
經過上述步驟,實現了moveplayer的功能。由於判斷是否被其他cell包含的時候,採用了兩次for,效率上是否還有優化的空間呢?下一節再看看吞食食物、玩家相互吞食的處理方式。
還是放個廣告吧,筆者出版的一本書《Unity3D網路遊戲實戰》充分的講解怎樣開發一款網路遊戲,特別對網路框架設計、網路協議、資料處理等方面都有詳細的描述,相信會是一本好書的。
專欄地址:https://zhuanlan.zhihu.com/p/27313638
相關文章
- 《球球大作戰》原始碼解析(6):碰撞處理原始碼
- 《球球大作戰》原始碼解析——(9)訊息處理原始碼
- 《球球大作戰》原始碼解析(7):遊戲迴圈原始碼遊戲
- 《球球大作戰》原始碼解析——(1)執行起來原始碼
- 《球球大作戰》原始碼解析(8):訊息廣播原始碼
- 《球球大作戰》原始碼解析:伺服器與客戶端架構原始碼伺服器客戶端架構
- 《球球大作戰》優化之路(上)優化
- 《球球大作戰》優化之路(下)優化
- 淺聊球球大作戰玩法與社交
- 《球勝大本營》——耳目一新的躲避球大作戰
- 尋找伊犁鼠兔,巨人網路《球球大作戰》發起野生動物保護公益行動
- 《球球大作戰》攜手上海科技館科普援藏,“鯨奇世界”拉薩巡展開幕
- 年度鞋王迪奧AJ1說送就送 CJ最豪橫《球球大作戰》展臺
- 專案實戰:Qt球機控制工具(球機運動八個方向以及運動速度,運動指定角度QT
- # 火題小戰 A.玩個球
- 買球賽的軟體哪個好 手機線上球賽買球appAPP
- 世界盃:用Python分析熱門奪冠球隊-(附原始碼)Python原始碼
- 程式設計思路-球連球組成的群程式設計
- 中國籃球運動發展
- Python寫個“點球大戰”小遊戲Python遊戲
- 【科普】Scrum——從橄欖球爭球到敏捷開發Scrum敏捷
- 貪吃蛇大作戰JavaFx版完整原始碼Java原始碼
- 【移動開發】Checkout開源庫原始碼解析移動開發原始碼
- 合法買球的app 網上app買球算犯法嗎APP
- 世界盃買球ios 2022卡達世界盃買球appiOSAPP
- tkinter飛機大戰測試程式by李興球
- 【開源遊戲】Legends-Of-Heroes 基於ET 7.2的雙端C#(.net7 + Unity3d)多人線上英雄聯盟風格的球球大作戰遊戲。遊戲C#Unity3D
- 2153: 【例8.3】計算球的體積 球的體積公式公式
- 買球賽用什麼APP 正規買球平臺排行APP
- 華瑞IT校園籃球賽:熱血少年,球場爭鋒
- Currenxie 環球賬戶
- 物理彈球高分技巧
- 設計一款籃球經理類遊戲:球員屬性遊戲
- 十大正規買球網站 球賽在哪個平臺買網站
- 足球壓球軟體app 世界盃可以玩滾球的正規appAPP
- 十大滾球體育app 可以玩滾球的正規appAPP
- 可以玩滾球的正規app 足球滾球推薦好的appAPP
- ios買球app推薦 蘋果手機買球的app有哪些iOSAPP蘋果