碰撞檢測方案(四叉樹)
最近公司要作一個相似於球球大做戰的項目,遊戲運行中會不斷進行大量的碰撞檢測,須要適合的碰撞檢測方案,因此在網上搜索了一下資料。下面是找到的幾種方案。node
方案一:循環遍歷:ruby
- 在遊戲場景運行期間,會每幀調用update(),在update函數中循環遍歷場景中全部的物體,經過計算物體中心點的距離來判斷物體是否碰撞。
- 試想若是場景中有N個物體,對每兩個物體都進行碰撞檢測,那時間複雜度就有N^2,效率低。並且實際上一個位於場景左下角的物體與一個位於場景右上角的物體明顯不可能發生碰撞,
方案二:使用cocos2dx自帶的chipmunk物理引擎:數據結構
- 能夠在scene添加物理世界,將全部的物體都設置成物理剛體,經過chipmunk的碰撞回調實現碰撞檢測
- 以前作遊戲遇到過剛體速度太快出現碰撞穿透現象,因此這次不採用此方案。
方案三:四叉樹優化碰撞檢測:函數
- 使用四叉樹空間索引,減小須要遍歷的物體數量,大大減小了計算量。
說得太多主要仍是由於我沒有使用過四叉樹~ 想嘗試一下~
四叉樹原理
網上能夠查到許多關於四叉樹的資料。四叉樹是一個每一個父節點都具備四個子節點的樹狀數據結構。咱們將屏幕劃分爲四個區域,用於區分處於不一樣位置的物體,四叉樹的四個節點正合適表示這四個區域。
方便起見將四塊區域命名爲象限1、2、3、四。優化
咱們將徹底處於某一個象限的物體存儲在該象限對應的子節點下,固然,也存在跨越多個象限的物體,咱們將它們存在父節點中,以下圖所示:lua
若是某個象限內的物體的數量過多,它會一樣會分裂成四個子象限,以此類推:spa
lua實現四叉樹
local QuadTree = {}
QuadTree.__index = QuadTree
QuadTree.MAX_OBJECTS = 10 QuadTree.MAX_LEVELS = 5 local function checkbounds(quadrant, box) local list = { cc.p(box.x + box.width, box.y + box.height), cc.p(box.x + box.width, box.y + box.height), cc.p(box.x + box.width, box.y + box.height), cc.p(box.x + box.width, box.y + box.height), } for _, pos in pairs(list) do if not (pos.x >= quadrant.x and pos.x <= quadrant.x + quadrant.width and pos.y >= quadrant.y and pos.y <= quadrant.y + quadrant.height) then return false end end return true end --bounds 屏幕範圍 --level 四叉樹層級 function QuadTree:new(bounds, level) local o = {} o = setmetatable(o,QuadTree) o.objects = {} o.nodes = {} o.level = level and level or 0 o.bounds = bounds return o end -- 獲取物體對應的象限序號,以屏幕中心爲界限,切割屏幕: -- - 右上:象限一 -- - 左上:象限二 -- - 左下:象限三 -- - 右下:象限四 function QuadTree:getIndex(node) local rect = node:getBoundingBox() if not checkbounds(self.bounds, rect) then return nil end local x = self.bounds.x local y = self.bounds.y local width = self.bounds.width / 2 local height = self.bounds.height / 2 local quadrant1 = cc.rect(x + width, y + height, width, height) local quadrant2 = cc.rect(x, y + height, width, height) local quadrant3 = cc.rect(x, y, width, height) local quadrant4 = cc.rect(x + width, y, width, height) if checkbounds(quadrant1, rect) then return 1 elseif checkbounds(quadrant2, rect) then return 2 elseif checkbounds(quadrant3, rect) then return 3 elseif checkbounds(quadrant4, rect) then return 4 end --若是物體跨越多個象限,則放回-1 return - 1 end function QuadTree:split() if #self.nodes > 0 then return end local x = self.bounds.x local y = self.bounds.y local width = self.bounds.width / 2 local height = self.bounds.height / 2 local tree1 = QuadTree:new(cc.rect(x + width, y + height, width, height), self.level + 1) local tree2 = QuadTree:new(cc.rect(x, y + height, width, height), self.level + 1) local tree3 = QuadTree:new(cc.rect(x, y, width, height), self.level + 1) local tree4 = QuadTree:new(cc.rect(x + width, y, width, height), self.level + 1) table.insert(self.nodes, tree1) table.insert(self.nodes, tree2) table.insert(self.nodes, tree3) table.insert(self.nodes, tree4) end -- 插入功能: -- - 若是當前節點[ 存在 ]子節點,則檢查物體到底屬於哪一個子節點,若是能匹配到子節點,則將該物體插入到該子節點中 -- - 若是當前節點[ 不存在 ]子節點,將該物體存儲在當前節點。隨後,檢查當前節點的存儲數量,若是超過了最大存儲數量,則對當前節點進行劃分,劃分完成後,將當前節點存儲的物體從新分配到四個子節點中。 function QuadTree:insert(node) --若是該節點下存在子節點 print("!!!!!!!!!!!!!!!!!!!!!!!!",tolua.type(node)) if #self.nodes > 0 then local index = self:getIndex(node) if index and index ~= - 1 then self.nodes[index]:insert(node) return end end --不然存儲在當前節點下 table.insert(self.objects, node) --若是當前節點存儲的數量超過了MAX_OBJECTS if #self.nodes <= 0 and #self.objects > QuadTree.MAX_OBJECTS and self.level < QuadTree.MAX_LEVELS then self:split() for i = #self.objects, 1, - 1 do local index = self:getIndex(self.objects[i]) if index and index ~= - 1 then self.nodes[index]:insert(self.objects[i]) table.remove(self.objects, i) end end end end -- 檢索功能: -- 給出一個物體對象,該函數負責將該物體可能發生碰撞的全部物體選取出來。該函數先查找物體所屬的象限,該象限下的物體都是有可能發生碰撞的,而後再遞歸地查找子象限... function QuadTree:retrieve(node) local result = {} if #self.nodes > 0 then local index = self:getIndex(node) if index and index ~= - 1 then local list = self.nodes[index]:retrieve(node) for _,value in pairs(list) do table.insert(result, value) end elseif index and index == - 1 then local x = self.bounds.x local y = self.bounds.y local width = self.bounds.width / 2 local height = self.bounds.height / 2 local quadrant1 = cc.rect(x + width, y + height, width, height) local quadrant2 = cc.rect(x, y + height, width, height) local quadrant3 = cc.rect(x, y, width, height) local quadrant4 = cc.rect(x + width, y, width, height) local rect = node:getBoundingBox() if checkbounds(quadrant1, rect) then local list = self.nodes[1]:retrieve(node) for _,value in pairs(list) do table.insert(result, value) end end if checkbounds(quadrant2, rect) then local list = self.nodes[2]:retrieve(node) for _,value in pairs(list) do table.insert(result, value) end end if checkbounds(quadrant3, rect) then local list = self.nodes[3]:retrieve(node) for _,value in pairs(list) do table.insert(result, value) end end if checkbounds(quadrant4, rect) then local list = self.nodes[4]:retrieve(node) for _,value in pairs(list) do table.insert(result, value) end end end end for _,value in pairs(self.objects) do table.insert(result, value) end return result end --判斷矩形是否在象限範圍內 function QuadTree:isInner(node, bounds) local rect = node:getBoundingBox() return rect.x >= bounds.x and rect.x + rect.width <= bounds.x + bounds.width and rect.y >= bounds.y and rect.y + rect.height <= bounds.y + bounds.height end -- 動態更新: -- 從根節點深刻四叉樹,檢查四叉樹各個節點存儲的物體是否依舊屬於該節點(象限)的範圍以內,若是不屬於,則從新插入該物體。 function QuadTree:refresh(root) root = root or self for i = #self.objects, 1, - 1 do local node = self.objects[i] local index = self:getIndex(node) if index then --若是矩形不屬於該象限,則將該矩形從新插入 if not self:isInner(node, self.bounds) then if self ~= root then root:insert(self.objects[i]) table.remove(self.objects, i) end -- 若是矩形屬於該象限 且 該象限具備子象限,則 -- 將該矩形安插到子象限中 elseif #self.nodes > 0 then self.nodes[index]:insert(self.objects[i]) table.remove(self.objects, i) end end end for i = 1, #self.nodes do self.nodes[i]:refresh(root) end end return QuadTree
後期實際運用到項目裏面的時候,發現還缺乏了移除節點,以及清除整個四叉樹的接口
四叉樹中object的結構我存的是CCNODEcode
--移除四叉樹節點中的object
function QuadTree:remove(removeNode) for i = #self.objects, 1, - 1 do local node = self.objects[i] if node and node:getTag() == removeNode:getTag() then table.remove(self.objects, i) return true end end for i = 1, #self.nodes do if self.nodes[i]:remove(removeNode) then return true end end return false end --清理四叉樹 function QuadTree:clear() for i = #self.objects, 1, - 1 do table.remove(self.objects,i) end self.objects = {} for i = 1, #self.nodes do self.nodes[i]:clear() end end