HexMap學(xué)習(xí)筆記(十一)——更多種地形特征物

作者:沈琰
這期在上期的基礎(chǔ)上添加了更多視覺(jué)上的細(xì)節(jié)物,代碼方面的難度不高。這里也可以替換上自己的模型使地圖更好看一些。
本期原文地址:https://catlikecoding.com/unity/tutorials/hex-map/part-11/
這是HexMap系列的第11篇教程,內(nèi)容是有關(guān)添加塔樓、橋梁和一些特殊的地形特征物到地圖中。

1 塔樓
在上篇教程中已經(jīng)在地圖上添加了城墻的編輯功能,它們只是由筆直的城墻段組成,沒(méi)有其他容易辨識(shí)的特征了?,F(xiàn)在要讓城墻顯得更為有趣一些,在上面添加塔樓。
城墻墻體的部分必須由代碼生成以適應(yīng)地形,但是塔樓并不需要這樣,我們可以用一個(gè)簡(jiǎn)單的預(yù)制體來(lái)表示塔樓。創(chuàng)建一個(gè)由兩個(gè)立方體組成的塔樓形狀,使用城市的紅色材質(zhì)球。塔樓的基座的長(zhǎng)寬為2乘2,高為4,所以它比城墻的墻體既高且厚。在這個(gè)底座上放一個(gè)單位大小的立方體表示塔頂。和其他預(yù)制體一樣,這些立方體不需要碰撞盒。
由于塔樓由多個(gè)物體組成,所以放到一個(gè)根節(jié)點(diǎn)下面,然后基于塔樓的底部移動(dòng)根節(jié)點(diǎn)的坐標(biāo),這樣一來(lái)根節(jié)點(diǎn)的局部坐標(biāo)就位于塔樓底部,不用再擔(dān)心塔樓高度的問(wèn)題了。

將對(duì)該預(yù)制體的引用添加到HexFeatureManager中并與物體關(guān)聯(lián)起來(lái)。

1.1 構(gòu)建塔樓
我們先在每一段城墻中都放一座塔樓,在AddWallSegment方法的末尾實(shí)例化一個(gè)塔樓,它位于城墻段左右頂點(diǎn)的平均值上。


可以看到沿著城墻生成了很多塔樓,但它們的方向沒(méi)有變化。我們應(yīng)該調(diào)整塔樓的旋轉(zhuǎn)使之與城墻對(duì)齊。由于我們知道城墻的左右端點(diǎn),所以能知道城墻的右邊是哪個(gè)方向。用這格方向就能確認(rèn)城墻和塔樓的朝向。
我們并不需要去計(jì)算塔樓該往哪旋轉(zhuǎn)多少度,只需要為transform.right賦上正確的值,Unity的代碼會(huì)負(fù)責(zé)物體的旋轉(zhuǎn),使其自身的right方向與給定的向量對(duì)齊。


設(shè)置Transform.right是如何工作的?
它使用Quaternion.FromToRotation方法推導(dǎo)出旋轉(zhuǎn),下面是這個(gè)屬性的代碼。
public?Vector3?right {
get {
return rotation *?Vector3.right;
}
set {
rotation =?Quaternion.FromToRotation(Vector3.right, value);
}
}
1.2 減少塔樓數(shù)量
每一段城墻上都有一座塔樓有點(diǎn)太多了,因此在AddWallSegment方法中添加一個(gè)布爾參數(shù),使放置塔樓成為可選的。給其一個(gè)默認(rèn)的false值,這樣所有的塔樓都會(huì)消失。

讓我們將塔樓限制在只在角落上的單元格連接處放置,塔樓就會(huì)呈現(xiàn)有規(guī)則的距離間隔。


這看起來(lái)就很不錯(cuò),但是你可能想要不太規(guī)則,塔樓更少的布局樣式。與其他的地形特征物一樣,可以使用哈希表的概率來(lái)決定是否放置塔樓,所以使用角落上城墻段的中心坐標(biāo)對(duì)網(wǎng)格進(jìn)行采樣,然后將其中一個(gè)哈希值與塔樓的出現(xiàn)閾值進(jìn)行比較。

閾值是在HexMetrics里定義的。設(shè)置為0.5會(huì)讓塔樓的出現(xiàn)次數(shù)減少一半,盡管還是有可能出現(xiàn)塔樓更多或者根本沒(méi)有塔樓的情況。


1.3 避免塔樓出現(xiàn)在斜坡上
現(xiàn)在放置塔樓時(shí)并沒(méi)有考慮到地形因素,然而在斜坡上放置塔樓并沒(méi)有多大意義,那里的城墻是斜著的,可能會(huì)從塔樓頂端穿出去。

為避免出現(xiàn)斜坡情況,檢測(cè)左右單元格的海拔高度是否一樣。在一樣的情況下我們才有概率去放置一座塔樓。


1.4 將城墻與塔樓平置于地面上
雖然塔樓不會(huì)出現(xiàn)在傾斜的墻壁上,但城墻兩側(cè)的單元格還是可以有不同的海拔高度,甚至相同高度的單元格由于頂點(diǎn)擾動(dòng)的原因?qū)嶋H垂直高度也會(huì)不一樣,城墻是可以沿著傾斜部分放置,但塔樓會(huì)在這種情況下顯得好像是漂浮起來(lái)。

實(shí)時(shí)上城墻也會(huì)在傾斜情況下顯得漂浮,只不過(guò)不像塔樓那樣引人注目。

解決這個(gè)問(wèn)題最簡(jiǎn)單的方法是將城墻的基準(zhǔn)點(diǎn)挪到地面下,向HexMetrics添加城墻的Y軸偏移,約一個(gè)標(biāo)準(zhǔn)單位就足夠了,然后將城墻的高度增加相同的量。

還需要修改塔樓預(yù)制體,讓其底部基點(diǎn)下沉一個(gè)單位,并底座的高度增加一個(gè)單位高度。

2 橋梁
目前我們的道路無(wú)法跨越河流,可以去添加橋梁了。先用一個(gè)簡(jiǎn)單的立方體表示橋梁的預(yù)制體。河流的寬度各不相同,但道路中心到每邊的長(zhǎng)度大約是7個(gè)標(biāo)準(zhǔn)單位,所以將其縮放設(shè)置為(3,1,7)。還是給它紅色的城市材質(zhì)并去掉碰撞盒。與城墻塔樓一樣,把立方體放入一個(gè)根節(jié)點(diǎn)中,這樣它的實(shí)際幾何形狀就不重要了。
在HexFeatureManager里添加橋梁預(yù)制體的引用,并建立關(guān)聯(lián)。


2.1 放置橋梁
在HexFeatureManager里添加一個(gè)AddBridge方法,橋梁應(yīng)該位于河岸兩邊道路的中心之間。

2.2 在筆直的河流上架設(shè)橋梁
需要架設(shè)橋梁的河流只有筆直的和平滑彎曲的兩種,在鋸齒急彎的河流中只會(huì)有一邊有道路。
先來(lái)考慮筆直的河流,在HexGridChunk.TriangulateRoadAdjacentToRiver方法中的第一個(gè)else if中負(fù)責(zé)修改道路讓其貼近單元格,所以就在這里架設(shè)橋梁。
我們現(xiàn)在處于河流的其中一邊,道路的中心點(diǎn)從河流中推開(kāi)了并且單元格的中心點(diǎn)也移動(dòng)了。要找到道路中心反方向的一邊,我們需要在相反的方向上移動(dòng)相同的長(zhǎng)度,而這一切要在道路中心改變之前完成。


橋梁出現(xiàn)了,但是現(xiàn)在我們?cè)跊](méi)有河流穿過(guò)時(shí)在每個(gè)方向上都生成了一個(gè)橋梁的實(shí)例。我們得確保每個(gè)單元格只生成一座橋,這里可以選擇一個(gè)相對(duì)于河流的方向生成橋,具體是哪個(gè)方向不重要。

2.3 在彎曲的河流上架設(shè)橋梁
在彎曲河流上架橋的方法類似,但結(jié)構(gòu)略有不同。當(dāng)在河流外側(cè)時(shí)需要添加一座橋,這是最后一段else代碼塊中的情況,這里的中間方向是用來(lái)偏移道路中心點(diǎn)的。我們需要使用這個(gè)偏移量的不同比例兩此,所以把其存儲(chǔ)在一個(gè)變量中。

曲線外側(cè)的偏移比例是0.25,但是內(nèi)側(cè)則為HexMetrics.innerToOuter*0.7f,使用這些值去放置橋梁。

還是要防止出現(xiàn)重復(fù)的橋梁,這次只用在中間方向添加橋梁就可以了。

2.4 橋梁的縮放
由于地形的頂點(diǎn)受噪聲圖的擾動(dòng),所以道路中心點(diǎn)到河流兩邊的的距離是不同的。我們的橋梁有時(shí)顯得太短,有時(shí)又太長(zhǎng)。

雖然我們已經(jīng)設(shè)計(jì)好了橋梁的長(zhǎng)度固定為7,但是還是可以對(duì)橋梁進(jìn)行縮放好讓其吻合道路中心點(diǎn)之間的實(shí)際距離,這意味著橋梁的模型會(huì)產(chǎn)生形變。由于距離不會(huì)偏差太大,這種形變應(yīng)該比距離不適合的統(tǒng)一長(zhǎng)度的橋梁更容易接受一些。
為了進(jìn)行正確的縮放,需要獲取橋梁預(yù)制體的長(zhǎng)度,并將其存儲(chǔ)在HexMetrics中。

現(xiàn)在我們可以設(shè)置橋梁實(shí)例的Z軸上的縮放為道路中心點(diǎn)的距離除以原始長(zhǎng)度。由于預(yù)制體的根節(jié)點(diǎn)是單位縮放,所以橋梁的模型可以顯示正確的伸縮。


.5 橋梁外形設(shè)計(jì)
可以使用更有意思的橋梁模型而不是單個(gè)立方體。例如可以使用三個(gè)立方體進(jìn)行縮放旋轉(zhuǎn)后組成一個(gè)粗糙的拱橋形狀。當(dāng)然也能使用更復(fù)雜奇特的3D模型,甚至包括道路引橋部分。但是要記得整個(gè)模型會(huì)被壓縮和拉伸。

3 特殊的特征物體
目前為止,單元格內(nèi)可以包含如城市,農(nóng)田和植物之類的特征物。但即使它們每一種都有三個(gè)大小類別,但與一整個(gè)單元格比起來(lái)還是較小。如果我們要添加一個(gè)較大的結(jié)構(gòu),比如說(shuō)城堡呢?
所以為地形添加一個(gè)特殊的特征物類型,它們的大小足以占據(jù)整個(gè)單元格。每一種都是很獨(dú)特的形狀,需要一個(gè)自己的預(yù)制體。例如一個(gè)簡(jiǎn)單的城堡形狀可以創(chuàng)建一個(gè)大的立方體再在四邊加上塔樓。中心立方體的縮放為(6,4,6),邊上的四個(gè)塔樓為(2,6,2)。雖然城堡顯得很大,但還是可以包含在形變的單元格之中。

另一個(gè)特殊的特征物可以是一個(gè)由三個(gè)立方體堆疊成的金字塔形的建筑,其底部立方體的縮放為(8,2.5,8)。

特殊特征物可以是任何東西,不必局限于建筑結(jié)構(gòu)。比如一組表示樹(shù)木的立方體,代表這個(gè)單元格內(nèi)被巨大植物覆蓋。

在HexFeatureManager里添加一個(gè)數(shù)組存放這些預(yù)制體。

分別把三個(gè)特殊特征物添加到數(shù)組中。


在HexMapEditor里添加設(shè)置特殊特征物的方法,就跟設(shè)置城市,農(nóng)場(chǎng)和植被的等級(jí)一樣。

在UI中添加一個(gè)滑動(dòng)條組件來(lái)控制它的值,由于現(xiàn)在只有3個(gè)特殊特征物,就將取值范圍設(shè)置為0-3,0代表沒(méi)有,1是城堡,2是金字塔,3代表巨型植物。

3.2 在地形上添加特殊特征物
現(xiàn)在可以為單元格設(shè)置特殊特征物了,在HexFeatureManager里添加一個(gè)新方法。它用于實(shí)例化所需的特殊特生物,并放置于指定位置上。因?yàn)楝F(xiàn)在0表示沒(méi)有特征物,故而在訪問(wèn)預(yù)制體數(shù)組之前需要將下標(biāo)減去1。

使用哈希網(wǎng)格為特征物指定隨機(jī)朝向。

當(dāng)在HexGridChunk里對(duì)單元格進(jìn)行三角剖分時(shí)檢測(cè)單元格是否有特殊特征物,如果有就調(diào)用新方法,就像AddFeature一樣。


3.3 避免出現(xiàn)在河流上
因?yàn)樘厥馓卣魑镂挥趩卧竦闹行?所以沒(méi)法與河流結(jié)合在一起,最終會(huì)漂浮在河流上。

為避免特殊特征物出現(xiàn)在河流之上,修改HexCell.SpecialIndex屬性,讓其只能在單元格內(nèi)沒(méi)有河流時(shí)才能更改下標(biāo)。

此外在添加河流時(shí)也需要去掉單元格上的特殊特征物,這可以通過(guò)在HexCell.SetOutgoingRiver方法中設(shè)置特征物下標(biāo)為零來(lái)實(shí)現(xiàn)。

3.4 避免出現(xiàn)在道路上
與河流一樣,道路也無(wú)法與特殊特征物結(jié)合,盡管看起來(lái)沒(méi)在河流上那么糟糕??赡苣阌X(jué)得讓道路保持原樣比較好,也許有一些其他的特征物能與道路結(jié)合,但現(xiàn)在我們還是簡(jiǎn)單的讓其不能共存。

在這種情況下我們讓特殊特征物頂替道路。所以在修改特殊特征物下標(biāo)時(shí),也要從單元格中刪除所有道路。

如果要去掉特殊特征物的時(shí)候會(huì)怎么樣?
如果把下標(biāo)設(shè)置為0,這意味著單元格內(nèi)已經(jīng)有一個(gè)特殊特征物了。因此這個(gè)單元格內(nèi)也不會(huì)有道路存在,所以我們不需要另一個(gè)不同的方法。
這也意味著在嘗試添加道路時(shí)需要額外的檢測(cè),只有當(dāng)兩個(gè)單元格內(nèi)都沒(méi)有特殊特征物時(shí)才能夠添加道路。

3.5 避免出現(xiàn)在其他特征物上
特殊特征物也不會(huì)與其他特征物混合,讓它們重疊在一起會(huì)顯得很混亂。同樣,這里也是根據(jù)不同特征物可以有變化的地方,但目前還是使用統(tǒng)一的方法。

在這種情況下我們就不添加次級(jí)的特征物了,就像檢測(cè)到處于水下一樣。這次把檢測(cè)過(guò)程放在HexFeatureManager.AddFeature里。

3.6 避免出現(xiàn)在水體上
最后還有在水體上的問(wèn)題,特殊特征物是否能在水下存在?當(dāng)在水下單元格里處理其他特征物時(shí),也對(duì)特殊特征物做同樣的處理。

在HexGridChunk.Triangulate里對(duì)特殊特征物同樣加上是否在水下的檢測(cè)。

既然兩個(gè)if語(yǔ)句內(nèi)都要檢測(cè)是否在水下,干脆就提取出來(lái)。

本期工程地址:https://github.com/tank1018702/Hex-Map-Learning/tree/SavingAndLoading
有意參與線下游戲開(kāi)發(fā)學(xué)習(xí)的童鞋,歡迎訪問(wèn)http://levelpp.com/。