如果物理實體有很多,那每個實體都要判斷和其他實體是否發生碰撞。有沒有比較簡便的方法呢,可以使用二進位與位掩碼,設置實體的類別,然後用位掩碼計算來得到兩者是否發生碰撞的結果。另外LOVE還提供了一個組別的功能,可以直接跳過計算結果,強制兩者發生碰撞和強制不發生碰撞 ...
有效的碰撞處理
只用IF判斷
假設在一個物理世界,不希望兩個同類實體發生碰撞,那麼
local begin_contact_callback = function(fixture_a, fixture_b)
local entity_a_type = fixture_a:getUserData()
local entity_b_type = fixture_b:getUserData()
-- 如果碰撞的兩個實體不同
if entity_a_type ~= entity_b_type then
--
end
end
但是如果新加了可互動元素,如一種道具,只能跟玩家實體碰撞,那麼
local begin_contact_callback = function(fixture_a, fixture_b)
local a = fixture_a:getUserData()
local b = fixture_b:getUserData()
if (a == 'powerup' and b == 'player') or (a == 'player' and b == 'powerup') then
--
elseif a ~= b and a ~= 'powerup' and b~= 'powerup' then
--
end
end
如果再加上其他東西,比如只有玩家可以推動的方塊,代碼量會飛速膨脹
⭐ 使用二進位和位掩碼
假設游戲已經有幾十種實體,我們可以根據實體在游戲內的作用歸為五類,給每種實體綁定類別和位掩碼
實體類別 | 類別對應的二進位 | 位掩碼 |
---|---|---|
場景(如雲、花) | 0000 | 0000 |
玩家 | 0001 | 1110 |
道具 | 0010 | 1001 |
敵人 | 0100 | 1001 |
牆體 | 1000 | 1111 |
比如玩家實體和敵人實體,在函數中我們提取玩家的類別和敵人的位掩碼做位與運算
0001 玩家 類別
1001 敵人 位掩碼
----
0001 不為0 發生碰撞
再舉個例子,敵人碰撞到了道具
0100 敵人 類別
1001 道具 位掩碼
----
0000 為0 不發生碰撞
因此,在上面表格的情況下
- 場景實體沒有被分配類別(要保證某1位為1),不會和任何實體發生碰撞
- 玩家實體不能相互碰撞,能與道具、敵人、牆體發生碰撞
- 道具實體能跟牆體、玩家發生碰撞
- 敵人實體能跟牆體、玩家發生碰撞
- 牆體實體能跟所有類別發生碰撞(除場景)
註:如果實體不能跟牆體發生碰撞,那麼一旦生成就會直接無限墜落至無底洞
綁定到實體
先生成實體的類別二進位和位掩碼,比如在squre.lua
中,創建了一個實體squre
某種情況下,實體可以屬於多個類別,比如
1011
,這個實體既是牆體也是敵人、玩家,雖然邏輯上是不可能的,但相應的碰撞處理均會發生⭐ 兩個蘋果,第一個蘋果可以只是場景擺件,僅與地形碰撞;第二個蘋果可以是道具,與地形和玩家均可碰撞
square.category = tonumber('0001', 2)
square.mask = tonumber('1110', 2)
square.group = 0
綁定到fixture
上,由於設置了類別和位掩碼,組號填0意味著沒有組別
square.fixture:setFilterData(square.category, square.mask, square.group)
-- Fixture:setCategory, Fixture:setMask or Fixture:setGroupIndex
LOVE 引擎最多支持16位二進位的類別和位掩碼,即0000000000000000
⭐ fixture創建時預設類別為1D,位掩碼為65535D,組別均為0
代碼與效果
-- entities/block.lua
local world = require 'world'
return function(x, y, width, height, rigidbody, category, bitmask, group)
e = {}
e.body = love.physics.newBody(world, x, y, rigidbody)
e.body:setMass(32)
e.shape = love.physics.newRectangleShape(width, height)
e.fixture = love.physics.newFixture(e.body, e.shape)
e.fixture:setFilterData(category, bitmask, group)
function e:draw()
love.graphics.polygon('line', self.body:getWorldPoints(self.shape:getPoints()))
local x, y = self.body:getPosition()
love.graphics.print({{0, 1, 0}, (category .. '+' .. bitmask) or group}, x, y, nil)
end
return e
end
下麵我們定義了兩個類別,分別是001
和010
local entities = {block(400, 400, 300, 10, 'static', '001', '011', 0),
block(400, 300, 50, 50, 'dynamic', '011', '011', 0),
block(400, 200, 40, 40, 'dynamic', '010', '011', 0),
block(400, 100, 30, 30, 'dynamic', '010', '011', 0)}
修改第二個和第三個方塊的位掩碼
local entities = {block(400, 400, 300, 10, 'static', '001', '011', 0),
block(400, 300, 50, 50, 'dynamic', '011', '000', 0),
block(400, 200, 40, 40, 'dynamic', '010', '001', 0),
block(400, 100, 30, 30, 'dynamic', '010', '011', 0)}
⭐ 組別
我們可以為各個實體設置組別,同組別將直接無視類別與位掩碼的計算結果,同組別且正數總是會碰撞,同組別且負數總不會碰撞。
e.fixture:setFilterData( xx , xx , group)
-- e.fixture:setGroupIndex(group)
考慮如下代碼
local entities = {block(400, 400, 300, 10, 'static', '001', '011', 0),
block(400, 300, 50, 50, 'dynamic', '010', '001', 0),
block(400, 200, 40, 40, 'dynamic', '010', '001', 0)}
第二個方塊跟第三個方塊不會碰撞,設置組別為1
local entities = {block(400, 400, 300, 10, 'static', '001', '011', 0),
block(400, 300, 50, 50, 'dynamic', '010', '001', 1),
block(400, 200, 40, 40, 'dynamic', '010', '001', 1)}
再考慮如下代碼,
local entities = {block(400, 400, 300, 10, 'static', '001', '011', 0),
block(400, 300, 50, 50, 'dynamic', '010', '011', 1),
block(400, 200, 40, 40, 'dynamic', '010', '011', 1)}
第二個方塊跟第三個方塊會碰撞,將組別設置為-1,即使算出來要發生碰撞,由於相同組且是負數,永遠也不會碰撞
local entities = {block(400, 400, 300, 10, 'static', '001', '011', 0),
block(400, 300, 50, 50, 'dynamic', '010', '011', -1),
block(400, 200, 40, 40, 'dynamic', '010', '011', -1)}