[Lua][Love Engine] 有效碰撞處理の類別與位掩碼 | fixture:setFilterData

2023-08-22 06:00:53

有效的碰撞處理

只用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

下面我們定義了兩個類別,分別是001010

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)}
love_RDKN36y4Zd

修改第二個和第三個方塊的位掩碼

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)}
love_WhSDZWlQMQ

⭐ 組別

我們可以為各個實體設定組別,同組別將直接無視類別與位掩碼的計算結果,同組別且正數總是會碰撞,同組別且負數總不會碰撞。

    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)}
image-20230821192630627

再考慮如下程式碼,

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)}
image-20230821192825989