Cocos 3.x 3D物理系統(tǒng) 射線檢測

參考
Cocos Creator 3D 物理模塊介紹
Cocos Creator 3.0 3D 物理講解
選擇適合你項目的物理系統(tǒng)

一、3D 物理簡介

Cocos Creator 3.0 目前支持輕量的碰撞檢測系統(tǒng) builtin 和具有物理模擬的物理引擎 cannon.js ,以及功能完善強(qiáng)大的 bullet 的 asm.js/wasm 版本(ammo.js),并為開發(fā)者提供了高效統(tǒng)一的組件化工作流程和便捷的使用方法。


image.png
1.碰撞檢測: builtin

builtin 僅有碰撞檢測的功能,相對于其它的物理引擎,它沒有復(fù)雜的物理模擬計算。如果您的項目不需要這一部分的物理模擬,那么可以考慮使用 builtin,這將使得游戲的包體更小。

若使用 builtin 進(jìn)行開發(fā),請注意以下幾點:

  • builtin 只有 trigger 類型的事件。
  • Collider 中的 isTrigger 無論值真假,都為運(yùn)動學(xué)類型的觸發(fā)器。
2.物理引擎: cannon.js

cannon.js 是一個開源的物理引擎,它使用 js 語言開發(fā)并實現(xiàn)了比較全面的物理功能,如果您的項目需要更多復(fù)雜的物理功能,那么您可以考慮使用它。 cannon.js 模塊大小約為 141KB

3.物理引擎: ammo.js

ammo.jsbullet 物理引擎的 asm.js/wasm 版本,由 emscripten 工具編譯而來。 Bullet 具有完善的物理功能,以及更佳的性能,未來我們也將在此投入更多工作。

需要注意的是,目前 ammo.js 模塊具有 1.5MB 左右的大小。

4. 不使用物理

若不需要用到任何物理相關(guān)的組件和接口,可以取消黃色框的勾選,這樣在發(fā)布時將有更小的包體。

:若處于取消勾選的狀態(tài),項目將不可以使用物理相關(guān)的組件和接口,否則運(yùn)行時將會報錯。

5.對比
  • builtin 極其輕量的碰撞檢測系統(tǒng),僅支持盒、球、膠囊體形狀和觸發(fā)事件
  • cannon.js 純 js 開發(fā)的物理引擎,支持大部分特性,易于擴(kuò)展,但性能不夠好,包體約為 141 KB
  • ammo.js 由 bullet 物理引擎編譯而來,支持所有特性,性能最優(yōu),但包體較大且不易擴(kuò)展,其中 js 版本的包體約為 1.32 MB,wasm 版本的包體約為 690 KB
image.png
二、2D 物理簡介

Cocos Creator 3.0 支持內(nèi)置的輕量 Builtin 物理系統(tǒng)和強(qiáng)大的 Box2D 物理系統(tǒng)。Builtin 物理系統(tǒng)只提供了碰撞檢測的功能,對于物理計算較為簡單的情況,我們推薦使用 Builtin 物理模塊,這樣可以避免加載龐大的 Box2D 物理模塊并構(gòu)建物理世界的運(yùn)行時開銷。而 Box2D 物理模塊提供了更完善的交互接口和剛體、關(guān)節(jié)等已經(jīng)預(yù)設(shè)好的組件。

你可以根據(jù)需要來選擇適合自己的物理模塊,通過編輯器主菜單中的 項目 -> 項目設(shè)置 -> 功能裁剪 切換物理模塊的使用。


image.png
三、物理組件

剛體rigidbody :剛體是指在運(yùn)動中和受力作用后,形狀和大小不變,而且內(nèi)部各點的相對位置不變的物體。
碰撞體collider:碰撞體是給物體加一個判定框,當(dāng)碰撞框重疊的時候,兩物體發(fā)生碰撞。碰撞體是檢測物理碰撞的框架,他永遠(yuǎn)跟隨物體的剛體移動,不會產(chǎn)生偏差。


image.png

盒碰撞器組件 BoxCollider
  • 剛體組件(RigidBody) 它負(fù)責(zé)操控物理對象與運(yùn)動相關(guān)的屬性,以及配置分組,一個節(jié)點最多一個
  • 碰撞器組件(Collider) 它負(fù)責(zé)操控物理對象上形狀相關(guān)的屬性,一個節(jié)點可以有多個
  • 觸發(fā)器(Trigger) 它是勾選上了IsTrigger的碰撞器組件,它會像幽靈一樣穿透其它物體,并提供穿透開始、保持,以及結(jié)束的事件

是否一定要加剛體組件是提問最多的,這里介紹一個小技巧:

  • 需要配置分組,加
  • 需要設(shè)置為運(yùn)動學(xué)或者動力學(xué)類型(不加意味著是靜態(tài)類型),加
1.剛體

剛體是組成物理世界的基本對象,可以讓一個節(jié)點受到物理影響并產(chǎn)生反應(yīng)。剛體組件用于控制模擬相關(guān)的部分屬性:


image.png
  • Linear Damping 線性阻尼,用于減小剛體的線性速率,值越大物體移動越慢
  • Angular Damping 角阻尼,用于減小剛體的旋轉(zhuǎn)速率,值越大剛體旋轉(zhuǎn)越慢

阻尼參數(shù)的范圍在 0 到無窮之間,0 意味著無阻尼,無窮意味著滿阻尼。一般情況下阻尼的值是在 0 ~ 0.1 之間。

  • Linear Factor 線性因子,可影響剛體在每個軸向的線性速度變化,值越大剛體移動越快
  • Angular Factor 旋轉(zhuǎn)因子,可影響剛體在每個軸向的旋轉(zhuǎn)速度變化,值越大剛體旋轉(zhuǎn)越快

因子是 Vec3 的類型,相應(yīng)分量的數(shù)值用于縮放相應(yīng)軸向的速度變化,默認(rèn)值都為 1,代表縮放為 1 倍,即無縮放。

// TypeScript
const rigidBody = this.getComponent(RigidBody);

目前剛體類型包括 STATIC、DYNAMIC 和 KINEMATIC 三種。參考Unity 物理系統(tǒng)之剛體、碰撞體、觸發(fā)器

動態(tài) Dynamic

  • 默認(rèn)類型
  • 受重力和附加力的影響,進(jìn)而改變速度。
  • 性能消耗最高的類型。
  • 可通過 MovePosition、MoveRotation 來移動。
  • 可通過 velocity 來改變速度。
  • 用來模擬移動的物體。

運(yùn)動學(xué) Kinematic

  • 不受力的影響。
  • 性能消耗比 Dynamic 少,比 Static 多。
  • 可通過 MovePosition MoveRotation 來移動。
  • 可通過 velocity 來改變速度。
  • 用來模擬大多數(shù)時候不動的物體,極少數(shù)時候需要移動。比如門。

靜態(tài) Static

  • 不受力的影響,物理系統(tǒng)不會對該物體進(jìn)行計算。
  • 性能消耗最低的類型。
  • 不可通過 MovePosition MoveRotation 來移動。
  • 不可通過 velocity 來改變速度。
  • 用來模擬從不移動的物體。

cocos文檔是這樣寫的:

STATIC,表示靜態(tài)剛體,可用于描述靜止的建筑物,若物體需要持續(xù)運(yùn)動,應(yīng)設(shè)置為 KINEMATIC 類型;
DYNAMIC,表示動力學(xué)剛體,能夠受到力的作用,請通過物理規(guī)律來運(yùn)動物體,并且請保證質(zhì)量大于 0;
KINEMATIC,表示運(yùn)動學(xué)剛體,通常用于表達(dá)電梯這類平臺運(yùn)動的物體,請通過 Transfrom 控制物體運(yùn)動;

經(jīng)過測試,KINEMATIC類型確實無法通過改變速度讓其動起來。


76bde0722ff65cd0d42c92835c29e874.gif

圖中白色為靜態(tài),藍(lán)色為運(yùn)動學(xué),黃色為動力學(xué)。其中白色和藍(lán)色都是操控的變換信息,很明顯的看出幾個表現(xiàn)

  • 白色和藍(lán)色之間會出現(xiàn)穿透現(xiàn)象
  • 白色的靜態(tài)物體也可以運(yùn)動
  • 兩個黃色方塊表現(xiàn)不同,白色上方的靜止不動,藍(lán)色上方的會跟隨著運(yùn)動

以上現(xiàn)象的原因是:

  • 靜態(tài)和運(yùn)動學(xué)都不會受到力的作用,所以產(chǎn)生了穿透,這是正常現(xiàn)象
  • 靜態(tài)物體的確是可以運(yùn)動的,靜態(tài)是指在時空中,每一個時刻都是靜態(tài),不會考慮其它時刻的狀態(tài)
  • 與靜態(tài)物體不同,運(yùn)動學(xué)物體會根據(jù)附近時刻估算出運(yùn)動狀態(tài)(比如速度),又由于摩擦力的作用,因此帶動了黃色方塊
2.剛體質(zhì)心

目前質(zhì)心固定在剛體組件綁定的節(jié)點上,質(zhì)心和碰撞體是相對關(guān)系。通過調(diào)整形狀的偏移 center,可以使質(zhì)心在形狀上進(jìn)行偏移。

image.png

參考
放空大佬在不在 有個問題好想溝通一下!
@開發(fā)團(tuán)隊 關(guān)于物理系統(tǒng)重心的疑問,可能是BUG?

目前我們的質(zhì)心是固定的,改不了,所小車的坐標(biāo)點就是質(zhì)心的位置。你可以看一下小車的根節(jié)點是不是在小車底部,這個時候新建一個空節(jié)點,然后把小車放入這個空節(jié)點下,調(diào)整位置,讓空節(jié)點看起來處于小車的正中心就行,這個時候再把碰撞組件由原來的小車根節(jié)點移到空節(jié)點上,這樣質(zhì)心的修改就成功了
在 Cocos Creator 3D 里目前質(zhì)心和重心是重合的,節(jié)點位置就是重心的位置,所以要改重心只能調(diào)整節(jié)點。center 的改動目前對重心是沒有任何效果的,對外力才有影響

3.休眠

休眠剛體時,會將剛體所有的力和速度清空,使剛體停下來。
處于休眠狀態(tài)中的物體,不會再對其進(jìn)行碰撞檢測和模擬。這會節(jié)約大量的CPU開銷。

image.png

// 休眠
if (rigidBody.isAwake) {
    rigidBody.sleep();
}
// 喚醒
if (rigidBody.isSleeping) {
    rigidBody.wakeUp();
}
4.讓剛體動起來

針對不同的類型,讓剛體運(yùn)動的方式不同:

  • 對于靜態(tài)剛體,應(yīng)當(dāng)盡可能保持物體靜止,但仍然可以通過 Transform 來改變物體的位置。
  • 對于運(yùn)動學(xué)剛體,應(yīng)當(dāng)通過改變 Transform 使其運(yùn)動。
  • 對于動力學(xué)剛體,需要改變其速度,有以下幾種方式:
(1)通過重力

剛體組件提供了 useGravity 屬性,將 useGravity 屬性設(shè)置為 true。

(2)通過施加力

剛體組件提供了 applyForce 接口,簽名為:

applyForce (force: Vec3, relativePoint?: Vec3)

根據(jù)牛頓第二定律,可對剛體某點上施加力來改變物體的原有狀態(tài)。

rigidBody.applyForce(new Vec3(200, 0, 0));
(3)通過扭矩

力與沖量也可以只對旋轉(zhuǎn)軸產(chǎn)生影響,使剛體發(fā)生轉(zhuǎn)動,這樣的力叫做扭矩。

剛體組件提供了 applyTorque 接口,簽名為:

applyTorque (torque: Vec3)

通過此接口可以施加扭矩到剛體上,因為只影響旋轉(zhuǎn)軸,所以不需要指定作用點。

(4)通過施加沖量

剛體組件提供了 applyImpulse 接口,簽名為:

applyImpulse (impulse: Vec3, relativePoint?: Vec3)

根據(jù)動量守恒,對剛體某點施加沖量,由于物體質(zhì)量恒定,從而使剛體改變原有狀態(tài)。

rigidBody.applyImpulse(new Vec3(5, 0, 0));
(5)通過改變速度

剛體組件提供了 setLinearVelocity 接口,可用于改變線性速度,簽名為:

setLinearVelocity (value: Vec3)

示例:

rigidBody.setLinearVelocity(new Vec3(5, 0, 0));

剛體組件提供了 setAngularVelocity 接口,可用于改變旋轉(zhuǎn)速度,簽名為:

setAngularVelocity (value: Vec3)

示例:

rigidBody.setAngularVelocity(new Vec3(5, 0, 0));

參考拉小登博客 讓剛體聽我的——ApplyForce、ApplyImpulse、SetLinearVelocity

1.力,循序漸進(jìn)——ApplyForce
顧名思義,ApplyForce方法會在剛體上施加一個力。學(xué)過物理力學(xué)的同學(xué)都知道,F(xiàn)=ma,有了力F就有了加速度a,有了加速度,物體就會有速度,就會慢慢動起來。(但是不會立馬動起來,因為力不會直接影響速度)。
舉個簡單的例子,小明推一個靜止的箱子,箱子不會立馬飛出去,而是慢慢的、越來越快的動起來(減速也一樣)。
2.速度,疊加——ApplyImpulse
與ApplyForce不同,ApplyImpulse不會產(chǎn)生力,而是直接影響剛體的速度。通過ApplyImpulse方法添加的速度會與剛體原有的速度疊加,產(chǎn)生新的速度。
3.一觸即發(fā)——SetLinearVelocity
setLinearVelocity與ApplyImpulse一樣,直接影響剛體的速度。不一樣的是,setLinearVelocity添加的速度會覆蓋剛體原有的速度。不過,在SetLinearVelocity方法不會自動喚醒sleeping的剛體,所以在調(diào)用該方法之前,記得將剛體body.wakeUp()一下。

5.限制剛體的運(yùn)動
(1)通過休眠

休眠剛體時,會將剛體所有的力和速度清空,使剛體停下來。

(2)通過阻尼

剛體組件提供了 linearDamping 和 angularDamping 屬性:

  • linearDamping 屬性用于設(shè)置線性阻尼。
  • angularDamping 屬性用于設(shè)置旋轉(zhuǎn)阻尼。

阻尼參數(shù)的范圍建議在 0 到 1 之間,0 意味著沒有阻尼,1 意味著滿阻尼。

注:執(zhí)行部分接口,例如施加力或沖量、改變速度、分組和掩碼會嘗試喚醒剛體。

(3)通過因子

剛體組件提供了 linearFactor 和 angularFactor 屬性:

  • linearFactor 屬性用于設(shè)置線性因子。
  • angularFactor 屬性用于設(shè)置旋轉(zhuǎn)因子。

因子是 Vec3 的類型,相應(yīng)分量的數(shù)值用于縮放相應(yīng)軸向的速度變化,默認(rèn)值都為 1,表示縮放為 1 倍,即無縮放。

注意:

  • 將因子某分量值設(shè)置為0,可以固定某個軸向的移動或旋轉(zhuǎn)。
  • 在 cannon 和 ammo 后端中,因子作用的物理量不同,cannon 中作用于速度,ammo 中作用于力。

使用矩形碰撞盒,碰撞后發(fā)現(xiàn)模型的移動軌跡從直線變歪。將angularFactor 的三個軸屬性全部改為0即可解決。

6.地面判斷
image.png

角色是否在地面上,可以通過法線的指向來得知,對應(yīng)的條件為normal.y > 0。有時候還需要知道地面的傾斜情況,這里通過normal.y的大小就能知道,越接近于1,哪么地面就越平(法線垂直向上);越接近于0,哪么地面就越陡峭。

7.跳躍

一旦能夠確定是否在地面后,就可以加上跳躍行為了。首先需要明確的一點是,這里使用的是動力學(xué)類型的剛體(原因是我希望通過物理引擎接管剛體后得到更加真實的物理反饋),所以需要通過改變物理數(shù)值來達(dá)到目的,例如直接將角色線性速度的y分量設(shè)置為5,如下圖(圖中碰撞體用的是膠囊體,這與實際場景相關(guān),可自行更改)。


asr43-w6gh9.gif

直接改y軸速度,可以良好的工作在完全水平的地面上。但有時候跳躍還需要根據(jù)地面斜度來做出不同的行為,這里提供一個思路:提供一個配置因子,范圍為0到1,根據(jù)因子將當(dāng)前法線和垂直法線插值,得出目標(biāo)法線,再乘以跳躍的速率,進(jìn)而得出期望的跳躍速度,將其加入到角色的線性速度之中。

8.行走和站立

站立行為,如果希望角色不會摔倒,哪么直接將AngularFactor全設(shè)置為0即可。行走行為,可根據(jù)角色的運(yùn)動狀態(tài)來實現(xiàn),假設(shè)需要將角色往x軸運(yùn)動,哪么就修改角色x軸的線性速度。因此控制行走,只需要兩個量,一個是運(yùn)動方向,另一個是運(yùn)動速率。

做出好的運(yùn)動控制的唯一竅門就是控制好速度,但這也是最容易錯誤使用的點:

  • 隨意設(shè)置速度,這是運(yùn)動不真實最根本的原因
  • 設(shè)置過大的速度,這將很容易出現(xiàn)穿透現(xiàn)象,可以嘗試上文介紹的多步模擬技術(shù)
  • 沒有掌握控制速度的方式,有些開發(fā)者直接修改為某個固定值大小,這當(dāng)然不是錯誤的做法,但修改速度之前應(yīng)當(dāng)考慮清楚這是不是想要的結(jié)果。
  • 有個小技巧是,如果是跳躍行為,可以考慮直接設(shè)置為固定值;
  • 如果是行走行為,更好的方式是根據(jù)上個時刻的狀態(tài)進(jìn)行修改
  • 使用了Kinematic類型的剛體,期望物理引擎會幫助你處理好碰撞行為,通過上文可以知道這是不可能的。
  • 但實際上有很多好的角色控制都基于Kinematic進(jìn)行實現(xiàn),這里面的技術(shù)細(xì)節(jié)遠(yuǎn)比本文介紹的復(fù)雜。
四、分組 掩碼
1.碰撞矩陣
image.png

默認(rèn)情況下只有一個 DEFAULT 分組,新建分組默認(rèn)不與其它組碰撞。點擊 + 按鈕可以新增分組。

注:新增分組的 index 和 name 均不能為空,且不能與現(xiàn)有項重復(fù)。
注:分組不可以刪除,但可以修改分組的名稱。

這張表列出了所有的分組,你可以通過勾選來決定哪兩組會進(jìn)行碰撞檢測。如上圖所示,DEFAULT和water是否會進(jìn)行碰撞檢測將取決于是否選中了對應(yīng)的復(fù)選框。

根據(jù)上面的規(guī)則,在這張表里產(chǎn)生的碰撞對有:

  • DEFAULT - water
  • DEFAULT - DEFAULT

而不進(jìn)行碰撞檢測的分組對有:

  • water - water

通過剛體組件上的 Group 屬性來配置對應(yīng)的物理元素的分組:

image.png

Cocos Creator 3.0 正式版震撼來襲!中,有提到碰撞矩陣相關(guān)變化:

[IMPROVE] 重構(gòu) 3d 物理碰撞矩陣,碰撞矩陣僅用于初始化剛體的分組和掩碼,不再維護(hù)更新,廢除 Use Collision Matrix 配置項

2.設(shè)置掩碼

參考Cocos Creator 3D 物理碰撞group和mask設(shè)置方法

a.假設(shè)場景中有:場景障礙物,玩家A,玩家B,穿墻的導(dǎo)彈

  • 障礙物阻擋玩家A和玩家B
  • 玩家A和玩家B不碰撞,但會被導(dǎo)彈擊中
  • 導(dǎo)彈可以穿越障礙物,但會擊中玩家A和玩家B

b.設(shè)置分組group

  • 設(shè)置障礙物的collider的group為0,(注意?。?!代碼中實際設(shè)置為1<<0)
  • 設(shè)置玩家A的collider的group為1,(注意!??!代碼中實際設(shè)置為1<<1)
  • 設(shè)置玩家B的collider的group為2,(注意?。?!代碼中實際設(shè)置為1<<2)
  • 設(shè)置導(dǎo)彈的collider的group為3,(注意?。?!代碼中實際設(shè)置為1<<3)

c.以障礙物為例,分析過程如下:

只要以下條件為真就會進(jìn)行檢測:(GroupA & MaskB) && (GroupB & MaskA)

障礙物和A會阻擋

  • A的分組是0010,所以障礙物的mask為1<<1
  • 障礙物的分組是0001,所以A的mask為1<<0(這一條作為分析障礙物時得到的額外信息,以下不再標(biāo)注)

同理,障礙物和B會阻擋

  • B的分組是0100,所以障礙物的mask為1<<2
  • 障礙物的分組是0001,所以B的mask為1<<0

障礙物和導(dǎo)彈不碰撞,即導(dǎo)彈能穿墻,此時把上面兩部分合并,得到:
障礙物的collider的mask為(1<<1)|(1<<2)(障礙物碰撞玩家A和玩家B)

如果障礙物和導(dǎo)彈碰撞,和上面一樣:

  • 導(dǎo)彈的分組是1000,所以障礙物的mask為1<<3
  • 障礙物的分組是0001,所以導(dǎo)彈的mask為1<<0

然后障礙物的mask再次合并,就變?yōu)椋?<<1)|(1<<2)|(1<<3)

d.設(shè)置掩碼mask(將需要互相碰撞的group掩碼或進(jìn)來(加進(jìn)來也可以))

  • 設(shè)置障礙物的collider的mask為(1<<1)|(1<<2)(障礙物碰撞玩家A和玩家B)
  • 設(shè)置玩家A的collider的mask為(1<<0)|(1<<3)(玩家A碰撞障礙物和導(dǎo)彈)
  • 設(shè)置玩家B的collider的mask為(1<<0)|(1<<3)(玩家B碰撞障礙物和導(dǎo)彈)
  • 設(shè)置導(dǎo)彈的collider的mask為(1<<1)|(1<<2)(導(dǎo)彈碰撞玩家A和玩家B)

如果導(dǎo)彈不能穿墻,需要更改mask為

  • 設(shè)置障礙物的collider的mask為(1<<1)|(1<<2)|(1<<3)(加入導(dǎo)彈)
  • 設(shè)置導(dǎo)彈的collider的mask為(1<<1)|(1<<2)|(1<<0)(加入障礙物)

如果玩家之間互相碰撞,需要更改mask為

  • 設(shè)置玩家A的collider的mask為(1<<0)|(1<<3)|(1<<2)(加入玩家B)
  • 設(shè)置玩家B的collider的mask為(1<<0)|(1<<3)|(1<<1)(加入玩家A)

示例代碼如下

        var c0 = blocks.getComponent(ColliderComponent);
        c0.setGroup(1 << 0);
        c0.setMask((1 << 1) | (1 << 2));

        var c1 = playerA.getComponent(ColliderComponent);
        c1.setGroup(1 << 1);
        c1.setMask((1 << 0) | (1 << 3));

        var c2 = playerA.getComponent(ColliderComponent);
        c2.setGroup(1 << 2);
        c2.setMask((1 << 0) | (1 << 3));

        var c3 = missle.getComponent(ColliderComponent);
        c3.setGroup(1 << 3);
        c3.setMask((1 << 1) | (1 << 2));
五、物理材質(zhì)
image.png

image.png

物理材質(zhì)有摩擦系數(shù),回彈系數(shù)。

目前物理材質(zhì)以碰撞體為單位進(jìn)行設(shè)置,每個 Collider 都具有一個 material 的屬性(不設(shè)置時, Collider 將會引用物理系統(tǒng)中的默認(rèn)物理材質(zhì))。 應(yīng)用到 Collider 同樣也分編輯器操作和代碼操作兩種方式。

編輯器內(nèi)操作,只需要將資源拖入到cc.PhysicMaterial屬性框中即可,如下圖所示:


image.png

代碼中操作:

const collider = this.node.getComponent(Collider);
collider.material = newPmtl;
六、物理事件

Cocos Creator 3.0 的物理事件包括 觸發(fā)事件 和 碰撞事件,分別由 觸發(fā)器 和 碰撞器 產(chǎn)生。

1.觸發(fā)器和碰撞器
  • Is Trigger 屬性為 true ,是觸發(fā)器。當(dāng)發(fā)生碰撞時,觸發(fā)器不會產(chǎn)生碰撞效果,所以觸發(fā)器只用于碰撞檢測。
  • Is Trigger 屬性為 false ,是碰撞器。當(dāng)發(fā)生碰撞時,碰撞器會產(chǎn)生碰撞效果,所以碰撞器既可以進(jìn)行碰撞檢測,又可以產(chǎn)生物理效果。

兩者的區(qū)別如下:

  • 觸發(fā)器不會與其它觸發(fā)器或者碰撞器做更精細(xì)的檢測。
  • 碰撞器與碰撞器會做更精細(xì)的檢測,并會產(chǎn)生碰撞數(shù)據(jù),如碰撞點、法線等。
2.觸發(fā)事件和碰撞事件區(qū)別
  • 觸發(fā)事件由觸發(fā)器生成,碰撞事件根據(jù)碰撞數(shù)據(jù)生成。
  • 觸發(fā)事件可以由觸發(fā)器和另一個觸發(fā)器/碰撞器產(chǎn)生。
  • 碰撞事件需要由兩個碰撞器產(chǎn)生,并且至少有一個是動力學(xué)剛體。

參考
《學(xué)Unity的貓》——第十章:Unity的物理碰撞,流浪喵星計劃
碰撞器、觸發(fā)器

觸發(fā)器在生活有很多例子,假設(shè)進(jìn)入到一些門口時,當(dāng)我們踏入到一定范圍,門會自動打開,這其實就是觸發(fā)器在現(xiàn)實生活中的例子,游戲中依然也是,例如,人物走回家的時候會自動的恢復(fù)血量,這其實就是在使用觸發(fā)器

3.觸發(fā)事件
  • onTriggerEnter 觸發(fā)開始
  • onTriggerStay 觸發(fā)保持
  • onTriggerExit 觸發(fā)結(jié)束

需要通過注冊事件來添加相應(yīng)的回調(diào):

  • 通過this.getComponent(Collider)獲取到 Collider
  • 通過 Collider 的 on 或者 once 方法注冊相應(yīng)事件的回調(diào)
  • 注:Collider 是所有碰撞組件的父類。

代碼示例:

public start () {
    let Collider = this.getComponent(Collider);
    Collider.on('onTriggerStay', this.onTrigger, this);
}

private onTrigger (event: ITriggerEvent) {
    console.log(event.type, event);
}

經(jīng)過實測,觸發(fā)事件,仍然需要其中一個組件是剛體

4.碰撞事件
  • onCollisionEnter 碰撞開始
  • onCollisionStay 碰撞保持
  • onCollisionExit 碰撞結(jié)束

代碼示例:

public start () {
    let Collider = this.getComponent(Collider);
    Collider.on('onCollisionStay', this.onCollision, this);
}

private onCollision (event: ICollisionEvent) {
    console.log(event.type, event);
}
七、射線檢測

官方文檔 射線檢測
淺析射線檢測 raycast 的使用 !Cocos Creator 3D !
首先,我們看到的視角是這樣子的。假設(shè)我們點擊其中屏幕中的一個位置(圖中的紅點點)。

image.png

因為這個視角是攝像機(jī)提供的,我們就把這個點點和攝像機(jī)組合一條射線。
image.png

接著,檢查這條射線穿過了那些物體,這些物體中可能就有我們點擊的對象。

也可以這么理解,你用眼睛看著一塊區(qū)域,伸出手指。你可以看到手指頭擋住了一點視線,從你的視線做經(jīng)過手指這個點畫一條射線,這個射線穿過的物體,就剛好是你想要點擊的物體。

creator 3d 提供了三種檢測方案,可以參考https://github.com/cocos-creator/test-cases-3d中的raycast示例:

1.基于物理碰撞器的射線檢測:
onEnable () {
    systemEvent.on(SystemEventType.TOUCH_START, this.onTouchStart, this);
}

onDisable () {
    systemEvent.off(SystemEventType.TOUCH_START, this.onTouchStart, this);
}

onTouchStart (touch: Touch, event: EventTouch) {
    this.cameraCom.screenPointToRay(touch.getLocationX(), touch.getLocationY(), this._ray);
    if (PhysicsSystem.instance.raycast(this._ray)) {
        const r = PhysicsSystem.instance.raycastResults;
        for (let i = 0; i < r.length; i++) {
            const item = r[i];
            if (item.collider.node.uuid == this.modelCom.node.uuid) {
                this.modelCom.material = this.rayMaterial;
            }
        }
    } else {
        this.modelCom.material = this.defaultMaterial;
    }
}
2.基于模型的射線檢測:
onTouchStart(touch: Touch, event: EventTouch) {
    const point = touch.getLocation();
    this.cameraCom.screenPointToRay(point.x, point.y, this._ray);
    if (geometry.intersect.rayModel(this._ray, this.modelCom.model!)) {
        this.modelCom.material = this.rayMaterial;
    } else {
        this.modelCom.material = this.defaultMaterial;
    }
}
3.基于 UITransform 組件的射線檢測:
onTouchStart(touch: Touch, event: EventTouch) {
    this.label.string = '點擊文字測試射線檢測';
    const point = touch.getLocation();
    this.canvas.cameraComponent!.screenPointToRay(point.x, point.y, this._ray);
    const uiTrans = this.label.getComponent(UITransform)!;
    uiTrans.getComputeAABB(this._aabb);
    if (geometry.intersect.rayAABB(this._ray, this._aabb)) {
        this.label.string = '檢測成功';
    }
}
4.API
  • raycast: 射線可以碰撞所有的可以檢測的物體(多個).
  • raycastClosest:射線碰到一個物體以后,停止檢測,直接返回一個物體(一個).

/**
 * 檢測所有的碰撞盒,并記錄所有被檢測到的結(jié)果,通過 PhysicsSystem.instance.raycastResults 訪問結(jié)果。
 * @param worldRay 世界空間下的一條射線
 * @param mask 掩碼,默認(rèn)為 0xffffffff
 * @param maxDistance 最大檢測距離,默認(rèn)為 10000000,目前請勿傳入 Infinity 或 Number.MAX_VALUE
 * @param queryTrigger 是否檢測觸發(fā)器
 * @return boolean 表示是否有檢測到碰撞盒
 */
raycast(worldRay: geometry.Ray, mask?: number, maxDistance?: number, queryTrigger?: boolean): boolean;
/**
 * 檢測所有的碰撞盒,并記錄與射線距離最短的檢測結(jié)果,通過 PhysicsSystem.instance.raycastClosestResult 訪問結(jié)果。
 * @param worldRay 世界空間下的一條射線
 * @param mask 掩碼,默認(rèn)為 0xffffffff
 * @param maxDistance 最大檢測距離,默認(rèn)為 10000000,目前請勿傳入 Infinity 或 Number.MAX_VALUE
 * @param queryTrigger 是否檢測觸發(fā)器
 * @return boolean 表示是否有檢測到碰撞盒
 */
raycastClosest(worldRay: geometry.Ray, mask?: number, maxDistance?: number, queryTrigger?: boolean): boolean;
八、射箭案例
odir1-qes1f.gif
1.射箭與回收箭——運(yùn)動學(xué)、動力學(xué)、事件

射箭的第一步是拉弓,箭需要完全跟隨彈性繩骨骼一起運(yùn)動,不希望箭受到物理規(guī)則的影響,此時應(yīng)將箭的剛體設(shè)置為 Kinematic 類型;第二步是松開彈性繩發(fā)射箭,這時希望給箭設(shè)置初速度后,可以按照物理規(guī)則進(jìn)行運(yùn)動,因此將箭的剛體設(shè)置為 Dynamic 類型

回收箭的大致過程是在箭射出去后,一旦觸碰到觸發(fā)區(qū)域就將其還原到弓上。這可以通過制作監(jiān)聽區(qū)域來實現(xiàn),首先利用碰撞體組件拼湊出區(qū)域,同時將碰撞體組件的 IsTrigger 勾選上。(下圖中的藍(lán)色地板為監(jiān)聽區(qū)域)


image.png

易誤點:

  • 利用修改變換信息來操作動力學(xué)(Dynamic)類型的剛體,應(yīng)當(dāng)通過速度、力或沖量等物理層的數(shù)值
  • 利用靜態(tài)(Static)類型的剛體來監(jiān)聽事件,靜態(tài)剛體只會和帶有類型為運(yùn)動學(xué)或動力學(xué)的剛體產(chǎn)生事件,可以更改為運(yùn)動學(xué)(Kinematic)類型,或者事件通過另一方進(jìn)行注冊
  • 監(jiān)聽事件時僅監(jiān)聽了觸發(fā)開始(OnTriggerEnter),但是誤以為包括了觸發(fā)保持(OnTriggerStay)和結(jié)束(OnTriggerExit)
2.瞄準(zhǔn)——碰撞矩陣(過濾檢測)、射線檢測、靜態(tài)平面

瞄準(zhǔn)是射箭前的步驟,準(zhǔn)心處于箭頭指向所在的射線上,在十字架前面加一個靜態(tài)平面碰撞體,然后利用射線檢測就可以得到準(zhǔn)心的位置;

image.png

靜態(tài)平面只是用來做射線檢測,給它專門建立一個分組,并且不與箭、蘋果等等物體進(jìn)行檢測,這是最通用的性能優(yōu)化方法


image.png

調(diào)用射線檢測方法時,設(shè)置傳入掩碼為僅和靜態(tài)平面檢測,即 0b10(二進(jìn)制表示法)。


image.png

注:這里傳入了mask參數(shù),代碼注釋中描述得并不清楚:@param mask 掩碼,默認(rèn)為 0xffffffff。實際上,mask參數(shù)是指要檢測的物理類型,比如此例中的RaycastPlane的index是1,即第2位,就要傳0b10

易誤點:

  • 分不清剛體組件上的分組和節(jié)點上的層。這兩者的概念類似,但是使用者不同,分組的使用者是物理模塊,層的使用者是渲染模塊
  • 對掩碼的理解不到位,不知傳何值。這里提供一個小技巧,以一個能夠篩選出Others的掩碼舉例,首先Others的index值為2,哪么只要讓二進(jìn)制掩碼從右往左的順序第2位為1,就能讓Others通過篩選,也就是ob100(這里強(qiáng)烈建議不要隨便更改分組的索引)
  • 誤認(rèn)為射線檢測接口的返回值是擊中的數(shù)據(jù)。獲取結(jié)果有專門的接口,此處設(shè)計是為了強(qiáng)調(diào)這是個復(fù)用對象。為了減少垃圾內(nèi)存,每次調(diào)用接口只會更新它們的數(shù)據(jù),而不是重新生成新的(若需要持久記錄,哪么可以克隆一份)
3.網(wǎng)格碰撞器組件(MeshCollider)
image.png
  • mesh 網(wǎng)格碰撞器引用的網(wǎng)格資源,用于初始化網(wǎng)格碰撞體
  • convex 是否使用網(wǎng)格的凸包近似,網(wǎng)格頂點數(shù)應(yīng)盡量小于255(通過它可以支持任意凸類碰撞體和動力學(xué)剛體)

注:cannon.js對網(wǎng)格碰撞器組件支持程度很差,只允許與球碰撞器產(chǎn)生檢測。 注:convex功能目前僅ammo.js后端支持。

4.射擊蘋果——靜態(tài)網(wǎng)格、凸包、多步模擬(步長調(diào)整)
8v773-kzyln.gif

一般的蘋果都帶有凹面,處理好凹類或帶連續(xù)平滑不規(guī)則曲面的模型都非常棘手,這是因為目前成熟的理論和技術(shù)都建立在離散、凸包的世界之上(微積分中用差分近似表示微分就是最典型的范例)。

在實時物理引擎中,對于這類物體只能支持到靜態(tài)或運(yùn)動學(xué)類型的剛體層級,對于動力學(xué)就束手無策了。然而不幸的是,真實的蘋果運(yùn)動表現(xiàn)強(qiáng)烈依賴動力學(xué),這種情況只能給蘋果填加凸包形式的網(wǎng)格碰撞體(需將 convex 勾選上),再加上一個動力學(xué)剛體,用近似物體去參與模擬

運(yùn)動表現(xiàn)與模擬參數(shù)有非常大的關(guān)系,穿透是最具有代表性的現(xiàn)象,這可以通過縮減步長和增加步數(shù)來實現(xiàn),調(diào)整步長有個小技巧:輸入分式,即 1/Frame,其中 Frame 表示幀率


image.png

易誤點:

  • 在帶有未勾選 convex 的網(wǎng)格碰撞器上添加了的動力學(xué)剛體,與其它物體產(chǎn)生了穿透現(xiàn)象,或者說完全沒有反應(yīng),這是典型的錯誤使用,只有勾上 convex 的才能支持動力學(xué)剛體

  • 對一個頂點數(shù)極多的模型開啟了 convex,過多的頂點數(shù)會使凸包的面數(shù)增多,這對性能有很大的影響,而且實際上并不需要面數(shù)特別多的凸包,一般建議模型的頂點數(shù)應(yīng)小于 255

  • 開啟凸包后,模型的凹面處的接觸不貼近,這是正常現(xiàn)象,現(xiàn)在的實時技術(shù)是將模型用多個凸包組合來解決,如下圖所示


    image.png
  • 只調(diào)整了步長,但未調(diào)整步數(shù),這兩者需要相互配合才有效果。小技巧是,步數(shù)可以隨意設(shè)置較大的值,步長根據(jù)最大的速度值進(jìn)行調(diào)整,值越大,步長應(yīng)當(dāng)越小

九、小車案例
image.png

上圖結(jié)構(gòu)也是通過真實車的結(jié)構(gòu)簡化而來的,設(shè)計好結(jié)構(gòu)后,還需要調(diào)整每個部分的屬性:

重心: 車的重心應(yīng)該要低一些,否則可能會很容易翻車;

車身和車輪:車輛運(yùn)動應(yīng)該是很平穩(wěn)的,可以把摩擦力系數(shù)都設(shè)置為 0,另外車輪要比車身低一些,這樣在碰到障礙物后車輛會有晃動的效果,用來模擬避震;

擋板:因為車身摩檫力設(shè)置為 0 了,為了防止車滑起來,加一個摩檫力不為 0 的擋板;

剛體:默認(rèn)質(zhì)量為 10,這里可以改成 200;因為摩檫力都為 0,避免車一直滑動,將阻力(damping)設(shè)置為 0.9;角速度因子 x 軸向設(shè)置成 0.5,減低車在 x 軸向的旋轉(zhuǎn)抖動。

十、關(guān)節(jié)

cocos creator 3D 3D怎么使用關(guān)節(jié)組件嘞?還是說沒有丫,小白太難了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容