這篇“Unity怎么實(shí)現(xiàn)表面接觸保持聯(lián)系”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“Unity怎么實(shí)現(xiàn)表面接觸保持聯(lián)系”文章吧。
在網(wǎng)站制作、成都網(wǎng)站制作過(guò)程中,需要針對(duì)客戶的行業(yè)特點(diǎn)、產(chǎn)品特性、目標(biāo)受眾和市場(chǎng)情況進(jìn)行定位分析,以確定網(wǎng)站的風(fēng)格、色彩、版式、交互等方面的設(shè)計(jì)方向。創(chuàng)新互聯(lián)建站還需要根據(jù)客戶的需求進(jìn)行功能模塊的開(kāi)發(fā)和設(shè)計(jì),包括內(nèi)容管理、前臺(tái)展示、用戶權(quán)限管理、數(shù)據(jù)統(tǒng)計(jì)和安全保護(hù)等功能。
本教程使用Unity 2019.2.14f1創(chuàng)建。它還使用ProBuilder軟件包。
效果之一
跑酷的球。
貼在地面
當(dāng)我們的球體到達(dá)坡道的頂部時(shí),由于其向上的動(dòng)量,它開(kāi)始飛行。這是現(xiàn)實(shí)的,但可能并不理想。
球體在坡道頂部飛行。
當(dāng)球體突然突然出現(xiàn)小的高差時(shí),也會(huì)發(fā)生類似的情況。我制作了一個(gè)測(cè)試場(chǎng)景,以0.1增量的步長(zhǎng)演示了這一步。
步驟測(cè)試場(chǎng)景。
如果步距不太高,則以足夠的速度接近時(shí),球體會(huì)反彈。在測(cè)試場(chǎng)景中,這對(duì)于平坦車道甚至很少發(fā)生,因?yàn)槲沂峭ㄟ^(guò)將步高降低到零而不合并頂點(diǎn)來(lái)實(shí)現(xiàn)的。這會(huì)產(chǎn)生所謂的幻影碰撞。應(yīng)該設(shè)計(jì)場(chǎng)景幾何圖形以避免這種情況,但我堅(jiān)持指出來(lái)。
彈起臺(tái)階。
在現(xiàn)實(shí)生活中,有多種技術(shù)可以將某些東西固定在地面上。例如,一級(jí)方程式賽車旨在將氣流轉(zhuǎn)換為下壓力。因此,為我們的領(lǐng)域做類似的事情有現(xiàn)實(shí)的基礎(chǔ)。
碰撞時(shí)間
讓我們考慮一下球體將從坡道發(fā)射的瞬間。為了使它粘在表面上,我們必須對(duì)其速度進(jìn)行調(diào)整,使其與表面重新對(duì)齊。讓我們檢查一下何時(shí)可以收到所需信息。通過(guò)在基于 OnGround
的 Update
中調(diào)整其顏色,將球體不在地面上時(shí)將其變?yōu)榘咨?,這與上一教程末尾展示的顏色類似
void Update () { … GetComponent().material.SetColor( "_Color", OnGround ? Color.black : Color.white ); }
要觀察準(zhǔn)確的時(shí)間,請(qǐng)暫時(shí)減少物理時(shí)間步長(zhǎng)和時(shí)間范圍。
三個(gè)物理步驟;時(shí)間步長(zhǎng)0.2;時(shí)標(biāo)0.5。
球體發(fā)射的物理步驟仍然存在碰撞。我們會(huì)在下一步中根據(jù)這些數(shù)據(jù)采取行動(dòng),因此我們認(rèn)為我們已經(jīng)扎根,而不再需要。這是我們不再獲取碰撞數(shù)據(jù)之后的步驟。因此,我們總是會(huì)為時(shí)已晚,但是只要我們意識(shí)到這一點(diǎn)就不會(huì)有問(wèn)題。
自上次接地以來(lái)的步驟
讓我們跟蹤一下自從我們接地以來(lái)已經(jīng)采取了多少物理步驟。為其添加一個(gè)整數(shù)字段,并在 UpdateState
的開(kāi)頭將其遞增。然后,如果事實(shí)證明我們?cè)诘孛嫔?,則將其設(shè)置回零。我們將使用它來(lái)確定何時(shí)應(yīng)該抓緊地面。它對(duì)于調(diào)試也很有用。
int stepsSinceLastGrounded; … void UpdateState () { stepsSinceLastGrounded += 1; velocity = body.velocity; if (OnGround) { stepsSinceLastGrounded = 0; jumpPhase = 0; if (groundContactCount > 1) { contactNormal.Normalize(); } } else { contactNormal = Vector3.up; } }
我們不必防范整數(shù)溢出嗎?
我們不必為此擔(dān)心。整數(shù)不溢出將需要花費(fèi)幾個(gè)月的實(shí)時(shí)時(shí)間。
抓拍
添加 SnapToGround
方法,該方法可在需要時(shí)將我們固定在地面上。如果成功,那么我們將被扎根。通過(guò)返回一個(gè)布爾值(最初只是返回 false
)來(lái)指示是否發(fā)生了這種情況。
bool SnapToGround () { return false; }
這樣,我們可以方便地將其與使用布爾OR的 UpdateState
中的 OnGround
結(jié)合起來(lái)。之所以可行,是因?yàn)橹挥性?code> OnGround 為 false
時(shí),才會(huì)調(diào)用 SnapToGround
。
void UpdateState () { stepsSinceLastGrounded += 1; velocity = body.velocity; if (OnGround|| SnapToGround()) { … } … }
SnapToGround
僅在我們不接地時(shí)被調(diào)用,因此自上次接地以來(lái)的步驟數(shù)大于零。但是,我們僅應(yīng)在失去聯(lián)系后立即嘗試捕捉一次。因此,當(dāng)步數(shù)大于1時(shí),我們應(yīng)該中止。
bool SnapToGround () { if (stepsSinceLastGrounded > 1) { return false; } return false; }
射線檢測(cè)
我們只想在球體下面要堅(jiān)持的地面時(shí)捕捉。我們可以通過(guò)以 body.position
和向下向量作為參數(shù)調(diào)用 Physics.Raycast
從球體的位置垂直向下投射射線來(lái)進(jìn)行檢查。物理引擎將執(zhí)行此射線廣播,并返回是否擊中某些東西。如果沒(méi)有,那就沒(méi)有根據(jù),我們就會(huì)中止。
if (stepsSinceLastGrounded > 1) { return false; } if (!Physics.Raycast(body.position, Vector3.down)) { return false; } return false;
如果射線確實(shí)擊中了某物,那么我們必須檢查它是否算作地面??梢酝ㄟ^(guò)第三個(gè) RaycastHit
結(jié)構(gòu)輸出參數(shù)來(lái)檢索有關(guān)命中信息的信息。
if (!Physics.Raycast(body.position, Vector3.down,out RaycastHit hit)) { return false; }
該代碼如何工作?
RaycastHit 是一個(gè)結(jié)構(gòu),因此是一個(gè)值類型。我們可以通過(guò) RaycastHit hit 定義一個(gè)變量,然后將其作為第三個(gè)參數(shù)傳遞給 Physics.Raycast 。但這是一個(gè)輸出參數(shù),這意味著它像對(duì)象引用一樣通過(guò)引用傳遞。必須通過(guò)向其中添加 out 修飾符來(lái)明確指出這一點(diǎn)。該方法負(fù)責(zé)為其分配值。
除此之外,還可以在參數(shù)列表內(nèi)聲明用于輸出參數(shù)的變量,而不必在單獨(dú)的行上聲明。那就是我們?cè)谶@里所做的。
命中數(shù)據(jù)包括法線向量,我們可以使用該向量來(lái)檢查我們命中的表面是否算作地面。如果沒(méi)有,請(qǐng)中止。請(qǐng)注意,在這種情況下,我們處理的是真實(shí)的表面法線,而不是碰撞法線。
if (!Physics.Raycast(body.position, Vector3.down, out RaycastHit hit)) { return false; } if (hit.normal.y < minGroundDotProduct) { return false; }
重新與地面對(duì)齊
如果我們此時(shí)還沒(méi)有中止,那么我們只是失去了與地面的接觸,但仍然在地面之上,因此我們要抓住它。將地面接觸計(jì)數(shù)設(shè)置為1,使用找到的法線作為接觸法線,然后返回 true
。
if (hit.normal.y < minGroundDotProduct) { return false; } groundContactCount = 1; contactNormal = hit.normal; return true;
現(xiàn)在我們認(rèn)為自己已經(jīng)扎根,盡管我們?nèi)蕴幱诳罩?。下一步是調(diào)整速度,使其與地面對(duì)齊。就像對(duì)齊所需速度一樣,它的工作原理是必須保持當(dāng)前速度,并且我們將顯式計(jì)算該速度,而不是依靠 ProjectOnContactPlane
。
groundContactCount = 1; contactNormal = hit.normal; float speed = velocity.magnitude; float dot = Vector3.Dot(velocity, hit.normal); velocity = (velocity - hit.normal * dot).normalized * speed; return true;
在這一點(diǎn)上,我們?nèi)匀黄≡诘孛嫔希侵亓⒂兄趯⑽覀兝降孛?。?shí)際上,速度可能已經(jīng)有些下降,在這種情況下,重新調(diào)整速度會(huì)減慢向地面的收斂速度。因此,僅當(dāng)其點(diǎn)乘積與表面法線為正時(shí),才應(yīng)調(diào)整速度。
if (dot > 0f) { velocity = (velocity - hit.normal * dot).normalized * speed; }
這足以使我們的球體在越過(guò)頂部時(shí)保持在坡道上。它們會(huì)漂浮一點(diǎn),但是在實(shí)踐中幾乎看不到。即使球體會(huì)在一幀中變成白色,但在 FixedUpdate
中,我們將球體始終視為接地。只是在我們處于中間狀態(tài)時(shí)調(diào)用 Update
。
堅(jiān)持傾斜。
它還可以防止球在跳下臺(tái)階時(shí)啟動(dòng)球體。
堅(jiān)持一步。
請(qǐng)注意,我們僅考慮位于我們下方的單個(gè)點(diǎn)來(lái)確定我們是否位于地面之上。只要關(guān)卡的幾何圖形不太嘈雜或不太詳細(xì),此方法就可以正常工作。例如,如果射線正好投射到其中,那么微小的深裂紋可能會(huì)導(dǎo)致破裂失敗。
最大捕捉速度
無(wú)論如何,高速都是我們的球體被發(fā)射的原因,所以讓我們添加一個(gè)可配置的最大捕捉速度。默認(rèn)情況下,將其設(shè)置為最大速度,以便在可能的情況下始終進(jìn)行捕捉。
[SerializeField, Range(0f, 100f)] float maxSnapSpeed = 100f;
最大捕捉速度。
然后,當(dāng)當(dāng)前速度超過(guò)最大捕捉速度時(shí),也終止 SnapToGround
。我們可以在射線檢測(cè)之前通過(guò)更早地計(jì)算速度來(lái)做到這一點(diǎn)。
bool SnapToGround () { if (stepsSinceLastGrounded > 1) { return false; } float speed = velocity.magnitude; if (speed > maxSnapSpeed) { return false; } if (!Physics.Raycast(body.position, Vector3.down, out RaycastHit hit)) { return false; } if (hit.normal.y < minGroundDotProduct) { return false; } groundContactCount = 1; contactNormal = hit.normal; //float speed = velocity.magnitude; float dot = Vector3.Dot(velocity, hit.normal); if (dot > 0f) { velocity = (velocity - hit.normal * dot).normalized * speed; } return true; }
請(qǐng)注意,由于精度限制,將兩個(gè)最大速度設(shè)置為相同的值可能會(huì)產(chǎn)生不一致的結(jié)果。最好使最大捕捉速度高于或低于最大速度。
相同的最大速度會(huì)產(chǎn)生不一致的結(jié)果。
探頭距離
當(dāng)球體下方有地面時(shí),無(wú)論距離多遠(yuǎn),我們都在捕捉。最好只檢查附近的地面。我們通過(guò)限制探頭的范圍來(lái)做到這一點(diǎn)。沒(méi)有最佳的最大距離,但是如果過(guò)低的捕捉可能會(huì)在陡峭的角度或較高的速度下失敗,而過(guò)高的捕捉則會(huì)導(dǎo)致不合理的捕捉到遠(yuǎn)低于地面的捕捉。使其可配置,最小值為零,默認(rèn)值為1。由于我們的球體的半徑為0.5,這意味著我們要在球體底部以下最多檢查半個(gè)單位。
[SerializeField, Min(0f)] float probeDistance = 1f;
探頭距離。
將距離作為第四個(gè)參數(shù)添加到 Physics.Raycast
。
if (!Physics.Raycast( body.position, Vector3.down, out RaycastHit hit, probeDistance )) { return false; }
忽略代理(Agents)
在檢查地面是否貼合時(shí),我們僅考慮可以表示地面的幾何圖形是有意義的。默認(rèn)情況下,raycast會(huì)檢查除放置在忽略Raycast 層上的對(duì)象外的所有內(nèi)容。不應(yīng)數(shù)的內(nèi)容可以變化,但我們正在移動(dòng)的領(lǐng)域很可能不會(huì)。我們不會(huì)偶然碰到要投射的球體,因?yàn)槲覀兪菑钠湮恢孟蛲馔渡洌俏覀兛赡軙?huì)碰到另一個(gè)移動(dòng)的球體。為了避免這種情況,我們可以將其 Layer 設(shè)置為忽略Raycast ,但讓我們?yōu)樗谢顒?dòng)的,應(yīng)忽略的內(nèi)容創(chuàng)建一個(gè)新層為此目的。
通過(guò)游戲?qū)ο蟮?em> Layer 下拉菜單中的添加圖層... 選項(xiàng)進(jìn)入圖層設(shè)置。項(xiàng)目設(shè)置的標(biāo)簽和圖層(Tags and Layers)部分。然后定義一個(gè)新的自定義用戶層。對(duì)于不屬于關(guān)卡幾何體的通用活動(dòng)實(shí)體,我們將其命名為 Agent 。
標(biāo)簽和圖層,以及自定義 代理商圖層8。
將所有球體移動(dòng)到該層。更改預(yù)制層即可。
圖層設(shè)置為 代理。
接下來(lái),向 MovingSphere
添加一個(gè)可配置的 LayerMask
探針掩碼,該掩碼最初設(shè)置為-1,與所有圖層匹配。
[SerializeField] LayerMask probeMask = -1;
然后,我們可以配置球體,以便探測(cè)除忽略Raycast 和 Agent 之外的所有層。
探頭罩。
要應(yīng)用遮罩,請(qǐng)將其作為第五個(gè)參數(shù)添加到 Physics.Raycast
。
if (!Physics.Raycast( body.position, Vector3.down, out RaycastHit hit, probeDistance, probeMask )) { return false; }
跳躍和抓拍
抓拍現(xiàn)在可以工作并且可以配置,但是當(dāng)我們跳躍時(shí)它也會(huì)激活,從而抵消了向上的動(dòng)力。為了使跳躍再次起作用,我們必須避免在跳躍后立即彈跳。我們可以通過(guò)計(jì)算自上次跳躍以來(lái)的物理步數(shù)來(lái)跟蹤此情況,就像我們計(jì)算自上次停飛以來(lái)的步數(shù)一樣。在 UpdateState
的開(kāi)頭將其遞增,并在激活跳轉(zhuǎn)時(shí)將其設(shè)置回零。
int stepsSinceLastGrounded, stepsSinceLastJump; … void UpdateState () { stepsSinceLastGrounded += 1; stepsSinceLastJump += 1; … } … void Jump () { if (OnGround || jumpPhase < maxAirJumps) { stepsSinceLastJump = 0; jumpPhase += 1; … } }
現(xiàn)在,即使跳轉(zhuǎn)后過(guò)早,我們也可以中止 SnapToGround
。由于碰撞數(shù)據(jù)的延遲,我們?nèi)匀徽J(rèn)為啟動(dòng)跳躍后的步驟已接地。因此,如果我們?cè)谔S后走了兩個(gè)或更少的步驟,就必須中止。
if (stepsSinceLastGrounded > 1|| stepsSinceLastJump <= 2) { return false; }
樓梯
接下來(lái)讓我們考慮一種更困難的表面:樓梯。實(shí)際上,球體根本無(wú)法很好地爬上樓梯,但是無(wú)論如何,我們可能希望它們這樣做,也許是因?yàn)樗鼈兇砹藨?yīng)該能夠在樓梯上導(dǎo)航的東西。我制作了一個(gè)測(cè)試場(chǎng)景,其中包含五個(gè)45°階梯,步長(zhǎng)分別為0.1、0.2、0.3、0.4和0.5。
樓梯 test scene.
使用默認(rèn)設(shè)置時(shí),球體根本無(wú)法處理樓梯。在最大加速度下,大多數(shù)設(shè)法上升,但是結(jié)果不可靠且有彈性,根本沒(méi)有平穩(wěn)的運(yùn)動(dòng)。試圖以一定角度移動(dòng)而不是直上樓梯幾乎是不可能的。
跳上樓梯;加速度100。
簡(jiǎn)化碰撞體(Collider)
較大的樓梯臺(tái)階使移動(dòng)無(wú)法進(jìn)行。而且,雖然可以以較小的臺(tái)階彈起樓梯,但是碰撞變得任意,運(yùn)動(dòng)變得不穩(wěn)定而不是平穩(wěn)。
與其嘗試與物理引擎戰(zhàn)斗,不如我們更加務(wù)實(shí)并使它的工作更輕松。我們要在樓梯上進(jìn)行平穩(wěn),一致,可控制的運(yùn)動(dòng)。當(dāng)我們使用平坦的斜坡代替時(shí),我們可以得到。因此,讓我們用坡道代替樓梯的碰撞體。
坡道是樓梯的近似值。最好的折衷方案是設(shè)計(jì)碰撞體坡道,使其切穿臺(tái)階中間。然后,碰撞將發(fā)生在可見(jiàn)幾何體的上方和下方。
樓梯簡(jiǎn)化.
我創(chuàng)建了這樣的形狀以匹配五個(gè)階梯,首先是常規(guī)的 ProBuilder 對(duì)象。然后,通過(guò) ProBuilder 窗口中的設(shè)置碰撞體選項(xiàng)將它們轉(zhuǎn)換為碰撞體。
簡(jiǎn)化的樓梯,作為普通物體和 碰撞體。
禁用樓梯的網(wǎng)格碰撞體組件,但此時(shí)不要?jiǎng)h除它們。然后暫時(shí)將最大地面角度增加到46°,以使球體可以向上移動(dòng)45°樓梯。
將樓梯視為坡道,最大地面角度為46°。
盡管需要一些額外的關(guān)卡設(shè)計(jì)工作,但使用簡(jiǎn)化的碰撞體上樓梯是使它們?cè)谖锢砩峡蓪?dǎo)航的最佳方法。通常,使碰撞形狀盡可能簡(jiǎn)單是一個(gè)好主意,出于性能和運(yùn)動(dòng)穩(wěn)定性的原因,避免不必要的細(xì)節(jié)。因此,我們將堅(jiān)持使用這種方法。但這是一個(gè)近似值,因此在仔細(xì)檢查時(shí),您會(huì)發(fā)現(xiàn)球體切穿并懸停在樓梯臺(tái)階上方。但是,從遠(yuǎn)處和運(yùn)動(dòng)中通常并不太明顯。
近似。
我們可以避免緩慢下樓梯嗎?
將來(lái),我們將在關(guān)注重力的情況下進(jìn)行處理。
Detailed 和 Stairs 層(Layer)
我們使用簡(jiǎn)化的碰撞體進(jìn)行球體與樓梯之間的交互并不意味著我們不能將原始的樓梯碰撞體用于其他碰撞。例如,我們可能希望小碎片正確地降落在各個(gè)樓梯臺(tái)階上,而不是從坡道上滑下來(lái)。讓我們通過(guò)增加兩層來(lái)實(shí)現(xiàn)這一點(diǎn):一層用于詳細(xì)信息,一層用于樓梯對(duì)象。
詳細(xì)和樓梯對(duì)象的圖層。
探針遮罩應(yīng)包括樓梯層,但不包括 Detailed 層。
調(diào)整好探頭罩。
接下來(lái),轉(zhuǎn)到項(xiàng)目設(shè)置的物理部分,然后調(diào)整圖層碰撞矩陣。樓梯僅應(yīng)與代理商交互,而代理不應(yīng)與 Detailed 交互。
層碰撞矩陣。
現(xiàn)在,再次啟用樓梯網(wǎng)格碰撞器組件。然后添加一些小的剛體對(duì)象,使其落在它們之上,以同時(shí)查看兩種相互作用。如果您給這些物體提供足夠低的質(zhì)量(例如0.05),那么球體將能夠?qū)⑺鼈兺频揭贿叀?/p>
與樓梯相撞的兩種方式。
樓梯的最大角度
如果我們能夠爬樓梯,那么我們使用的樓梯最大角度與正常地面的最大角度是有道理的。因此,為它們添加一個(gè)單獨(dú)的最大角度,默認(rèn)情況下設(shè)置為50°。
[SerializeField, Range(0, 90)] float maxGroundAngle = 25f, maxStairsAngle = 50f; … float minGroundDotProduct, minStairsDotProduct; … void OnValidate () { minGroundDotProduct = Mathf.Cos(maxGroundAngle * Mathf.Deg2Rad); minStairsDotProduct = Mathf.Cos(maxStairsAngle * Mathf.Deg2Rad); }
最大地面和樓梯角度。
我們現(xiàn)在必須與哪種最小點(diǎn)產(chǎn)品進(jìn)行比較取決于我們所使用的表面類型。我們將為此添加一個(gè)可配置的樓梯遮罩選項(xiàng),類似于探針遮罩。
[SerializeField] LayerMask probeMask = -1, stairsMask = -1;
樓梯 mask.
為什么不使用 LayerMask.NameToLayer("Stairs")?
這是可能的,但是通過(guò)使用遮罩,我們不再依賴于硬編碼的圖層名稱,并且更加靈活,這也使實(shí)驗(yàn)更加容易。
創(chuàng)建一個(gè)新的 GetMinDot
方法,該方法返回給定圖層的適當(dāng)最小值,該整數(shù)是整數(shù)。假設(shè)我們可以直接比較樓梯的蒙版和圖層,如果它們不相等,則返回最小地面點(diǎn)積,否則返回最小樓梯點(diǎn)積。
float GetMinDot (int layer) { return stairsMask != layer ? minGroundDotProduct : minStairsDotProduct; }
但是,該掩碼是位掩碼,每層只有一位。具體來(lái)說(shuō),如果樓梯是第11層,則它與第11位匹配。我們可以通過(guò)使用 1 << layer
來(lái)設(shè)置單個(gè)位的值,該值將左移運(yùn)算符應(yīng)用于數(shù)字1的次數(shù)等于層索引(十)。結(jié)果將是二進(jìn)制數(shù)10000000000。
return stairsMask !=(1 << layer)?
如果蒙版僅選擇了一個(gè)圖層,這將起作用,但是讓我們?yōu)槿魏螆D層組合支持一個(gè)蒙版。我們通過(guò)采用掩碼和圖層位的布爾AND來(lái)實(shí)現(xiàn)。如果結(jié)果為零,則該層不屬于蒙版。
return(stairsMask & (1 << layer)) == 0?
在 EvaluateCollision
的開(kāi)頭檢索正確的最小點(diǎn)值,并使用它來(lái)檢查接觸是否算作地面。
void EvaluateCollision (Collision collision) { float minDot = GetMinDot(collision.gameObject.layer); for (int i = 0; i < collision.contactCount; i++) { Vector3 normal = collision.GetContact(i).normal; if (normal.y >=minDot) { groundContactCount += 1; contactNormal += normal; } } }
檢查我們是否在地面上時(shí),還可以在 SnapToGround
中使用 GetMinDot
。
if (hit.normal.y陡峭的接觸
除了接地觸點(diǎn),還有其他觸點(diǎn)。移動(dòng)需要接地,但有時(shí)我們僅與墻壁接觸。否則我們可能陷入困境。如果我們有空氣加速功能,在這種情況下我們?nèi)匀豢梢钥刂?,但是通過(guò)一些額外的工作,我們可以做更多的事情。
檢測(cè)陡峭的接觸
陡峭的觸點(diǎn)太陡而不能算作地面,但不是天花板或懸垂。因此,一切都達(dá)到了完美的垂直墻。就像在常規(guī)地面觸點(diǎn)上一樣,讓我們?cè)谧侄魏蛯傩灾懈櫞祟愑|點(diǎn)的正常和計(jì)數(shù)。
Vector3 contactNormal, steepNormal; int groundContactCount, steepContactCount; bool OnGround => groundContactCount > 0; bool OnSteep => steepContactCount > 0;還要在
ClearState
中重置新數(shù)據(jù)。void ClearState () { groundContactCount =steepContactCount =0; contactNormal =steepNormal =Vector3.zero; }在
EvaluateCollision
中,如果我們沒(méi)有地面接觸,請(qǐng)檢查它是否為陡峭接觸。完美垂直的墻的點(diǎn)積應(yīng)為零,但讓我們稍微寬一點(diǎn)些,接受高于-0.01的所有值。if (normal.y >= minDot) { groundContactCount += 1; contactNormal += normal; } else if (normal.y > -0.01f) { steepContactCount += 1; steepNormal += normal; }裂縫
縫隙是有問(wèn)題的,因?yàn)橐坏┛ㄔ诳p隙中而沒(méi)有跳氣,除非空氣加速度很大,否則就不可能逃脫。我創(chuàng)建了一個(gè)帶有小裂縫的測(cè)試場(chǎng)景來(lái)演示這一點(diǎn)。
逃脫裂縫。
跳墻
讓我們還回顧一下跳墻。之前,我們僅將跳躍限制為僅在地面或空中跳躍時(shí)進(jìn)行。但是,如果我們將跳躍方向設(shè)為陡峭法線而不是接觸法線,那么我們也可以支持從墻跳。
開(kāi)始制作跳轉(zhuǎn)方向變量,并刪除
Jump
中的當(dāng)前有效性檢查。void Jump () { Vector3 jumpDirection; //if (OnGround || jumpPhase < maxAirJumps) { stepsSinceLastJump = 0; jumpPhase += 1; float jumpSpeed = Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight); float alignedSpeed = Vector3.Dot(velocity,jumpDirection); if (alignedSpeed > 0f) { jumpSpeed = Mathf.Max(jumpSpeed - alignedSpeed, 0f); } velocity +=jumpDirection* jumpSpeed; //} }相反,請(qǐng)檢查我們是否在地面上。如果是這樣,請(qǐng)使用接觸法線作為跳轉(zhuǎn)方向。如果不是,那么下一個(gè)檢查是我們是否處在陡峭的狀態(tài)。如果是這樣,請(qǐng)改用陡峭的法線。之后,檢查空氣跳動(dòng),為此我們?cè)俅问褂媒佑|法線,該法線已設(shè)置為向上向量。如果所有這些都不適用,則不可能進(jìn)行跳轉(zhuǎn),并且我們中止。
Vector3 jumpDirection; if (OnGround) { jumpDirection = contactNormal; } else if (OnSteep) { jumpDirection = steepNormal; } else if (jumpPhase < maxAirJumps) { jumpDirection = contactNormal; } else { return; }墻跳。
空中跳躍
此時(shí),我們應(yīng)該重新考慮空中跳躍。檢查跳躍階段是否小于最大空中跳躍的唯一作用是,在跳躍之后,該階段會(huì)立即重置為零,因?yàn)樵谙乱徊街?,我們?nèi)詫⑵湟暈榻拥?。因此,我們僅應(yīng)在啟動(dòng)跳轉(zhuǎn)后多一步才能在
UpdateState
中重置跳轉(zhuǎn)階段,以免錯(cuò)誤著陸。stepsSinceLastGrounded = 0; if (stepsSinceLastJump > 1) { jumpPhase = 0; }為了保持空中跳躍的正常進(jìn)行,我們現(xiàn)在必須檢查跳躍階段是否小于或等于
Jump
中的最大值。else if (jumpPhase<=maxAirJumps) { jumpDirection = contactNormal; }但是,這樣可以使空氣從表面掉下來(lái)后再跳一次而不跳動(dòng)。為了防止這種情況,我們?cè)谔鴼鈺r(shí)會(huì)跳過(guò)第一跳階段。
else if (jumpPhase <= maxAirJumps) { if (jumpPhase == 0) { jumpPhase = 1; } jumpDirection = contactNormal; }但這僅在完全允許空氣跳躍的情況下才有效,因此首先檢查一下。
else if (maxAirJumps > 0 &&jumpPhase <= maxAirJumps) { if (jumpPhase == 0) { jumpPhase = 1; } jumpDirection = contactNormal; }最后,讓壁跳重設(shè)跳躍階段,以便有可能將壁跳變成新的空中跳躍序列。
else if (OnSteep) { jumpDirection = steepNormal; jumpPhase = 0; }空氣壁空氣跳躍。
向上跳躍偏見(jiàn)
從垂直墻上跳下來(lái)不會(huì)增加垂直速度。因此,盡管有可能在附近的相對(duì)壁之間反彈,但重力始終會(huì)將球體拉下。我用兩個(gè)方塊制作了一個(gè)測(cè)試場(chǎng)景來(lái)演示這一點(diǎn)。
卡在底部;跳高3。
但是,有些游戲?qū)⑻鴫ψ鳛檫_(dá)到極高的一種手段。我們可以通過(guò)在跳轉(zhuǎn)方向上增加一個(gè)向上的偏差來(lái)支持這一點(diǎn)。最簡(jiǎn)單的方法是將上矢量添加到跳轉(zhuǎn)方向并將結(jié)果標(biāo)準(zhǔn)化。最終方向是兩個(gè)方向的平均值,因此從平坦地面的跳躍不會(huì)受到影響,而從完全垂直的墻壁上跳躍的影響最大,成為45°跳躍。
jumpDirection = (jumpDirection + Vector3.up).normalized; float alignedSpeed = Vector3.Dot(velocity, jumpDirection);墻跳了起來(lái);跳高3。
這會(huì)影響所有不在完美平坦的地面或空中的跳躍軌跡,這在上坡時(shí)跳躍時(shí)最明顯。
跳躍時(shí)沒(méi)有偏見(jiàn)。
最后,從
Update
刪除調(diào)試球顏色。//GetComponent().material.SetColor( // "_Color", OnGround ? Color.black : Color.white //); 以上就是關(guān)于“Unity怎么實(shí)現(xiàn)表面接觸保持聯(lián)系”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
當(dāng)前文章:Unity怎么實(shí)現(xiàn)表面接觸保持聯(lián)系
URL分享:http://weahome.cn/article/gsojdj.html