昨天,我們分享了一篇2D物理文檔《LayaAirIDE的可視化2D物理使用文檔》。
今天,我們針對LayaAir引擎的初學者,以及對物理引擎使用不熟悉的開發者,再來分享一篇3D物理文檔,本篇文檔全面介紹了3D物理引擎使用的基礎能力。方便開發者快速上手。
LayaAir 3D引擎集成了世界三大物理引擎之一的Bullet引擎,當在Unity中使用了物理組件,用LayaAir的Unity插件導出後,默認就是採用的Bullet引擎。當然,LayaAir引擎也支持使用比較輕量的Cannon.js物理引擎的基礎功能。
閱讀本篇時,除非是Cannon物理引擎的專屬章節,或者註明是cannon物理引擎,默認介紹都是基於LayaAir封裝的Bullet引擎。
一、剛體
1.1 什麼是剛體
無論是2D,還是3D,物理的開篇,都需要先了解剛體,這是物理引擎的基礎之一。
大家都知道,自然界一切有形體的物質,都可以叫物體。
剛體是力學中為了體現物體特性的一種科學抽象概念,也是一種理想狀態的力學表達模型,是指在運動中和受到力的作用後,形狀和大小不變,而且內部各點的相對位置不變的物體。
然而,現實中不可能存在這種理想模型,物體在受力之後,會根據力、材料、彈性、 塑性等綜合因素,決定是否改變或改變多少。如果物體本身的變化不影響整個運動過程,為使被研究的問題簡化,仍將該物體當作剛體來處理而忽略物體的體積和形狀,這樣所得結果仍與實際情況相當符合。
1.2 常用的剛體屬性
isKinematic是否為運動剛體
3D的剛體,默認是動力學剛體。會受力的影響,可以位移。
一旦我們把剛體設置為運動剛體類型後,即將isKinematic的值設置為true。
那麼運動剛體可以觸發第三方的物理反饋,自己卻不受物理影響。例如,運動剛體與動力學剛體發生撞擊,動力學剛體會受力反彈,但運動剛體卻不會受力的影響,不會產生受力位移,運動剛體的位移只能通過transform改變節點坐標。
與2D的運動學類型剛體不同,LayaAir 3D的運動剛體脫離了物理引擎運動,即使設置速度也不可以使其位移。這樣做的好處是減少了物理運算,節省了性能。
mass質量
質量是物質的量的量度,Bullet引擎中的質量單位為kg。
剛體的質量越大,運動狀態改變越難,比如,不同質量的兩個物體相撞,質量大的一方改變更小一些,如動圖1的右側所示:
(動圖1)
靜態剛體和運動剛體就相當於無限大質量,所以不受力的影響。
gravity 重力
自然界中物體受地心吸引的作用而受到的力叫重力,物理引擎中也同樣模擬了重力,
動力學剛體在同等的質量下,重力越大,下落的加速度越大。對比效果如動圖1-1。
(動圖1-1)
linearVelocity 線性速度
剛體的linearVelocity屬性稱為線速度或者線性速度,是指物體的直線運動速度。
動力學剛體的線速度是3維向量Vector3類型值,向量的方向即速度的方向,向量的長度即速度的大小。
動圖1-2,是動力學剛體在同樣重力值為0的情況下,沒有設置線速度和y軸設置了線速度值的對比效果。
(動圖1-2)
linearDamping 線性阻尼
剛體的linearDamping屬性,是指線性速度的阻尼係數,使得線性速度衰減。
動圖1-3,是動力學剛體在重力為0並且y軸設置了同樣為-1的線速度值情況下,左側為0.9線性阻尼值和右側為1線性阻尼值的對比效果。
(動圖1-3)
angularVelocity 角速度
剛體的angularVelocity屬性是角速度, 角速度簡單理解就是單位時間的角位移,以弧度每秒進行旋轉 。當我們設置動力學剛體angularVelocity屬性為正值的時候,則按順時針旋轉位移。angularVelocity屬性為負值的時候,則按逆時針旋轉位移。屬性值的絕對值越大,旋轉位移速度越快。
(動圖1-4)
angulaVelocity屬性的值是3維向量Vector3類型值,Bullet使用歐拉角來描述物體的旋轉,3D向量的每個分量代表繞x、y、z軸旋轉的速度,單位是弧度/秒。動圖1-4,就是在x軸分別設置了3.14與31.4的對比效果。
angularDamping 角阻尼
剛體的角阻尼相當於是為角速度旋轉方向施加了相反的力,使得旋轉速度衰減。動圖1-5,是在同樣的31.4角速度下,左側為1的的角阻尼值,右側為0.9的角阻尼值,對比效果。
(動圖1-5)
二、物理碰撞
碰撞是物理引擎中最基礎、最常用的功能。在這個小節裡,我們對3D物理碰撞進行全面的認知。
2.1 碰撞器與觸發器
對於檢測3D物理碰撞的方式,有碰撞器與觸發器兩種。我們先從概念認知開始。
2.1.1 碰撞器
在LayaAir引擎2D物理的時候,通過封裝的不同形狀的碰撞體,就可以直接實現帶範圍的物理碰撞。
而LayaAir引擎的3D物理,形狀不再是最主要的特徵,只是碰撞器用於檢測碰撞範圍的三維形狀區域。
完整的3D碰撞器,由碰撞器和碰撞器形狀兩部分組成。
3D碰撞器根據特點的不同,分為靜態碰撞器、剛體碰撞器、角色碰撞器。
這些碰撞器必須要添加三維碰撞器形狀(例如:盒形、球形、圓錐形、圓柱形、膠囊形、平面、混合、模型網格),才可以實現有範圍的物理碰撞。
(圖2)
圖2是膠囊形狀角色碰撞器的編輯預覽效果。
2.1.2 觸發器
LayaAir 3D物理的觸發器相當於2D物理裡的傳感器。
觸發器是碰撞器的一個屬性,任何碰撞器的觸發器屬性設置生效後,當前的碰撞器即轉變為觸發器(比如,剛體碰撞器設置觸發器後可稱為剛體觸發器)。即使發生物體接觸,也不會產生碰撞的物理反饋。例如,動圖3-1右側所示。下落的盒子無視物理引擎,直接穿透而過。
(動圖3-1)
設置觸發器後,雖然失去了物理引擎反饋,但是可以激活觸發器的事件生命周期方法,用於檢測物體間碰撞接觸的發生。
激活觸發器生命周期也有特定的情況除外,具體規則會在下面的物理生命周期章節介紹
當觸發器isTrigger設置為true時,或者在Unity的碰撞體組件那裡勾選Is Trigger並導出使用時,如圖3-2所示。觸發器即可設置生效。
(圖3-2)
通過LayaAir引擎代碼設置觸發器的方式:
/*……省略若干代碼*///創建盒型MeshSprite3Dlet box = scene.addChild(newLaya.MeshSprite3D(Laya.PrimitiveMesh.createBox(sX, sY, sZ)))asLaya.MeshSprite3D;//創建靜態碰撞器let staticCollider:Laya.PhysicsCollider= box.addComponent(Laya.PhysicsCollider);//設置為觸發器,取消物理反饋staticCollider.isTrigger =true;/*……省略若干代碼*/
2.2 理解各種碰撞器
2.2.1 靜態碰撞器 PhysicsCollider
LayaAir的3D物理碰撞器類是PhysicsCollider,為了便於記憶和理解,我們叫他靜態碰撞器類。因為它的特性是不受力,不會產生物理移動。
當其與動力學剛體碰撞器或角色碰撞器發生物理碰撞後,可以觸發物理碰撞生命周期方法,但不會產生物理的受力位移。
這種碰撞器可以用於不需要物理受力位移的物體,只需要觸發碰撞邏輯的應用場景。例如牆體,撞牆後判定遊戲結束。
在Unity中,如果我們添加了某種Collider組件,但並沒有添加Rigidbody組件,那導出後就是PhysicsCollider。
2.2.2 剛體碰撞器 Rigidbody3D
LayaAir的2D物理剛體與碰撞體是分開的,而3D物理的剛體與碰撞器是整合的,Rigidbody3D類即是剛體也是碰撞器,我們可稱為剛體碰撞器。
默認情況下,Rigidbody3D是動力學類型的剛體碰撞器,這是可以受力影響的剛體類型碰撞器,所以我們通常用動力學剛體碰撞器進行受力的交互反饋。例如,撞擊後的反彈、飛出或者倒下,放在空中會受重力影響而掉落,等等。
當我們將剛體Rigidbody3D的isKinematic設置為true後,那麼默認的動力學剛體碰撞器就轉變為運動剛體碰撞器。
運動剛體碰撞器從表象上看,與靜態碰撞器基本上沒有什麼區別。都是不受重力、不受速度、不受其它力的影響,在物理世界中永遠處於靜止,只能通過transform去改變節點坐標來移動。
但實質上,運動剛體有物理特性,它可以是施力物體,可以對非運動剛體產生力,例如通過控制節點去移動運動剛體,會推著擋在前面的動力學剛體移動。而靜態碰撞器的應用場景則是要永遠不動,也無法施加力。並且,通過節點去移動靜態碰撞器,也比較消耗性能。如果有移動的碰撞器需求,例如來回移動的跳板或障礙,使用運動剛體碰撞器就可以了。
通過代碼設置運動剛體的方式:
/*……省略若干代碼*///創建剛體碰撞器let _rigidBody = sphere.addComponent(Laya.Rigidbody3D)asLaya.Rigidbody3D;//開啟運動類型剛體_rigidBody.isKinematic =true;/*……省略若干代碼*/在Unity中設置運動類型剛體的方式,如圖4所示:
(圖4)
由於LayaAir的3D物理中有了靜態碰撞器PhysicsCollider,所以並沒有在Rigidbody3D中去實現靜力學類型的剛體碰撞器。有靜止的碰撞反饋需求,直接使用靜態碰撞器即可。
2.2.3 角色碰撞器 CharacterController
角色控制器類CharacterController常用於對第一人稱和第三人稱遊戲角色的控制,可以方便的控制角色的跳躍、跳躍速度、降落速度、行走、等。
由於角色控制器繼承於PhysicsComponent,也具有碰撞器的特性,可以添加三維碰撞形狀,產生碰撞的反饋,因此也稱為角色碰撞器,屬於碰撞器之一。
與靜態碰撞器和剛體碰撞器都繼承自物理觸發器組件PhysicsTriggerComponent不同,角色控制器直接繼承於物理組件的父類PhysicsComponent。所以,角色控制器是無法設置為觸發器的。但是,角色碰撞器與觸發器進行接觸,仍然可以激活觸發器事件的生命周期方法。
2.3 碰撞形狀
碰撞形狀是用於檢測碰撞接觸的範圍,只有添加了形狀,碰撞器和觸發器才能觸發物理反饋和生命周期。
LayaAir引擎支持8種3D碰撞形狀,分別為:
盒形BoxColliderShape、球形SphereColliderShape、圓柱形CylinderColliderShape、膠囊形CapsuleColliderShape、圓錐形ConeColliderShape、平面形狀StaticPlaneColliderShape、複合形狀CompoundColliderShape、網格形狀MeshColliderShape。
2.3.1 Unity中可導出的碰撞形狀
Unity中的盒形碰撞體Box collider、球形碰撞體Sphere Collider、膠囊形碰撞體Capsule Collider、網格碰撞體 Mesh Collider,這4種組件是可以通過LayaAir導出插件直接導出使用的。
這些組件包括了碰撞形狀,無需通過引擎代碼添加碰撞形狀,所以對於盒形、球形、膠囊形、網格形、以及由以上基礎形狀碰撞體組合而成的複合碰撞形狀。都建議在Unity裡編輯導出使用。
需要注意的是,這些碰撞體組件的節點如果沒有添加剛體組件,那導出後在LayaAir引擎屬於靜態碰撞器,加上Rigidbody組件後,在LayaAir引擎屬剛體碰撞器。
下面我們簡單介紹一下這些碰撞體形狀的基礎屬性設置
盒形碰撞形狀
盒形碰撞形狀是通過設置XYZ調整長寬高的長方體(含立方體)形狀。常用於盒子外形的長方體物體,如圖5-1所示。
(圖5-1)在Unity中,為物體節點對象添加Box Collider組件,設置XYZ各軸的大小,如圖5-2所示,導出後即可使用。
(圖5-2)
球形碰撞形狀
球形碰撞形狀是通過設置半徑調整球體大小的碰撞形狀。常用於球形外觀的物體,如圖6-1所示。
(圖6-1)
在Unity中,為物體節點對象添加Sphere Collider組件,設置半徑,如圖6-2所示,導出後即可使用。
(圖6-2)
膠囊形碰撞形狀
膠囊形碰撞形狀是由兩個半球和一個圓柱體組成,需要通過設置球體半徑和圓柱體的高來組成膠囊形狀。常用於角色碰撞器。如圖7-1所示。
(圖7-1)
在Unity中,為物體節點對象添加Capsule Collider組件,設置半徑和高,如圖7-2所示,導出後即可使用。
(圖7-2)
網格形碰撞形狀
網格形碰撞形狀是利用模型網格資源構建的形狀,如圖8-1的蜥蜴所示。相對於其它固定規則的碰撞形狀(LayaAir內置的3D碰撞基礎形狀),網格形碰撞形狀屬於自定義任意外觀的碰撞形狀,可以適用於任何模型網格。
(圖8-1)
在Unity中,為物體節點對象添加Mesh Collider組件,設置模型網格,如圖8-2所示,導出後即可使用。
(圖8-2)
複合碰撞形狀
複合碰撞形狀是由多個基礎形狀組合而成的碰撞器形狀。例如桌子或者凳子等,可以由多個盒形碰撞形狀組成,如圖8-3所示。
(圖9-1)
LayaAir引擎的複合碰撞形狀,其實Unity中並沒有直接對應的組件。但是,開發者在Unity中,對同一個節點對象添加多個基礎的碰撞體,例如同時添加Box Collider組件和Sphere Collider組件,如圖8-4所示,那通過LayaAir的導出插件導出後,會自動識別為複合碰撞形狀。
(圖9-2)
2.3.2 Unity沒有的LayaAir碰撞形狀
除了Unity碰撞體組件支持的一些形狀外,LayaAir引擎中還內置了一些基礎的3D碰撞形狀。這些只能通過代碼的方式進行添加。
分別是:圓柱形、圓錐形、平面形狀。
圓柱形碰撞形狀
圓柱形碰撞形狀是由兩個大小相等、相互平行的圓形(底面)以及連接兩個底面的一個曲面(側面)圍成的幾何體形狀,通過設置底面半徑和連接高度來調整碰撞形狀的大小。常用於場景的柱子等圓柱形外觀的物體碰撞。如圖10-1所示。
(圖10-1)
項目代碼裡,通過創建一個CylinderColliderShape實例的方式,傳入半徑和高,即可返回一個圓柱形碰撞形狀對象,將這個對象添加給碰撞器的colliderShape屬性即可。API說明如圖10-2所示。
(圖10-2)
圓錐形碰撞形狀
圓錐形碰撞形狀是以直角三角形的直角邊所在直線為旋轉軸,其餘兩邊旋轉360度而成的曲面所圍成的幾何體形狀。需要設置底面半徑和錐體高來調整碰撞形狀大小。常用於錐體外觀的物體碰撞。如圖11-1所示。
(圖11-1)
項目代碼裡,通過創建一個ConeColliderShape實例的方式,傳入半徑和高,即可返回一個圓錐形碰撞形狀對象,將這個對象添加給碰撞器的colliderShape屬性即可。API說明如圖11-2所示。
(圖11-2)
平面碰撞形狀
平面碰撞形狀,是一種無限大的2D平面碰撞形狀。通常用於整個場景地面的碰撞形狀。通過法線來確定在3維世界的平面朝向,可以通過偏移值來調整距離原點的偏移多少。API說明如圖12-1所示。
(圖12-1)
通過API,我們可以看到normal是一個3維向量值,表示著平面的法線。例如這個值為Vector3(0, 1, 0),則表示法線位於Y軸正方向,平面碰撞形狀就是處於其垂直的X軸無限大水平面。
圖12-2是法線同樣位於Y軸正方向,偏移值offset分別為0(左側)和為1(右側)的效果對比。
(圖12-2)
2.3.3 碰撞器的形狀添加示例
使用Unity導出的碰撞組件
Unity導出的碰撞組件使用起來最簡單,由於組件已經整合了碰撞器和碰撞形狀,直接加載就可以使用了。某些情況下甚至可以不寫代碼,所以我們介紹一下使用Unity的節點對象和剛體,通過代碼添加碰撞形狀的示例。
在Unity中,是可以直接創建圓柱體這種基礎3D對象的,但是Unity沒有圓柱形碰撞組件,創建的圓柱體默認是膠囊碰撞體組件Capsule Collider,所以,我們刪除圓柱體對象的膠囊碰撞體組件,添加剛體組件(Rigidbody)導出,編寫代碼如下所示:
/* ……省略若干代碼 */Laya.Scene3D.load("Conventional/SampleScene.ls",Laya.Handler.create(null,function(_Scene3D:Laya.Scene3D){//添加3D場景到舞臺Laya.stage.addChild(_Scene3D);let _camera = _Scene3D.getChildByName("Main Camera") as Laya.Camera; _camera.clearFlag = Laya.CameraClearFlags.Sky;//從場景中找到圓柱對象let _cylinder = _Scene3D.getChildByName("Cylinder");//從圓柱對象上獲得剛體碰撞器(對應Unity的剛體組件)let cyRigid = _cylinder.getComponent(Laya.Rigidbody3D) as Laya.Rigidbody3D;//創建圓柱體形狀(通常與圓柱對象的大小保持一致)let cyShape = new Laya.CylinderColliderShape(0.5,2);//為剛體碰撞器添加碰撞形狀 cyRigid.colliderShape = cyShape;}));/* ……省略若干代碼 */LayaAir內置的基礎碰撞形狀使用示例
內置的碰撞器使用思路為,創建節點對象,創建碰撞器,創建碰撞器形狀,為碰撞器添加碰撞形狀。
我們以創建圓錐形剛體碰撞器為例,編寫代碼如下所示:
/* ……省略若干代碼 *//**增加圓錐形剛體碰撞器 */private addCone():void{//生成隨機值半徑和高let raidius =Math.random()*0.2+0.2;let height =Math.random()*0.5+0.8;//創建圓錐形3D模型節點對象let cone =newLaya.MeshSprite3D(Laya.PrimitiveMesh.createCone(raidius, height));//把圓錐形3D節點對象添加到3D場景節點下this.newScene.addChild(cone);//設置隨機位置this.tmpVector.setValue(Math.random()*6-2,6,Math.random()*6-2); cone.transform.position =this.tmpVector;//為圓錐形3D節點對象創建剛體碰撞器let _rigidBody =<Laya.Rigidbody3D>(cone.addComponent(Laya.Rigidbody3D));//創建圓錐形碰撞器形狀(使用節點對象的值,保持一致性)let coneShape =newLaya.ConeColliderShape(raidius, height);//為剛體碰撞器添加碰撞器形狀 _rigidBody.colliderShape = coneShape;}/* ……省略若干代碼 */其它基礎形狀的創建可參考官網的引擎示例
複合碰撞形狀的使用示例
雖然可以通過Unity導出複合的碰撞形狀,這裡也有必要單獨介紹一下複合碰撞形狀如何通過代碼添加。
複合碰撞形狀主要就是可以添加多個不同的子形狀,理解後其實也是非常簡單。
創建複合碰撞形狀的方式並不複雜,先實例化複合碰撞形狀CompoundColliderShape(),再通過複合碰撞形狀對象的addChildShape方法添加基礎碰撞形狀子對象即可。
我們繼續通過代碼和注釋來理解。編寫代碼如下所示:
/* ……省略若干代碼 */Laya.Mesh.load("res/threeDimen/Physics/table.lm",Laya.Handler.create(this,function(mesh:Laya.Mesh){//讀取Unity導出的桌子模型節點對象,添加到3D場景節點下,var table = scene.addChild(newLaya.MeshSprite3D(mesh))asLaya.MeshSprite3D;//給桌子節點對象添加剛體碰撞器var rigidBody = table.addComponent(Laya.Rigidbody3D)asLaya.Rigidbody3D;//實例化一個複合碰撞形狀對象var compoundShape:Laya.CompoundColliderShape=newLaya.CompoundColliderShape();//創建盒形碰撞形狀var boxShape:Laya.BoxColliderShape=newLaya.BoxColliderShape(0.5,0.4,0.045);//獲取本地偏移var localOffset:Laya.Vector3= boxShape.localOffset;//修改偏移 localOffset.setValue(0,0,0.125); boxShape.localOffset = localOffset;//為複合碰撞形狀對象添加子形狀(剛剛創建的盒形碰撞形狀) compoundShape.addChildShape(boxShape);//後面的代碼都是類似,把一個個的子形狀都添加到複合碰撞形狀對象上。子形狀也可以是別的形狀,例如球形、圓柱形等,根據模型節點的實際情況來。/* ……省略若干boxShapeXX類似的代碼,只保持到boxShape4 */var boxShape4:Laya.BoxColliderShape=newLaya.BoxColliderShape(0.1,0.1,0.3);var localOffset4:Laya.Vector3= boxShape4.localOffset; localOffset4.setValue(0.2,0.153,-0.048); boxShape4.localOffset = localOffset3; compoundShape.addChildShape(boxShape4);//把組合好的複合碰撞形狀添加給剛體碰撞器的碰撞器形狀屬性 rigidBody.colliderShape = compoundShape;}));/* ……省略若干代碼 */2.4 碰撞生命周期方法
生命周期是從開始到結束的完整周期過程,有主動觸發的主幹生命周期方法,例如onAwake()、onEnable()、等。也有被動觸發的事件類生命周期虛方法,這種只有在某個條件達到時才會自動激活,例如,本小節要講的物理事件相關的方法。
2.4.1 物理事件的生命周期方法說明
前文介紹過,檢測物理碰撞的方式有兩種,那物理事件的方法,也對應著兩種。分別是碰撞事件生命周期方法和觸發事件生命周期方法。
碰撞事件生命周期方法說明:
碰撞器之間發生碰撞後,自動激活的事件虛方法。
(點擊放大查看高清圖)
觸發事件生命周期方法說明:
設置為觸發器之後,因物體接觸而自動激活的事件虛方法。
(點擊放大查看高清圖)
特別說明:
碰撞事件的生命周期方法永遠不會與觸發事件的生命周期方法同時激活,只能是碰撞事件或者是觸發事件。並且,如果有一方是觸發器,那兩方一定無法進入碰撞事件,只有進入觸發事件的可能。無論是碰撞事件還是觸發事件的生命周期方法,從進入到離開的順序皆為「Enter,Stay,Stay,……,Exit」。2.4.2 碰撞事件生命周期方法的觸發條件
根據碰撞器的類型不同,並不是所有碰撞器之間,都會觸發碰撞的反饋,以及激活相應的生命周期方法。
下面通過表格的方式,對應了各碰撞器之間是否可觸發碰撞事件的生命周期虛方法。
(點擊放大查看高清圖)
總結:
通過上面的表格,我們發現,靜態碰撞器和運動剛體碰撞器,只能與動力學剛體碰撞器或者是角色碰撞器碰撞才可以觸發碰撞器生命周期方法,靜態碰撞器和運動剛體碰撞器彼此之間,是無法觸發碰撞器生命周期的。
而動力學剛體碰撞器和角色碰撞器,和任意的碰撞器發生碰撞都可以觸發碰撞器生命周期方法。
2.4.3 觸發事件生命周期方法的觸發條件
碰撞器是只能與碰撞器之間碰撞,才有可能進入碰撞器的生命周期,
而觸發器則不然,觸發器不僅與觸發器之間有可能進入觸發器的生命周期,當觸發器與碰撞器之間接觸,也有可能進入觸發器的生命周期,所以,我們分成兩個表來理解。
觸發器與觸發器之間:
(點擊放大查看高清圖)
觸發器與碰撞器之間:
(點擊放大查看高清圖)
總結:
通過上面的兩個表格,我們發現,無論是觸發器與觸發器之間,還是觸發器與碰撞器之間,只有靜態碰撞器與靜態觸發器彼此之間碰撞或者接觸,是無法進入物理觸發事件的。
而其它類型之間接觸,哪怕碰撞器沒有開啟觸發器,甚至沒有觸發器屬性(角色碰撞器),只要有任意一方是觸發器,那也會自動進入觸發器的生命周期。
2.4.4 使用生命周期方法
創建Script3D腳本
生命周期的方法,只能在腳本類裡使用,所以,我們需要創建一個腳本,3D遊戲必須要繼承3D的腳本Script3D。空腳本的示例代碼如下:
/** * TypeScript語言的3D腳本示例 */export default class TSDemo extends Laya.Script3D{ constructor(){super();}}2D腳本與3D腳本不要混用,如果是用IDE創建的腳本模板,需要將繼承的2D腳本類(Laya.Script)改為3D腳本類(Laya.Script3D),
添加物理腳本
只有為節點添加了我們自定義的腳本,我們才可以讓該節點使用生命周期方法。
添加的方式很簡單,直接在代碼中,用節點的addComponent()方法,就可以輕鬆的把繼承了腳本類的3D腳本添加到節點上。
例如,我們創建一個3D盒子,並為其綁定剛剛創建的TSDemo腳本。示例代碼如下:
//引入自定義腳本TSDemoimport TSDemo from"./TSDemo";/** * TypeScript語言示例 */export default class GameUI extends ui.TestSceneUI{/* ……省略若干代碼 */private addBox():void{//創建盒型MeshSprite3Dlet box =this.newScene.addChild(new Laya.MeshSprite3D(Laya.PrimitiveMesh.createBox(0.75,0.5,0.5))) as Laya.MeshSprite3D;//設置材質 box.meshRenderer.material =this.mat1;//設置空間位置let transform = box.transform;let pos = transform.position; pos.setValue(1,6,0); transform.position = pos;//創建剛體碰撞器let _rigidBody = box.addComponent(Laya.Rigidbody3D) as Laya.Rigidbody3D;//創建盒子形狀碰撞器let boxShape =new Laya.BoxColliderShape(0.75,0.5,0.5);//設置盒子的碰撞形狀 _rigidBody.colliderShape = boxShape;//添加自定義腳本組件TSDemo box.addComponent(TSDemo);}/* ……省略若干代碼 */}重寫物理生命周期方法
之前介紹過,物理事件的生命周期方法分別為三個碰撞事件方法和三個觸發事件方法。我們在使用的時候,重寫這些虛方法即可,當物理行為觸發了對應的物理事件就會自動執行。
重寫生命周期方法的示例代碼如下:
/** * TypeScript語言的3D腳本示例 */export default class TSDemo extends Laya.Script3D{ constructor(){super();} onTriggerEnter():void{ /* ……省略若干邏輯代碼 */ console.log("觸發器物理事件onTriggerEnter");} onTriggerStay():void{/* ……省略若干邏輯代碼 */ console.log("觸發器物理事件onTriggerStay");} onTriggerExit():void{/* ……省略若干邏輯代碼 */ console.log("觸發器物理事件onTriggerExit");} onCollisionEnter():void{/* ……省略若干邏輯代碼 */ console.log("碰撞器物理事件onCollisionEnter");} onCollisionStay():void{/* ……省略若干邏輯代碼 */ console.log("碰撞器物理事件onCollisionStay");} onCollisionExit():void{/* ……省略若干邏輯代碼 */ console.log("碰撞器物理事件onCollisionExit");}}2.5 碰撞分組與過濾碰撞組
當我們產生複雜的碰撞需求時,例如,想碰哪個,不碰哪個。這時候就需要進行分組,並指定可以與哪個碰撞組進行碰撞。另外,設置碰撞組過濾,還會優化性能。
各種碰撞器從物理組件父類PhysicsComponent那裡繼承了collisionGroup與canCollideWith屬性,用以實現碰撞分組和指定碰撞組。
2.5.1 碰撞組 collisionGroup
碰撞組的值,我們通常設置為2的N次冪值。如果應用場景比較複雜,需要用到的碰撞分組比較多,記不住太多2的N次冪值,也可以直接使用LayaAir引擎內置的碰撞組工具類。
LayaAir引擎內置了17個碰撞組屬性值,用於過濾不需要的碰撞。
引擎內置的碰撞組工具類為Physics3DUtils。
全部可碰撞的組
由於碰撞組之間的碰撞依據是位運算的按位與,按位與的計算結果非0則可以碰撞,為0則不可碰撞。
Physics3DUtils工具類的COLLISIONFILTERGROUP_ALLFILTER屬性值為-1,-1與任何2的冪值進行按位與都非0,所以取該屬性值為分組時,則所有的碰撞組都可碰撞。使用示例為:
//指定xxx碰撞器所屬哪個碰撞組(-1組與LayaAir任何內置組都可碰撞)xxx.collisionGroup =Laya.Physics3DUtils.COLLISIONFILTERGROUP_ALLFILTER;自定義碰撞分組
LayaAir內置的碰撞組,不包括剛剛講的-1(COLLISIONFILTERGROUP_ALLFILTER),我們可以用的還有10個,分別是COLLISIONFILTERGROUP_CUSTOMFILTER1......10。全都是2的冪,從64到32768。
為了方便記憶,我們可以不記實際值,記住CUSTOMFILTER後1到10的ID號區別即可。
使用示例為:
//指定xxx碰撞器所屬哪個碰撞組(COLLISIONFILTERGROUP_CUSTOMFILTER2對應的值為128)xxx.collisionGroup =Laya.Physics3DUtils.COLLISIONFILTERGROUP_CUSTOMFILTER2;特定的碰撞分組
除了以上的分組外,LayaAir也對接了一些Bullet物理引擎預留的特定分組,用於比較簡單的碰撞過濾需求。
例如,當前場景我們只有動態剛體碰撞器,靜態碰撞器,運動學剛體碰撞器,只是對這幾種碰撞器之間作碰撞過濾,那麼我們就可以分別使用對應的默認碰撞組、靜態碰撞組、運動學剛體碰撞組。
具體的預留分組屬性說明如下:
以上的屬性是原樣對接了Bullet物理引擎,例如碎片碰撞組和字符過濾器的概念,當前的引擎版本還沒有。開發者想用也可以,但建議不採用,推薦使用自定義碰撞分組,以ID為分組標記更便於記憶。
2.5.2 過濾碰撞組 canCollideWith
指定碰撞單個組
碰撞器的canCollideWith屬性可以用於指定與哪個組碰撞,指定哪個,就可以與哪個碰撞。其它的都不可以碰撞,起到了過濾其它碰撞組的效果。
使用示例為:
//指定xxx碰撞器可以與其發生碰撞的碰撞組(本例只與自定義組1碰撞)xxx.canCollideWith =Laya.Physics3DUtils.COLLISIONFILTERGROUP_CUSTOMFILTER1;指定碰撞多個組
如果我們想碰撞多個組,可以採用位運算的按位或| ,去指定多個可以與其發生碰撞的碰撞組。
使用示例為:
//指定xxx碰撞器可以與其發生碰撞的碰撞組(本例只與自定義組1、2、5進行碰撞)xxx.canCollideWith =Laya.Physics3DUtils.COLLISIONFILTERGROUP_CUSTOMFILTER1 |Laya.Physics3DUtils.COLLISIONFILTERGROUP_CUSTOMFILTER2 |Laya.Physics3DUtils.COLLISIONFILTERGROUP_CUSTOMFILTER5;關於位運算用於碰撞的基礎原理,可以點擊閱讀《物理引擎的碰撞分組,適用2D和3D》
指定不可碰撞的組
在多個碰撞分組的情況下,如果我們只想排除掉某個或者某幾個碰撞組不與其發生碰撞,與其它所有的碰撞組發生碰撞如何處理呢?
這時候可以通過異或運算符^來實現。用 -1去異或^任何2的冪值,那該值的碰撞組就不會被碰撞。
使用示例為:
//指定不可以與其發生碰撞的碰撞組(本例將不與自定義組2、5進行碰撞,除自定義2與5組之外,都可以發生碰撞)xxx.canCollideWith =Laya.Physics3DUtils.COLLISIONFILTERGROUP_ALLFILTER ^Laya.Physics3DUtils.COLLISIONFILTERGROUP_CUSTOMFILTER2 ^Laya.Physics3DUtils.COLLISIONFILTERGROUP_CUSTOMFILTER5;三、物理約束
在物理世界中,有些物體的運動會受到其它物體的影響,例如:人體關節、鐘擺、鏈條、滑輪組、等等。
這種限制物體運動,避免其運動超出一定限度的物理方法就是約束。由於其還具有著關節的特性表現,所以有些引擎也稱為關節。
3.1 LayaAir支持哪些約束
目前在LayaAir引擎中只支持兩種,分別是固定約束Fixed Constraint和可配置約束Configurable Constraint。
Unity裡的固定關節組件Fixed Joint可導出對應固定約束Fixed Constraint,可配置關節組件Configurable Joint可導出對應可配置約束Configurable Constraint。
固定約束是比較常用的約束,而可配置約束可以模擬任意約束的效果,所以這兩種約束可以滿足絕大多數的常用需求。
3.2 固定約束Fixed Constraint
固定約束將對象的移動限制為依賴於另一個對象,一個物體產生位移變化 ,另一個與其約束的物體也會隨之變化 。有些類似父子節點關係,但它與父子節點不同,位移不是通過transform實現,而是基於物理引擎。
固定關節類似2D物理(Box2D)裡的焊接關節,適用於遊戲中的物體對象永久或暫時粘在一起的需求,最好是兩個沒有父子關係的物理一起運動。好處是不必通過腳本更改對象的層級視圖來實現所需的效果。代價是所有使用固定關節的對象都必須使用剛體。
LayaAir引擎支持Unity中固定關節Fixed Joint的三個屬性(Connected Body、Break Force、Break Torque)是支持導出使用的。如下圖12所示。
(圖12)
3.2.1 設置連接剛體 setConnectRigidBody
Unity中的Connected Body對應LayaAir的設置連接剛體setConnectRigidBody,
setConnectRigidBody用於指定固定約束要連接的剛體,若不指定,則該約束連接到世界。
3.2.2 斷開力 breakForce
Unity中的Break Force對應LayaAir的斷開力breakForce,
breakForce用於設置破壞固定約束需要施加的最大力。
3.2.3 斷開力矩 breakTorque
Unity中的Break Torque對應LayaAir的斷開力矩 breakTorque,
breakTorque用於設置破壞固定約束需要施加的最大力矩。
3.3 可配置約束Configurable Constraint
可配置約束可實現各種約束類型的所有功能,比如上文介紹過的固定約束,也可以通過可配置約束來實現,並且提供更強大的角色移動控制。
當開發者想要自定義布娃娃的運動並對角色強制實施某些姿勢時,這種約束特別有用。使用可配置約束還可以將約束修改為開發者自行設計的高度專業化約束。
LayaAir引擎支持Unity中可配置關節的屬性如圖13的紅框中所示。下面將逐一詳細介紹。
(圖13)
Unity的可配置關節Configurable Joint裡的斷開力Break Force和斷開力矩Break Torque在LayaAir中也是支持的,由於上個小節已介紹,本小節不再重複介紹。所以圖14中也沒有體現。
3.3.1 設置連接剛體 setConnectRigidBody
Unity中的Connected Body對應LayaAir的設置連接剛體setConnectRigidBody,
setConnectRigidBody用於指定固定約束要連接的剛體,若不指定,則該約束連接到世界。
3.3.2 錨點 anchor
Unity中的Anchor對應LayaAir的錨點anchor ,
錨點anchor 是用於定義自身剛體約束中心的點。物理模擬會使用此點作為計算的中心點。
3.3.3 主軸 axis
Unity中的Axis對應LayaAir的主軸 axis ,
主軸 axis用於基於物理模擬來定義對象自然旋轉的局部軸,該軸決定了對象在物理模擬下自然旋轉的方向。
3.3.4 連接錨點 connectAnchor
Unity中的Connected Anchor 對應LayaAir的連接錨點connectAnchor ,
連接錨點connectAnchor 用於設置所連接剛體的約束錨點。
例如自己是車輪,連接的剛體是車身。那錨點就是車輪的約束中心點,連接錨點就是所連接的車身約束中心點。
3.3.5 副軸 secondaryAxis
Unity中的Secondary Axis 對應LayaAir的副軸secondaryAxis,
副軸secondaryAxis的作用是與主軸axis共同定義了約束的局部坐標系。第三個軸會與這兩個軸所構成的平面相垂直。
3.3.6 沿XYZ軸平移約束模式 (X\Y\Z)Motion
Unity中的X Motion, Y Motion, Z Motion 對應LayaAir的 XMotion, YMotion, ZMotion,
(X\Y\Z)Motion是表示沿 X、Y 、Z 軸平移約束的模式,根據屬性設置的不同,約束的模式也不同。可以設置的值分別是:自由移動Free、鎖定移動 Locked、限制性移動 Limited。
自由移動Free就是不作限制的沿某軸移動。
鎖定移動 Locked是沒有運動,完全固定住。
限制性移動 Limited是平移運動受限於用戶定義的約束。
3.3.7 繞XYZ軸旋轉的角運動約束模式angular (X\Y\Z)Motion
Unity中的Angular X Motion, Angular Y Motion, Angular Z Motion 對應LayaAir的 angularXMotion, angularYMotion, angularZMotion,
angular (X\Y\Z)Motion是表示繞X、Y 、Z 軸旋轉的角運動約束模式,也是根據自由移動Free、鎖定移動 Locked、限制性移動 Limited三種值的設置來區別約束模式,與(X\Y\Z)Motion類似,只是運動形式的線性平移和角運動旋轉的區別。
3.3.8 彈簧線性限制 (linearLimitSpring、linearDamp)
Unity中的 Linear Limit Spring 是當對象超過了限制位置時要拉回對象而施加的彈簧力。該項有兩個配置參數,彈簧力Spring與阻尼Damper。
彈簧力Spring
其中的彈簧力Spring 在LayaAir引擎中對應線性限制的彈簧力linearLimitSpring,如果此處的值設置為零,則無法逾越限制;零以外的值將使限制變得有彈性。
阻尼Damper
其中的阻尼Damper在LayaAir引擎中對應線性阻尼linearDamp,設置為大於零的值可讓約束抑制振蕩(否則將不斷的進行振蕩)。
3.3.9 線性移動限制(minLinearLimit、maxLinearLimit、linearBounce)
Unity中的Linear Limit是關節線性移動的限制,LayaAir導出插件只支持該項的限制Limit與反彈力Boundciness設置。
限制Limit
其中的Limit是從原點到限制位置的距離。在LayaAir引擎中需要分別設置線性移動限制的最小值minLinearLimit和線性移動限制的最大值maxLinearLimit。
反彈力Boundciness
其中的反彈力 Bounciness 是當對象達到限制距離時,要將對象拉回而施加的彈力。在LayaAir引擎中對應線性反彈力linearBounce。
3.3.10 彈簧角運動限制(angularLimitSpring、angularDamp)
在Unity中,彈簧的角運動旋轉限制分為X軸旋轉限制Angular X Limit Spring以及Y軸和Z軸旋轉限制Angular YZ Limit Spring。這些限制都是當對象超過了約束的限制角度時要反向旋轉對象而施加的彈簧力矩,只是軸的區別。而且他們都有彈簧力Spring與阻尼Damper兩個配置項,
彈簧力Spring
其中的彈簧力Spring 在LayaAir引擎中對應角運動旋轉限制的彈簧力angularLimitSpring,如果此處的值設置為零,則無法逾越限制;零以外的值將使限制變得有彈性。
阻尼Damper
其中的阻尼Damper在LayaAir引擎中對應角運動旋轉阻尼angularDamp,設置為大於零的值可讓約束抑制振蕩(否則將不斷的進行振蕩)。
3.3.11 角運動限制(minAngularLimit、maxAngularLimit、angularBounce)
在Unity中,關於角運動旋轉的限制有X軸旋轉的下限Low Angular X Limit、X軸旋轉的上限Hight Angular X Limit、Y軸旋轉的限制Angular Y Limit、Z軸旋轉的限制Angular Z Limit。這些限制項,LayaAir導出插件只支持限制Limit與反彈力Boundciness設置。
限制Limit
其中的Limit是限制旋轉角度,設置對象旋轉角度的下限值。在LayaAir引擎中需要分別設置旋轉角度限制的最小值minAngularLimit和旋轉角度限制的最大值maxAngularLimit。這兩個值都是3D向量值。
旋轉限制最小值的X對應X軸旋轉的下限Low Angular X Limit值,Y對應Y軸旋轉的限制Angular Y Limit值取負,Z對應Z軸旋轉的限制Angular Z Limit值取負。
旋轉限制最大值的X對應X軸旋轉的上限Hight Angular X Limit值,Y對應Y軸旋轉的限制Angular Y Limit值,Z對應Z軸旋轉的限制Angular Z Limit值。
反彈力Boundciness
其中的反彈力 Bounciness 是當對象的旋轉達到限制角度時在對象上施加的反彈力矩。在LayaAir引擎中對應角度反彈力矩angularBounce。
四、物理射線
4.1 什麼是物理射線
射線的定義是只有一個端點無限延長形成的直的線。LayaAir引擎的數學對象Laya.Ray()就是只有起點和方向的射線。
在LayaAir引擎中,射線常用於基礎的碰撞檢測,所以具有射線的發射特性,用於碰撞檢測功能的射線稱為物理射線。
需要注意的是,射線可以用於物理射線檢測,但是物理射線並不等同於射線。
4.2 創建射線
LayaAir引擎提供了創建3D空間射線的類Laya.Ray(),以及通過攝像機從屏幕空間點去生成這個射線的方法viewportPointToRay()。
示例代碼如下所示:
/*……省略若干代碼*///創建一個屏幕點let point =newLaya.Vector2();//創建一個射線 Laya.Ray(射線的起點,射線的方向)let ray =newLaya.Ray(newLaya.Vector3(0,0,0),newLaya.Vector3(0,0,0));//以滑鼠點擊的點作為原點point.x =Laya.stage.mouseX;point.y =Laya.stage.mouseY;//計算一個從屏幕空間生成的射線_camera.viewportPointToRay(point, ray);/*……省略若干代碼*/4.3 使用物理射線
在LayaAir 3D中實現射線檢測是使用物理模擬器類PhysicsSimulation。
射線檢測的方法有4個,分別為射線檢測第一個碰撞物體的方法raycast 和 raycastFromTo以及射線檢測所有碰撞物體的方法raycastAll和raycastAllFromTo。
檢測一個和所有的區別比較容易理解,就是碰到第一個物體後射線立即結束,和射線可穿透所有碰撞物體一直不結束,這兩種區別。如圖14所示。
(圖14)
那為什麼同樣的功能名稱還有帶FromTo和不帶FromTo兩種,又有什麼區別呢?
與數學對象的射線所不同的是,用於檢測碰撞的物理射線是有長度的,或者是需要設置世界空間的結束位置。
帶FromTo的是使用兩個點(射線的起始位置點和結束位置點)作為參數。
而不帶FromTo的則是直接使用已經創建好的射線,不需要設置射線的結束位置點,但需要設置長度,如果我們不設置長度,則採用默認值長度2147483647。
如果是不帶FromTo的射線檢測,我們可以沿用上個小節創建射線的示例,稍加補充一下,具體代碼如下所示:
/*……省略若干代碼*///創建一個屏幕點let point =new Laya.Vector2();//創建一個射線 Laya.Ray(射線的起點,射線的方向)let ray =new Laya.Ray(new Laya.Vector3(0,0,0),new Laya.Vector3(0,0,0));//以滑鼠點擊的點作為原點point.x =Laya.stage.mouseX;point.y =Laya.stage.mouseY;//計算一個從屏幕空間生成的射線_camera.viewportPointToRay(point, ray);//拿到3D場景中射線碰撞的物體_scene3D.physicsSimulation.rayCastAll(ray,this.outs);//如果射線碰撞到物體if(this.outs.length !==0){for(let i =0; i <this.outs.length; i++){//在射線擊中的位置添加一個立方體this.addBoxXYZ(this.outs[i].point.x,this.outs[i].point.y,this.outs[i].point.z );}}/*……省略若干代碼*/帶FromTo的射線檢測使用示例,具體代碼如下所示:
/*……省略若干代碼*//*進行射線檢測,檢測所有碰撞的物體//_scene3D.physicsSimulation.raycastAllFromTo(this.from, this.to, this.outs);//檢測所有物體的射線使用與上個示例類似*///進行射線檢測,檢測第一個碰撞物體_scene3D.physicsSimulation.raycastFromTo(this.from,this.to,this.out);//將射線碰撞到的物體設置為紅色((this.out.collider.owner asLaya.MeshSprite3D).meshRenderer.sharedMaterial as Laya.BlinnPhongMaterial).albedoColor =new Laya.Vector4(1.0,0.0,0.0,1.0);/*……省略若干代碼*/4.4 使用異形物理射線
常規的物理射線是用一條射線來檢測碰撞,LayaAir引擎中也提供了與物理射線檢測類似的功能,但採用的是自定義碰撞器形狀檢測來代替物理射線,相當於異形的射線檢測功能。
與普通的射線檢測一樣,異形射線也是有檢測第一個和檢測所有兩個檢測方法,分別是shapeCast和shapeCastAll。
(圖15)
圖15的示例,就採用球形射線來實現碰撞檢測,具體代碼如下所示:
//創建球型碰撞器var sphereCollider:Laya.SphereColliderShape=new Laya.SphereColliderShape(0.5);//通過按鈕this.castAll狀態切換是採用檢測全部還是檢測第一個if(this.castAll){//採用球形碰撞器進行形狀檢測,檢測所有碰撞的物體this.scene.physicsSimulation.shapeCastAll(sphereCollider,this.from,this.to,this.outs);for(let i =0; i <this.outs.length; i++){((this.outs[i].collider.owner as Laya.MeshSprite3D).meshRenderer.sharedMaterial asLaya.BlinnPhongMaterial).albedoColor =new Laya.Vector4(1.0,0.0,0.0,1.0);}else{//採用球形碰撞器進行形狀檢測,檢測第一個碰撞物體if(this.scene.physicsSimulation.shapeCast(sphereCollider,this.from,this.to,this.out))((this.out.collider.owner as Laya.MeshSprite3D).meshRenderer.sharedMaterial as Laya.BlinnPhongMaterial).albedoColor =new Laya.Vector4(1.0,0.0,0.0,1.0);}4.5 設置射線碰撞組
無論是普通射線還是異形射線,都可以設置碰撞組,以及指定射線可碰撞的組。
設置碰撞組值collisonGroup和指定可發生碰撞的組值canCollideWith在前文中已經介紹過,
我們將值帶入射線檢測對應的方法參數即可實現射線的選擇性碰撞。
五、Cannon.js物理引擎的使用
之前的章節一直在介紹LayaAir基於Bullet物理引擎封裝的物理引擎API。Bullet雖然強大,但是有些開發者對於物理精度要求不高,物理功能的使用也比較基礎,只對物理引擎庫的體積有要求(Cannon物理引擎庫不足200k)。那或許Cannon.js可以考慮。
5.1 如何切換使用Cannon.js物理引擎庫
如果想使用Cannon.js物理引擎庫,要引入物理引擎庫cannon.js以及LayaAir引擎封裝的物理API庫laya.cannonPhysics.js。
採用LayaAirIDE創建項目的開發者,可以直接在IDE中通過F9打開項目設置的類庫設置,如圖16進行勾選即可。
(圖16)
如果不打算使用Bullet物理引擎,Physics3D相關的物理庫不需要勾選。
引用了cannon.js與laya.cannonPhysics.js引擎庫後,就可以直接使用Cannon.js物理引擎的API了。
在LayaAir封裝的物理API的設計上,基本上與Bullet引擎保持了一致,只是在命名前增加了Cannon標識,例如3D剛體Rigidbody3D在Cannon物理引擎的使用時變為了CannonRigidbody3D。
畢竟Cannon.js物理引擎與Bullet物理引擎本身存在差異,以及對於Cannon.js的基礎物理功能使用的定位,Bullet中有很多API,在Cannon.js中是沒有的。關於這些,開發者可以通過下一小節進行詳細了解。
5.2 Cannon.js物理引擎中可以使用的API
Cannon.js物理引擎與Butllet的使用方式基本相同,只是類名稱會有所不同,本小節列出常用的API供開發者參考。
5.2.1 碰撞器
(點擊放大查看高清圖)
5.2.2 常用的剛體物理屬性
(點擊放大查看高清圖)
5.2.3 碰撞器形狀
5.2.4 常用的碰撞器屬性
(點擊放大查看高清圖)
5.2.5 射線檢測
射線檢測基於Cannon物理模擬器類CannonPhysicsSimulation
(點擊放大查看高清圖)
5.2.6 碰撞生命周期
LayaAir引擎的3D物理碰撞生命周期,適用於Cannon.js引擎與Bullet引擎,參考前文的碰撞生命周期方法即可。
END
引擎案例分享:
聊聊電商圈成功的遊戲跨界案例3D研發經驗分享:50多款3D小遊戲的炫稷遊戲創始人程銀斌分享3D研發經驗!3D技術分享:有著30多款3D小遊戲產品的長沙嗨鹿互動科技資深研發工程師分享3D遊戲研發經驗132款3D跑酷極限運動主題的微信小遊戲分享LayaAir引擎78款3D射擊主題微信小遊戲分享,看看玩過幾款!