Unity簡(jiǎn)單的2D物理系統(tǒng)的實(shí)現(xiàn)
遇見的問題
在使用unity創(chuàng)建2D項(xiàng)目的時(shí)候,雖然unity自己提供了例如Rigidbody2D與Collider2D等組件來提供對(duì)2D項(xiàng)目的支持.但unity的本質(zhì)依然是一個(gè)3D引擎,這里提供的2D的組件依然是基于3D引擎,無法對(duì)真正的2D項(xiàng)目提供很好的支持.
解決的辦法
unity的2D組件在為2D Platform類型的項(xiàng)目提供支持的時(shí)候,只能說功能堪堪可以使用,但是依然有很大的需要改變的地方. 比較好的辦法是自己來創(chuàng)建一個(gè)滿足2D項(xiàng)目需求的物理系統(tǒng).
一部分效果展示
性能與功能對(duì)比
在場(chǎng)景內(nèi)放置1000個(gè)僅攜帶BoxCollider2D(不含剛體)的會(huì)移動(dòng)的物體(攜帶自制的動(dòng)畫控制器與AI),此時(shí)的幀數(shù)僅有6幀.

在場(chǎng)景內(nèi)放置10000個(gè)不攜帶unity提供的碰撞體的會(huì)移動(dòng)物體,此時(shí)幀數(shù)尚且能保持15幀左右

在場(chǎng)景內(nèi)發(fā)射1100發(fā)速度固定,并且未碰撞時(shí)生命時(shí)常無限的攜帶BoxCollider2D與Kinematic類型的Rigidbody2D子彈,此時(shí)的幀數(shù)也僅有個(gè)位數(shù)的了.

在上一圖中,如果去掉剛體,此時(shí)發(fā)射1100發(fā)子彈幀數(shù)依然有100幀左右.
此時(shí)繼續(xù)追加子彈,當(dāng)子彈數(shù)目追加刀1500發(fā)時(shí),幀數(shù)僅剩個(gè)位數(shù).
如果在去掉BoxCollider2D并開啟自制物理系統(tǒng)的碰撞檢測(cè),放置5個(gè)允許子彈碰撞的運(yùn)動(dòng)物體(即每發(fā)子彈每幀需要與5個(gè)物體做碰撞檢測(cè),這個(gè)數(shù)目不包含墻壁等),此時(shí)將子彈增加到1萬發(fā)以上,依然能有很高的幀數(shù)

繼續(xù)將子彈追加刀2萬發(fā),效果如下圖

最終追加到3萬發(fā)子彈,幀數(shù)仍然有20幀以上
其他說明:這些物體允許與平臺(tái)(包括地面墻壁頂部與單向板子)碰撞并且禁止同行(或允許單向通行).
這些物體也允許被玩家碰撞,對(duì)玩家造成傷害(如果取消怪物對(duì)玩家的碰撞效果,改為攻擊行為才造成傷害,性能也會(huì)更好).
這些物體也允許被子彈碰撞,允許觸發(fā)子彈造成的傷害或者效果.
目前物體(包括玩家或者怪物)與平臺(tái)的碰撞使用射線實(shí)現(xiàn),在使用中遇見了挺多的麻煩,性能也相比之下不如自制的物理系統(tǒng).目前已有解決方案,預(yù)計(jì)會(huì)在下一個(gè)版本完全由自制物理系統(tǒng)實(shí)現(xiàn).
單向通行板
允許玩家(或者敵人,或者指定敵人)單向通行(包括跳躍)
無碰撞體與剛體的玩家與無碰撞體與剛體的敵人或者道具碰撞
允許玩家與其他不包含碰撞體或者剛體的物體互相碰撞并且觸發(fā)碰撞事件
方形碰撞器/圓形碰撞器相互碰撞的實(shí)現(xiàn)
方形碰撞器(A)與其他碰撞器(B)的碰撞檢測(cè)
取A的四個(gè)頂點(diǎn),只要任意頂點(diǎn)落入B的范圍之內(nèi),則判定為發(fā)生碰撞
圓形碰撞器(A)與方形碰撞器(B)的碰撞檢測(cè)
取B的四個(gè)頂點(diǎn),只要任意頂點(diǎn)落入A的范圍之內(nèi),則判定為發(fā)生碰撞
圓形碰撞器(A)與圓形碰撞器(B)的碰撞檢測(cè)
計(jì)算AB的圓心距即可
簡(jiǎn)易優(yōu)化
計(jì)算方形碰撞器的時(shí)候,根據(jù)物體位于的相對(duì)方位(上下左右)優(yōu)先計(jì)算左上和右下或者右上和左下兩個(gè)頂點(diǎn),可以更少的計(jì)算就能獲得結(jié)果.
動(dòng)態(tài)碰撞體A與動(dòng)態(tài)碰撞體B的X或者Y的距離大于一定值,則一定不相撞,省略后面的計(jì)算.
動(dòng)態(tài)碰撞體A與動(dòng)態(tài)碰撞體B的X或者Y的距離大于一定值,根據(jù)這個(gè)值計(jì)算兩個(gè)物體最短M幀后才會(huì)碰撞,在此期間將B移除與A的碰撞檢測(cè),在M幀之后放回.
動(dòng)態(tài)碰撞體A與純粹類靜態(tài)碰撞體B(如墻壁),同一時(shí)間一定只能有A與一個(gè)B相撞(完成純粹類靜態(tài)碰撞體融合之后),此時(shí)只用計(jì)算A在情況時(shí)候脫離B即可.
注:三角形碰撞器下個(gè)版本實(shí)現(xiàn)
純粹類靜態(tài)碰撞體(墻壁與靜態(tài)觸發(fā)點(diǎn)等)目前的難點(diǎn)
由于動(dòng)態(tài)碰撞體和道具類靜態(tài)碰撞體都是一個(gè)固定類型(大小與頂點(diǎn)等),創(chuàng)建的同類的物體碰撞體都是固定的參數(shù).
但是純粹類靜態(tài)碰撞體,本身的大小與頂點(diǎn)等參數(shù)可能每一個(gè)都不同,每單獨(dú)一個(gè)碰撞體就歸于一類實(shí)在是工作量比較大.
解決方案1
制作固定可重用的Tile,并且相鄰的Tile會(huì)融合形成一個(gè)整體,提高碰撞檢測(cè)效率.但是可能得做一部分unity editor的開發(fā),然后就是每一種瓦片就得花費(fèi)一些時(shí)間制作一下.最后則是繪制好的地圖需要做序列化然后存儲(chǔ),在游戲運(yùn)行的時(shí)候來反序列化,然后生成關(guān)卡,在關(guān)卡生成的時(shí)候會(huì)自動(dòng)完成碰撞體的注冊(cè).
解決方案2
依然使用GameObject,會(huì)為該對(duì)象附加一個(gè)碰撞體組件與一個(gè)Mono Behavior組件,這個(gè)組件會(huì)自動(dòng)獲取該對(duì)象的碰撞體,并且計(jì)算大小與頂點(diǎn)后注冊(cè)在自制物理系統(tǒng)中.
一旦注冊(cè)完成則自動(dòng)銷毀該碰撞體與腳本. 這個(gè)方案的缺點(diǎn)是沒法完成覆蓋的碰撞體的融合與拆分,如果地圖設(shè)計(jì)不合適,會(huì)提高一部分資源消耗.
非剛體移動(dòng)的問題與解決方案
角色嵌入墻體的問題
由于在我的自制物理系統(tǒng)中完全拋棄的剛體,那么所有的移動(dòng)都是瞬移(Translate)來完成的,如果角色速度過大,可能導(dǎo)致角色陷入墻體.
為什么會(huì)發(fā)生這個(gè)情況?
假設(shè)當(dāng)前是第A幀,此時(shí)角色的速度為V,正面的墻壁與角色的距離為D,且有D<V,并且D>0.
在上面的場(chǎng)景中,當(dāng)前幀角色沒有與墻壁相撞,這意味著角色當(dāng)前幀可以向當(dāng)前方向移動(dòng).如果移動(dòng)完成,此時(shí)角色與墻壁的距離為D-V=R.根據(jù)條件可知R<0,此時(shí)角色就已經(jīng)嵌入墻壁中了.
如何解決
將角色與墻壁碰撞檢測(cè)距離延長(zhǎng)為當(dāng)前速度V,當(dāng)檢測(cè)到角色與墻壁發(fā)生碰撞的時(shí)候,此時(shí)角色與墻壁的距離D必然有D<V且D>=0,那么當(dāng)前幀使V=D,下一幀到來時(shí)角色最大位移D的距離,此時(shí)角色與墻壁的距離為0,角色與墻壁相撞,當(dāng)前幀我們不在允許角色繼續(xù)向該方向移動(dòng).
斜坡移動(dòng)的問題
即使unity自身也沒有提供2D項(xiàng)目的斜坡移動(dòng)方案,這個(gè)問題當(dāng)然只能自己解決了.
在下面的例子中,我們假設(shè)角色向右移動(dòng),且使用了方形碰撞器,并且角色的移動(dòng)能夠越過高度為Y的坎或者坡度小于arctan(Y/V)的坡
如何正確的斜坡移動(dòng)(向上)
取角色右上(P1)與右下(P2)兩個(gè)頂點(diǎn),向右檢測(cè)D的距離(可以額外用一幀讓玩家完全靠近后在做接下來的操作),如果P1檢測(cè)成功但是P2檢測(cè)失敗,則說明玩家向右移動(dòng)遇見了斜坡(或者坎).
此時(shí)我們聲明一個(gè)新的點(diǎn)P3,這個(gè)點(diǎn)與P2點(diǎn)在X軸距離V個(gè)單位,在Y軸距離Y個(gè)單位.
之后由P3點(diǎn)向下檢測(cè),假設(shè)P3與斜坡(坎)的距離為M.如果M<=0,則說明坡度過大或者坎過高,因?yàn)檫@次位移會(huì)導(dǎo)致玩家進(jìn)入墻體,因此禁止位移.如果M>0,則說明玩家的該次位移沒有問題.此時(shí)玩家的位移向量為(X,Y-M)
如何正確的斜坡移動(dòng)(向下)
實(shí)際上,如果你之前物理系統(tǒng)檢測(cè)玩家是否在地板上的代碼沒有問題的話,此時(shí)玩家的角色就會(huì)正確的自動(dòng)向下移動(dòng)才對(duì).
但是如果每次一旦檢測(cè)到玩家踏空,就調(diào)用TransientToFloating()完成狀態(tài)轉(zhuǎn)化的話,這里確實(shí)會(huì)出問題.每一幀的移動(dòng)都會(huì)導(dǎo)致玩家轉(zhuǎn)換到浮空狀態(tài)然后復(fù)原站立/奔跑狀態(tài)(這會(huì)導(dǎo)致動(dòng)畫轉(zhuǎn)換鬼畜),在不恰當(dāng)?shù)膱?chǎng)合玩家會(huì)做出多次狀態(tài)轉(zhuǎn)化. 這意味著也許需要專門針對(duì)斜坡向下的移動(dòng)來完成指定的功能. 這部分問題預(yù)計(jì)下一個(gè)版本解決.