unity3d實現像素遊戲的精確碰撞判定

-- 檢測碰撞物,如果發生碰撞則進行位移
function LColliderBDY:BDYFixedUpdate(velocity)
	local isGround = false
	local isWall = false

	-- 檢測和什麼碰,2d碰撞範圍一般比實際要大,因爲AABB要大一點,爲了精確碰撞,需要自己實現
	local contactColliders = CS.Tools.Instance:Collider2DOverlapCollider(self.collider, self.filter) -- 這個函數其實Collider2D.OverlapCollider,用來手動檢測碰撞,這邊因爲lua的緣故封裝了一下

	-- 最終位移座標
	local finalOffset_x = 0
	local finalOffset_y = 0
	for p, k in pairs(contactColliders) do
		if self.collider.bounds:Intersects(k.bounds) then

			local up, down, left, right = false, false, false, false

			local go = k.attachedRigidbody.gameObject
			if go:GetComponent(typeof(CS.UnityEngine.Rigidbody2D)) ~= nil and go.name == "test" then -- 如果是地圖塊
				local name = utils.split(k.name, ",")
				local num = tonumber(name[#name]) -- 地圖塊最後一個數字作爲bit

				if num | 14 == 15 then --位操作,算出這個方塊朝哪個方向進行碰撞,一個方塊可以有多個碰撞方向,這部分隨意設計,只需要能知道這個collider的判定方向,用layermask什麼都行
					up = true
				end
				if num | 13 == 15 then --位操作
					down = true
				end
				if num | 11 == 15 then --位操作
					left = true
				end
				if num | 7 == 15 then --位操作
					right = true
				end
			elseif go:GetComponent(typeof(CS.XLuaTest.LuaBehaviour)) ~= nil then -- 是遊戲object,則只允許左右進行碰撞,LuaBehaviour是用來調用lua的腳本,雨女無瓜
				left = true
				right = true
			else
				return false, false
			end

			local menseki = utils.getBoundsIntersectsArea(self.collider.bounds, k.bounds)
			if menseki.magnitude > 0 then -- 無視多少面積設置,先不設

				-- 算2個collider之間距離,主要是爲了法線
				local cd2d = self.collider:Distance(k)

				local a =  CS.UnityEngine.Vector3(cd2d.pointA.x, cd2d.pointA.y, 0)
				local b =  CS.UnityEngine.Vector3(cd2d.pointB.x, cd2d.pointB.y, 0)
				local normal =  -CS.UnityEngine.Vector3(cd2d.normal.x, cd2d.normal.y, 0)
				CS.UnityEngine.Debug.DrawLine(a, a + normal, CS.UnityEngine.Color.red)
				CS.UnityEngine.Debug.DrawLine(b, b + normal, CS.UnityEngine.Color.yellow)

				-- 做碰撞法線與行進方向的點積
				-- local projection = CS.UnityEngine.Vector2.Dot(velocity.normalized, normal) -- 沒用到,有需要可以自己看情況加

				local offset_x = 0
				local offset_y = 0

				-- 左移,右移
				if self.collider.bounds.center.x < k.bounds.center.x then
					if left and CS.UnityEngine.Vector2.Dot(velocity.normalized, CS.UnityEngine.Vector2(-1, 0)) <= 0 then -- 如果碰撞朝向與行進方向相反,則求出位移座標

						offset_x = -menseki.x
					end
				else
					if right and CS.UnityEngine.Vector2.Dot(velocity.normalized, CS.UnityEngine.Vector2(1, 0)) <= 0 then
						offset_x = menseki.x
					end
				end
				-- 上移,下移
				if self.collider.bounds.center.y > k.bounds.center.y then
					if up and CS.UnityEngine.Vector2.Dot(velocity.normalized, CS.UnityEngine.Vector2(0, 1)) <= 0 then
						offset_y = menseki.y
					end
				else
					if down and CS.UnityEngine.Vector2.Dot(velocity.normalized, CS.UnityEngine.Vector2(0, -1)) <= 0 then
						offset_y = -menseki.y
					end
				end

				if (up or down) and (left or right) then -- 如果同時滿足上下和左右方向同時存在的情況,則根據碰撞方向來篩選掉另一個軸的位移
					offset_x = offset_x * math.abs(normal.x)
					offset_y = offset_y * math.abs(normal.y)
				end

				-- 留下最小位移座標
				if velocity.x > 0 then
					if offset_x < finalOffset_x then
						finalOffset_x = offset_x
					end
				else
					if offset_x > finalOffset_x then
						finalOffset_x = offset_x
					end
				end

				if velocity.y > 0 then
					if offset_y < finalOffset_y then
						finalOffset_y = offset_y
					end
				else
					if offset_y > finalOffset_y then
						finalOffset_y = offset_y
					end
				end

				if go:GetComponent(typeof(CS.UnityEngine.Rigidbody2D)) ~= nil and go.name == "test" then -- 判斷是不是撞到地面,這樣寫不好,以後再優化
					if finalOffset_x ~= 0 then
						isWall = true
					end
					if finalOffset_y > 0 then
						isGround = true
					end
				end
			end
		end
	end

	-- 更新自身位置
	self.collider.attachedRigidbody.position = self.collider.attachedRigidbody.position + CS.UnityEngine.Vector2(finalOffset_x, finalOffset_y)

	return isGround, isWall
end

直接上代碼,在fixedupdate裏調用這個代碼,代碼是lua寫的

記得rigidbody2d設爲Kinematic,useFullKinematicContacts設爲false,因爲我們自己實現碰撞,不需要unity的碰撞oncolliderenter之類的,把這個關了可能可以提高性能8

講一下思路

因爲unity用的box2d的aabb碰撞觸發範圍比定義的範圍要大,所以對於像素遊戲來說,會有像素差,會影響實際畫面,比如站在地上有時候會離地騰空1個像素高

況且像素遊戲基本是方塊判定,也用不太到什麼摩擦力,反彈,幾何形狀碰撞等,簡單的自己也能加,所以自己實現碰撞是一個不錯的選擇

用Collider2D.OverlapCollider詳見官方API,來檢測碰撞物,獲得碰撞物允許碰撞的朝向(上,下,左,右),自己的collider和檢測到的collider的bounds互相檢測一下,相交的話

看自己在對象的位置,以及自己移動方向是否面朝對象允許碰撞的朝向(點積小於0)算出碰撞面積,也就是需要位移的x軸和y軸的偏移量,留下基於自己位置和方向的最小的偏移量,這些用移動方向法線來寫可能代碼會好看點,最後更新rigidbox的position

只是提供思路,沒了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章