1.4 基于Unity整合BEPUphysicsint物理引擎實(shí)戰(zhàn)
上一節(jié)我們?cè)敿?xì)的講解BEPUphysicsint 的物理事件蜻懦。此物理引擎會(huì)產(chǎn)生了碰撞事件與非碰撞事件,碰撞事件大家好理解吴旋,非碰撞事件例如: 物理Entity的update事件,Entity的activation/deactivation事件等。本節(jié)課來(lái)實(shí)戰(zhàn)如何編譯BEPUphysicsint源碼到自己的項(xiàng)目, 如何整合物理引擎與Unity圖形渲染厢破。本文分成4個(gè)部分:
對(duì)惹荣瑟,這里有一個(gè)游戲開(kāi)發(fā)交流小組,希望大家可以點(diǎn)擊進(jìn)來(lái)一起交流一下開(kāi)發(fā)經(jīng)驗(yàn)呀
1: 源碼編譯, 將github上的源碼下載下來(lái)編譯到Unity項(xiàng)目;
2: 在Unity中初始化BEPUphysicsint的物理世界;
3: 編寫(xiě)PhyBoxEntity組件創(chuàng)建物理Entity并同步Unity圖像;
4: 使用Unity自帶碰撞器組件,來(lái)生成創(chuàng)建PhyBoxEntity;
創(chuàng)建項(xiàng)目,源碼編譯BEPUphysicsint
準(zhǔn)備源碼摩泪,我們從github上面下載下來(lái)BEPUphysicsint的源碼笆焰,下載地址為:
https://github.com/sam-vdp/bepuphysics1int
下載下來(lái)以后,解壓開(kāi)來(lái)代碼截圖,如圖1.4-1:
圖1.4-1 項(xiàng)目源碼目錄截圖
下載下來(lái)源碼后怎么用到Unity(百度也百度不到)见坑,我的方法是先分析文件夾嚷掠,搞清楚每個(gè)文件夾的大概的功能與作用,看下項(xiàng)目,打開(kāi)vs的工程BEPUphysics.sln看下源碼項(xiàng)目如何組織的鳄梅,如圖1.4-2:
圖1.4-2 工程目錄截圖
結(jié)合我們的文件夾叠国,我們大膽來(lái)猜測(cè)分析下代碼的作用未檩,哪些我們可能需要的戴尸,哪些要?jiǎng)h除的。分析時(shí)冤狡,我一般會(huì)先根據(jù)名字來(lái)猜測(cè)孙蒙,再核對(duì)一下里面的代碼。BEPUbenchmark/****BEPUfloatBenchmark,我先查找名字benchmark的意思,benchmark的英文有類(lèi)似單元測(cè)試悲雳,實(shí)例代碼的意思挎峦,所以這兩個(gè)文件夾下的代碼應(yīng)該是供我們整合到項(xiàng)目做的實(shí)例參考,可以閱讀里面的源碼合瓢,但是不用放Unity源碼編譯坦胶。進(jìn)一步核實(shí)一下里面的代碼驗(yàn)證一下自己的判斷顿苇,打開(kāi)入口文件Benchmark.cs,里面講解的就是如何setup物理世界等峭咒。如圖1.4-3所示:
圖1.4-3 示例代碼構(gòu)建物理世界
**BEPUik/ ****BEPUphysics: **ik與physics看上去就是物理引擎的核心代碼庫(kù),打開(kāi)代碼大概驗(yàn)證一下纪岁,ik里面有IKJoint.cs相關(guān)代碼, physics里面有Entity, Space等代碼,所以這個(gè)應(yīng)該是核心代碼凑队,等會(huì)要放入U(xiǎn)nity項(xiàng)目工程。如圖1.4-4所示
圖1.4-4 物理引擎核心代碼
經(jīng)過(guò)類(lèi)似的分析名字與內(nèi)容幔翰,我們發(fā)現(xiàn)BEPUutilities(工具代碼)+ BEPUik+ BEPUphysics+ FixedMath.Net文件夾所對(duì)應(yīng)的代碼為引擎編譯所依賴(lài)的必須代碼, ConversionHelper這里是向量定點(diǎn)與浮點(diǎn)的轉(zhuǎn)換代碼漩氨,在Unity中可能用到或參考,可以考慮放項(xiàng)目中遗增。
接下來(lái)創(chuàng)建Unity項(xiàng)目來(lái)編譯源碼(參考unity 2020.3.33f1版本)叫惊。項(xiàng)目創(chuàng)建完成后分好文件夾,這里我分了3個(gè)一級(jí)目錄文件夾: AssetsPackage, Scenes, Scripts,Scripts又分為了3rd, Framework, 兩個(gè)文件夾,如圖1.4-5:
圖1.4-5 物理引擎核心代碼
其中3rd用來(lái)存放第三方的代碼贡定,我在里面又新建了一個(gè)BEPU的文件夾,拷貝我們分析的必須代碼到Unity項(xiàng)目赋访。
圖1.4-6 移植物理引擎源碼到項(xiàng)目
不出意外的話(huà),大量的報(bào)錯(cuò),主要分成幾類(lèi), AssemblyInfo.cs 里面的代碼報(bào)錯(cuò)缓待,打開(kāi)這個(gè)代碼一看蚓耽,沒(méi)有什么用,直接連同里面的” Properties”一起刪除掉旋炒。又發(fā)現(xiàn)源碼文件夾里面”bin”與”obj”文件夾步悠,這個(gè)是源碼工程的編譯時(shí)生成的目錄,刪掉瘫镇。如圖1.4-7:
圖1.4-7 刪除原項(xiàng)目工程編譯文件夾
接著就是BoxBoxCollider.cs代碼報(bào)錯(cuò)鼎兽,打開(kāi)代碼一看,對(duì)比原版铣除,發(fā)現(xiàn)原版里面有個(gè)編譯的宏開(kāi)關(guān)ALLOWUNSAFE,原版本打開(kāi)了谚咬,Unity版本沒(méi)有,我們?cè)赨nity PlayerSetting里面加上這個(gè)宏尚粘,如圖1.4-8:
圖1.4-8 打開(kāi)編譯宏開(kāi)關(guān)
經(jīng)過(guò)這些操作择卦,代碼很神奇的就全部編譯過(guò)了。接下來(lái)往Unity里來(lái)構(gòu)建我們的物理世界了郎嫁。
在Unity中初始化BEPUphysicsint的物理世界
這個(gè)簡(jiǎn)單多了秉继,打開(kāi)BEPUphysicsint的benchmark代碼參考,copy過(guò)來(lái)就可以了。初始化的時(shí),主要做的事情有: 構(gòu)建物理世界, 配置物理世界重力泽铛,設(shè)置物理迭代的參數(shù)尚辑,關(guān)閉原來(lái)Unity自帶物理引擎, Update中迭代物理世界。這里是全局執(zhí)行一次盔腔,新建一個(gè)BEPUPhyMgr.cs的全局單例杠茬,用來(lái)做初始化月褥。代碼實(shí)現(xiàn)在Framework/BEPUWrapper文件夾下,直接上代碼:
public classBEPUPhyMgr : MonoBehaviour{
public BEPUphysics.Space space;
publicstatic BEPUPhyMgr Instance = null;
publicvoidAwake() {
if (BEPUPhyMgr.Instance != null) {
return;
}
Physics.autoSimulation = false; // 關(guān)閉原來(lái)物理引擎迭代;
// Physics.autoSyncTransforms = false; // 關(guān)閉射線檢測(cè)功能
BEPUPhyMgr.Instance = this; // 初始化單例
this.space = new BEPUphysics.Space(); // 創(chuàng)建物理世界
this.space.ForceUpdater.gravity = new BEPUutilities.Vector3(0, -9.81m, 0); // 配置重力
this.space.TimeStepSettings.TimeStepDuration = 1 / 60m; // 設(shè)置迭代時(shí)間間隔
}
public void Update() {
this.space.Update(); // 模擬迭代物理世界
}}
在Unity中創(chuàng)建一個(gè)GameApp空節(jié)點(diǎn)瓢喉,掛這個(gè)BEPUPhyMgr組件來(lái)做初始化(這里是測(cè)試,具體在項(xiàng)目框架中做初始化)吓坚。創(chuàng)建一個(gè)Cube與Plane, 刪除掉它原來(lái)的物理碰撞器。如圖1.4-9所示:
圖1.4-9
創(chuàng)建物理Entity并同步Unity圖像
構(gòu)建完物理世界后,我們就是要往物理世界里面放物理的Entity, 物理世界迭代計(jì)算Entity,然后再把Entity里面的位置灯荧,旋轉(zhuǎn)等信息同步到Unity的圖像節(jié)點(diǎn)的Transform中礁击,這樣物理的Entity就可以帶著圖像的節(jié)點(diǎn)移動(dòng)了。這個(gè)需要我們開(kāi)發(fā)者來(lái)自己開(kāi)發(fā)逗载,同時(shí)Entity的類(lèi)型有很多哆窿,我們以Box Entity為例,主要做2件事情:
(1) 根據(jù)用戶(hù)給定的物體大小與物體是否可移動(dòng),來(lái)創(chuàng)建對(duì)應(yīng)的物理Entity;
(2) 對(duì)于可移動(dòng)的物體厉斟,我們每次update同步Entity位置等到Unity Transform組件;
新建一個(gè)PhyBoxEntity.cs的組件到Framework/BEPUWrapper文件夾下挚躯。構(gòu)建物理Entity Box需要長(zhǎng),寬擦秽,高码荔,我們也設(shè)定相關(guān)參數(shù),物體是否可以移動(dòng),給一個(gè)bool給用戶(hù)選擇感挥。有時(shí)候我們要調(diào)整物理的中心(與標(biāo)準(zhǔn)Unity物理引擎類(lèi)似)缩搅,我們加個(gè)相當(dāng)于Unity節(jié)點(diǎn)的中心偏移:
public class PhyBoxEntity : MonoBehaviour{
BEPUphysics.Entities.Prefabs.Box box;
public bool isStatic = false;
private float width = 1;
private float height = 1;
private float length = 1;
private float centerX = 0, centerY = 0, centerZ = 0;
}
根據(jù)這些設(shè)定,我們來(lái)使用BEPUphysics來(lái)創(chuàng)建Box的Entity,其它的類(lèi)似触幼,并加入到物理世界硼瓣。
if (isStatic) {
this.box = new BEPUphysics.Entities.Prefabs.Box(new BEPUutilities.Vector3(0, 0, 0), System.Convert.ToDecimal(this.width),
System.Convert.ToDecimal(this.height), System.Convert.ToDecimal(this.length));
}
else {
this.box = new BEPUphysics.Entities.Prefabs.Box(new BEPUutilities.Vector3(0, 0, 0), System.Convert.ToDecimal(this.width),
System.Convert.ToDecimal(this.height), System.Convert.ToDecimal(this.length), 1);
}
BEPUPhyMgr.Instance.space.Add(box);
接下來(lái)同步Unity的圖像節(jié)點(diǎn)的Transform到物理Entity,直接把物體Transform的位置同步設(shè)置到Entity里面。
Vector3 pos = this.transform.position;
this.box.position = new BEPUutilities.Vector3(System.Convert.ToDecimal(pos.x + this.centerX), System.Convert.ToDecimal(pos.y + this.centerY),
System.Convert.ToDecimal(pos.z + this.centerZ));
最后對(duì)于移動(dòng)的物體,我們?cè)贚ateUpdate里面同步物理Entity位置到Unity Transform置谦。
public void LateUpdate() {
if (this.isStatic) {
return;
}
// FixMath.NET.Fix64 g = 9.81m;
// Debug.Log(g.ToString());
BEPUutilities.Vector3 worldPos = this.box.position;
// Debug.Log(worldPos.ToString());
double x = System.Convert.ToDouble((decimal)worldPos.X);
double y = System.Convert.ToDouble((decimal)worldPos.Y);
double z = System.Convert.ToDouble((decimal)worldPos.Z);
this.transform.position = new Vector3((float)x - this.centerX, (float)y - this.centerY, (float)z - this.centerY);
}
掛一個(gè)BoxPhyEntity組件到Cube節(jié)點(diǎn)堂鲤,你會(huì)神奇的發(fā)現(xiàn)Cube在物理引擎的迭代作用下掉下去了。
使用Unity自帶碰撞器組件,來(lái)生成創(chuàng)建PhyBoxEntity
這里還有一個(gè)問(wèn)題媒峡,就是物理Entity的大小設(shè)置無(wú)法像Unity自帶的物理引擎一樣瘟栖,可視化編輯,其實(shí)這個(gè)問(wèn)題很好解決谅阿,我們的策略是添加Unity對(duì)應(yīng)的物理碰撞器組件 (如: BoxCollider)半哟,利用Unity做好的功能特性來(lái)編輯物理Entity大小與中心,運(yùn)行的時(shí)候讀取數(shù)據(jù)即可奔穿。這里我做了一個(gè)簡(jiǎn)單的轉(zhuǎn)化镜沽,代碼如下:
[RequireComponent(typeof(BoxCollider))]
添加PhyBoxEntity組件的時(shí)候敏晤,必要要添加BoxCollider組件贱田。
BoxCollider boxPhy = this.GetComponent<BoxCollider>();
this.width = boxPhy.size.x;
this.height = boxPhy.size.y;
this.length = boxPhy.size.z;
this.centerX = boxPhy.center.x;
this.centerY = boxPhy.center.y;
this.centerZ = boxPhy.center.z;
初始化的時(shí)候,從碰撞器中讀取大小數(shù)據(jù),這樣就可以關(guān)聯(lián)到Unity自帶的形狀嘴脾。后續(xù)你開(kāi)發(fā)PhyBoxEntity, PhySphereEntity等都可以通過(guò)這樣的方式來(lái)整合男摧。同時(shí)這些Entity可以繼承一個(gè)基類(lèi)PhyBaseEntity, 來(lái)做些設(shè)計(jì)上的調(diào)整蔬墩。最后上下效果圖,我添加一個(gè)Cube與一個(gè)平面到物理世界耗拓,效果如下:
今天的分享就到這里了拇颅,關(guān)注我們,可以獲取Unity BEPUphysint3D實(shí)戰(zhàn)源碼乔询。