《球球大作戰》原始碼解析(6):碰撞處理
系列文章
《球球大作戰》原始碼解析——(1)執行起來
《球球大作戰》原始碼解析:伺服器與客戶端架構
《球球大作戰》原始碼解析:移動演算法
《球球大作戰》原始碼解析(6):碰撞處理
《球球大作戰》原始碼解析(7):遊戲迴圈
《球球大作戰》原始碼解析(8):訊息廣播
小球移動過程中,可能會碰到食物、其他玩家和病毒,如果碰到食物,則吞食食物,質量增加;如果碰到其他玩家,體積大的吃掉體積小的,如果吞食病毒,分身解體。tickPlayer中有一段遍歷所有cell的程式碼,它處理了遊戲中的碰撞事件。
- for(var z=0; z<currentPlayer.cells.length; z++) {
- ……
- }
程式碼中定義了一個SAT.Circle型別的playerCircle,它指的是以currentCell.x和currentCell.y為圓心,currentCell.radius為半徑的圓。後續將會用這個圓形去和場景中的物體做碰撞檢測。
- var V = SAT.Vector; //一開始定義
- var C = SAT.Circle;
- var playerCircle = new C(
- new V(currentCell.x, currentCell.y),
- currentCell.radius
- );
吞食食物
吞食食物的程式碼如下所示,foodEaten表示被吃掉的食物列表,程式對food列表的所有食物執行funcFood方法,即是使用 SAT.pointInCircle看看食物是不是被包含在玩家的面積之內。然後再對每個foodEaten執行deleteFood方法,即刪除掉這個食物。food.map(funcFood)表示對food陣列的每個元素傳遞給指定的函式,並返回一個陣列,該陣列由函式的返回值構成。funcFood返回的是玩家是否吞食了食物,形成true/false的列表。reduce() 方法接收一個函式作為累加器,陣列中的每個值(從左到右)開始縮減,最終為一個值,是ES5中新增的一個陣列逐項處理方法。針對map(funcFood)返回的true/false列表,如果該食物被包含(為true),則將它新增到返回值中。
- var foodEaten = food.map(funcFood)
- .reduce( function(a, b, c) { return b ? a.concat(c) : a; }, []);
- foodEaten.forEach(deleteFood);
- function funcFood(f) {
- return SAT.pointInCircle(new V(f.x, f.y), playerCircle);
- }
- function deleteFood(f) {
- food[f] = {};
- food.splice(f, 1);
- }
看到這裡作者還是比較失望的,因為本來期待有更好的方法,減少計算量。像這樣兩兩判斷誰不會啊!
吞食massFood
massFood是玩家噴射出的“質量”處理過程與吞食食物類似,獲取被吃掉的mass的列表massEaten,然後從massFood列表中刪掉它。
- var massEaten = massFood.map(eatMass)
- .reduce(function(a, b, c) {return b ? a.concat(c) : a; }, []);
- ……
- var masaGanada = 0;
- for(var m=0; m<massEaten.length; m++) {
- masaGanada += massFood[massEaten[m]].masa;
- massFood[massEaten[m]] = {};
- massFood.splice(massEaten[m],1);
- for(var n=0; n<massEaten.length; n++) {
- if(massEaten[m] < massEaten[n]) {
- massEaten[n]--;
- }
- }
- }
吞食病毒
如果不小心吞食了病毒,玩家會被迫分身,程式碼如下所示。
- var virusCollision = virus.map(funcFood)
- .reduce( function(a, b, c) { return b ? a.concat(c) : a; }, []);
- if(virusCollision > 0 && currentCell.mass > virus[virusCollision].mass) {
- sockets[currentPlayer.id].emit('virusSplit', z);
- }
下圖為吞食病毒導致的分身前後,綠色圓形為病毒,大球aa吞食病毒後,立即分解為兩個小球。
增加質量
如果玩家吞食了食物或massfood,小球會變大,相關程式碼如下。
- if(typeof(currentCell.speed) == "undefined")
- currentCell.speed = 6.25;
- masaGanada += (foodEaten.length * c.foodMass);
- currentCell.mass += masaGanada;
- currentPlayer.massTotal += masaGanada;
- currentCell.radius = util.massToRadius(currentCell.mass);
- playerCircle.r = currentCell.radius;
吞食其他玩家
接下來是使用四叉樹計算玩家之間的碰撞,筆者就在想,前面都用了那麼多個for迴圈了,這可是每個玩家都對food,massfood,病毒都for一次啊。這裡用四叉樹意義很大麼?為什麼不一開始就都用呢?
先使用tree.put構建四叉樹,四叉樹可以把判斷的範圍變小,把每個玩家都放進去,然後通過tree.get(currentPlayer, check)獲取發生碰撞的玩家。最後再對每個可能發生碰撞的玩家執行collisionCheck。
- tree.clear();
- users.forEach(tree.put);
- var playerCollisions = [];
- var otherUsers = tree.get(currentPlayer, check);
- playerCollisions.forEach(collisionCheck);
接下來看看check,它遍歷玩家身上每個cells,然後使用SAT.testCircleCircle測試是否圓在圓內,如果是的話返回一個response結構,該結構裡面包含對方玩家的id、name、座標等資訊。然後構建playerCollisions陣列。
- function check(user) {
- for(var i=0; i<user.cells.length; i++) {
- if(user.cells[i].mass > 10 && user.id !== currentPlayer.id) {
- var response = new SAT.Response();
- var collided = SAT.testCircleCircle(playerCircle,
- new C(new V(user.cells[i].x, user.cells[i].y), user.cells[i].radius),
- response);
- if (collided) {
- response.aUser = currentCell;
- response.bUser = {
- id: user.id,
- name: user.name,
- x: user.cells[i].x,
- y: user.cells[i].y,
- num: i,
- mass: user.cells[i].mass
- };
- playerCollisions.push(response);
- }
- }
- }
- return true;
- }
然後是對發生碰撞的玩家執行邏輯,把它吃掉。
- function collisionCheck(collision) {
- if (collision.aUser.mass > collision.bUser.mass * 1.1 && collision.aUser.radius > Math.sqrt(Math.pow(collision.aUser.x - collision.bUser.x, 2) + Math.pow(collision.aUser.y - collision.bUser.y, 2))*1.75) {
- console.log('[DEBUG] Killing user: ' + collision.bUser.id);
- console.log('[DEBUG] Collision info:');
- console.log(collision);
- var numUser = util.findIndex(users, collision.bUser.id);
- if (numUser > -1) {
- if(users[numUser].cells.length > 1) {
- users[numUser].massTotal -= collision.bUser.mass;
- users[numUser].cells.splice(collision.bUser.num, 1);
- } else {
- users.splice(numUser, 1);
- io.emit('playerDied', { name: collision.bUser.name });
- sockets[collision.bUser.id].emit('RIP');
- }
- }
- currentPlayer.massTotal += collision.bUser.mass;
- collision.aUser.mass += collision.bUser.mass;
- }
- }
這裡是筆者看不懂還是四叉樹沒啥作用呢?在這裡用四叉樹和直接兩次迴圈有區別麼?check是固定返回true的啊!!!!!下面的四叉樹說明,可以證明這裡用四叉樹是無效的。
四叉樹
四叉樹空間索引原理及其實現 - 心如止水-GISer的成長之路 - CSDN部落格
四叉樹索引的基本思想是將地理空間遞迴劃分為不同層次的樹結構。它將已知範圍的空間等分成四個相等的子空間,如此遞迴下去,直至樹的層次達到一定深度或者滿足某種要求後停止分割。四叉樹的結構比較簡單,並且當空間資料物件分佈比較均勻時,具有比較高的空間資料插入和查詢效率,因此四叉樹是GIS中常用的空間索引之一。常規四叉樹的結構如圖所示,地理空間物件都儲存在葉子節點上,中間節點以及根節點不儲存地理空間物件。
四叉樹對於區域查詢,效率比較高。但如果空間物件分佈不均勻,隨著地理空間物件的不斷插入,四叉樹的層次會不斷地加深,將形成一棵嚴重不平衡的四叉樹,那麼每次查詢的深度將大大的增多,從而導致查詢效率的急劇下降。
nodejs的 simple-quadtree介紹
程式碼中的tree.get、tree.put等方法用到了nodejs的simple-quadtree庫,這裡做個簡單介紹。
simple-quadtree
simple-quadtree是一套小型的四叉樹實現,每棵樹支援 put、 get、remove 和 clear四種操作。四叉樹的節點物件必須包含x,y座標,以及長度寬度w、h。
Put方法
Put方法可以將節點放入四叉樹裡面,例如:
- qt.put({x: 5, y: 5, w: 0, h: 0, string: 'test'});
Get方法
Get方法會迭代取出四叉樹節點,然後呼叫回撥函式,如下所示。
- qt.get({x:0, y: 0, w: 10, h: 10}, function(obj) {
- // obj == {x: 5, y: 5, w: 0, h: 0, string: 'test'}
- });
如果回撥函式返回true,迭代會一直進行下去,如果回撥函式返回false,則迭代停止。由於原始碼中的check方法總是返回true,所以這裡使用四叉樹並沒能減少計算量,相反比for迴圈多了構建樹的計算。沒什麼用!
還是放個廣告吧,筆者出版的一本書《Unity3D網路遊戲實戰》充分的講解怎樣開發一款網路遊戲,特別對網路框架設計、網路協議、資料處理等方面都有詳細的描述,相信會是一本好書的。
作者:羅培羽
原地址:https://zhuanlan.zhihu.com/p/28107508
相關文章
- 《球球大作戰》原始碼解析——(9)訊息處理原始碼
- 《球球大作戰》原始碼解析(7):遊戲迴圈原始碼遊戲
- 《球球大作戰》原始碼解析:移動演算法原始碼演算法
- 《球球大作戰》原始碼解析——(1)執行起來原始碼
- 《球球大作戰》原始碼解析(8):訊息廣播原始碼
- 《球球大作戰》原始碼解析:伺服器與客戶端架構原始碼伺服器客戶端架構
- 《球球大作戰》優化之路(上)優化
- 《球球大作戰》優化之路(下)優化
- 淺聊球球大作戰玩法與社交
- 《球勝大本營》——耳目一新的躲避球大作戰
- 《球球大作戰》攜手上海科技館科普援藏,“鯨奇世界”拉薩巡展開幕
- 年度鞋王迪奧AJ1說送就送 CJ最豪橫《球球大作戰》展臺
- 尋找伊犁鼠兔,巨人網路《球球大作戰》發起野生動物保護公益行動
- snabbdom原始碼解析(七) 事件處理原始碼事件
- 原始碼解析Java Attach處理流程原始碼Java
- 設計一款籃球經理類遊戲:球員屬性遊戲
- # 火題小戰 A.玩個球
- 設計一款籃球經理類遊戲(三):球隊的設計遊戲
- 買球賽的軟體哪個好 手機線上球賽買球appAPP
- SpringBoot原始碼解析-ExceptionHandler處理異常的原理Spring Boot原始碼Exception
- Handler訊息處理機制原始碼解析 上原始碼
- 世界盃:用Python分析熱門奪冠球隊-(附原始碼)Python原始碼
- ThinkPHP6 原始碼分析之請求處理PHP原始碼
- 程式設計思路-球連球組成的群程式設計
- Python寫個“點球大戰”小遊戲Python遊戲
- 【科普】Scrum——從橄欖球爭球到敏捷開發Scrum敏捷
- Mybatis原始碼之美:3.4.解析處理parameterMap元素MyBatis原始碼
- 貪吃蛇大作戰JavaFx版完整原始碼Java原始碼
- 合法買球的app 網上app買球算犯法嗎APP
- 世界盃買球ios 2022卡達世界盃買球appiOSAPP
- tkinter飛機大戰測試程式by李興球
- Spring6 當中的 Bean 迴圈依賴的詳細處理方案+原始碼解析SpringBean原始碼
- 實況足球戰略版《實況球會經理》今日全平臺公測!
- 設計一款籃球經理類遊戲(三):球員升級、裝備、技能與組合遊戲
- spring原始碼深度解析— IOC 之 迴圈依賴處理Spring原始碼
- 【開源遊戲】Legends-Of-Heroes 基於ET 7.2的雙端C#(.net7 + Unity3d)多人線上英雄聯盟風格的球球大作戰遊戲。遊戲C#Unity3D
- 氣球塔防6 for Mac(BloonsTD6 塔防闖關遊戲)Mac遊戲
- 2153: 【例8.3】計算球的體積 球的體積公式公式