最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

HexMap學習筆記(六)——河流

2019-03-22 18:29 作者:皮皮關做游戲  | 我要投稿

作者:沈琰


前言

這是目前為止長度最長的一篇,難度也是直線上升。不僅此篇所用的三角剖分方法更為復雜,并且從這篇教程開始,會逐漸使用編輯著色器代碼的方式添加一些簡單的視覺效果。

盡管經過Unity的簡化,但編輯著色器代碼依然是Unity新手的一個難點。不僅是那與C#迥然相異的語法,如果要實現一個看的過去的效果,還需要相當扎實的數學功底。推薦基礎較為薄弱的同學先跟著做一遍,不用太過糾結原理。當然底子強的同學要深入理解也可以另行查閱作者的shader系列教程。

本篇原文地址:http://link.zhihu.com/?target=https%3A//catlikecoding.com/unity/tutorials/hex-map/part-6/




本篇難度:★★★☆☆

這個教程是HexMap系列的第六部分,上一篇的內容是實現一個較大的地圖,這部分現在已經完成,可以開始考慮更大范圍的地形特性了,即此篇教程中的河流。

從山上流下的河流

1 單元格與河流

在六邊形地圖中添加河流有三種方法。

第一種方法是讓其從單元格中穿過,從一個單元格流向另一個單元格,這是《無盡傳說》中的做法。

第二種方法是讓其在單元格之間流過,沿著單元格個邊緣到另一個單元格的邊緣,《文明5》中是這么做的。

第三種方法是不使用額外的河流結構特性,而是直接用特殊的單元格表示水體,《奇跡時代3》中是這么做的。

而在我們的工程中,由于單元格的邊緣連接已經用階梯化或陡峭的方式特殊處理過,沒有留給河流的空間,所以就采用第一種方法,讓河流從一個單元格流向另一個單元格。這意味著每個單元格要么就是沒有河流經過,要么河流穿過這個單元格,要么這個單元格是河流的起點或者終點。而在有河流穿過的單元格中,要么河流是筆直穿過,要么是一步銳角轉彎,要么是兩步鈍角轉彎。

五種可能的河流情況

2 編輯河流

要實現編輯河流的功能,我們需要添加河流的選項卡(toggle)組件到UI上。事實上我們需要添加三種編輯模式:忽略,添加和移除,就簡單的使用枚舉來記錄這個值。由于這個功能只能在編輯模式中使用,所以可以在HexMapEditor這個類中去定義這個枚舉和編輯模式的字段。

添加三個toggle組件到UI上并放到一個新的toggle group中,就像顏色編輯一樣。這里修改了標簽名的位置在其選項框下面。這樣把三個選項框全放到一行時占用空間會足夠薄。

河流編輯UI
為什么不使用下拉菜單?
你要喜歡你也可以用。不幸的是,Unity的下拉菜單在運行模式下不能處理重編譯,選項列表會在重編譯時丟失并無法使用。

如何證實確實是拖拽事件?通過檢測當前單元格是否是前一個單元格的相鄰單元格,循環(huán)遍歷前一個單元格所有的相鄰單元格來進行檢測,如果找到了與當前單元格相吻合的結果就能同時確認拖拽的方向。

這不會產生拖拽抖動么?
當你移動鼠標穿過單元格邊界時,可能會在單元格之間快速來回擺動,這確實會導致拖拽抖動,但情況沒那么糟。
可以通過記錄上一次拖拽事件來減緩抖動,然后防止下一次直接向相反方向拖拽。

現在可以開始編輯單元格的河流了,盡管看不見,但可以通過檢視面板(inspector)的debug模式下的字段來驗證是否工作正常。

檢視面板Debug模式下單元格的河流
什么是debug檢視面板?
你可以在檢視面板的標簽菜單里切換為debug模式,在這個模式下檢視面板會顯示對象的原始數據。


3 單元格之間的河道

河流的三角化可以分為兩個部分來考慮,即河道和水流。我們先創(chuàng)建河道,把水流放到后面。

河流的最簡單部分是流經單元格之間的連接處的位置,這里目前用三個四邊形組成的長條形狀來三角化這個部分,可以通過降低中間四邊形的高度和添加兩道墻來創(chuàng)建河道。

為河流添加邊緣

但如果要這么做就需要添加兩個額外的四邊形來生成垂直的墻,另一個方法是使用四個四邊形來組成連接單元格的部分,這樣就能通過拉低中間的頂點形成河道的傾斜墻壁。

一直使用一樣四邊形數量會比較方便,所以我們選擇后一個方法。

3.1 添加邊界頂點

要把邊界連接部分的三個四邊形改為四個,就需要額外的邊界頂點,因此重構EdgeVertices這個結構,首先重命名v4為v5,v3為v4。要確保所有代碼始終能引用正確的頂點,要使用IDE的重命名或重構方法,這樣改動就能應用到所有地方,不然你只能手動去檢查代碼并進行改動。

四個邊界點和五個邊緣頂點的區(qū)別


3.2 河床高度

我們通過拉低邊界連接部分的中間頂點創(chuàng)建出了河道,這定義了河床豎直方向的坐標。盡管每個單元格的精確豎直坐標會受不規(guī)則化的影響,但還是應該在相同高度的單元格之間保持河床的恒定,這確保河流看起來不會是逆流而上。同樣河床需要足夠低,即使單元格的豎直方向的頂點擾動達到最大值也應該與單元格的底面保持一定的距離,為水流留下足夠的空間。

讓我們在HexMetrics里定義這個偏移量并把它作為高度等級的變量傳遞出來,一級高度等級的步長應該就足夠了。

修改邊界連接處的中間頂點后的樣子

可以看到河流的痕跡初現并在地上留下了空隙,要填充空隙則需要在三角化連接部分時修正六個邊界上的垂直坐標。

邊界連接處的河道完成

4 穿過單元格的河道

現在在兩個單元格之間創(chuàng)建出了正確的河道,但是在河流穿過單元格時總是會在中心位置結束。要修正這個問題需要費些功夫。讓我們先從河流筆直穿過單元格,從一邊到其相反方向的另一邊這種情況開始考慮。

如果沒有河流,單元格每一個方向都是由扇形三角組成,但當河流穿過時就需要在中間插入一條河道。實際上就是需要把單元格的中心點延伸成一條線,從而把中間的兩個三角形變成了四邊形,這樣三角扇部分就變?yōu)榱颂菪巍?/p>

強制把河道變成三角形

穿過單元格之間的河道比穿過連接處的河道要長得多,當頂點被擾動時看起來會很明顯。所以我們通過在中間和邊界之間的一半的位置插入一組新的邊界頂點來把梯形分為兩段。

河道的三角剖分結構

由于對帶有河流和沒有河流的單元格進行三角剖分會大不相同,所以為此創(chuàng)建一個專用方法。如果單元格內有河流就使用這個方法,不然就用之前的。

4.1河流筆直橫穿情況下的三角剖分

要構建筆直穿過單元格的河道,需要把單元格的中心點延伸成一條線并與河道的寬度相同。

可以通過單元格的中心點到第一個角頂點的前一個方向的角頂點移動四分之一的位置到到左邊的頂點。

壓縮的河道

不幸的是河道看起來好像被壓縮了,中間邊界的的頂點靠的太近,為什么會這樣?

考慮六邊形的外邊長是1這個情況,那么中心點的延伸線的長度就是二分之一。因為中間邊界線兩端的頂點位于之間一半的位置,那么中間邊界線的長度就是四分之三。

河道的寬度是不變的二分之一,由于中間邊界的長度是四分之三,剩余的長度就是四分之一,每邊的寬度就是八分之一。

相對長度

由于現在的中間邊界線的長度是四分之三,那它長度的八分之一實際值就是六分之一,這意味著中間邊界線的第二個和第四個頂點應該使用六分之一進行插值而不是四分之一。

我們可以在EdgeVerices里添加一個構造函數實現這個特殊版本的插值,而不是強行修改v2和v4的值,使用一個參數來控制。

河道恢復筆直后就可以開始第二段梯形的三角化工作了,這里無法直接使用邊界條的生成函數,只能手動添加。第一步先創(chuàng)建邊上的三角形。

實際上我們沒有只用一個參數的AddQuadColor方法,在這之前我們都用不到,所以就直接創(chuàng)建一個。

4.3 一折彎道

下一步,來考慮鋸齒形急彎拐入相鄰單元格的河道的三角剖分情況,這部分也歸TriangulateWithRiver方法負責,所以首先要搞清楚正在為哪種類型的河流三角化。

不再有擠壓感的鋸齒彎道


4.4 兩折彎道

剩下的就是既不是急彎又不是筆直河道的情況,即分兩步旋轉產生相對平緩的曲線河流。

平滑的曲線

5 單元格鄰近河流部分的三角剖分

現在河道完成了,但沒有三角化包含河流的單元格的其他部分,現在就去填充這部分空間。

河道邊上的空洞

在三角化時,當單元格內有河流但又不留經當前方向時,調用一個新方法。

彎曲和筆直的河流上有重疊部分


5.1 與河道吻合

當然,我們得確保我們使用的單元格中心點與河流部分的中心線吻合,這在鋸齒急彎部分是對的,只需要在緩彎和筆直河流上做出些額外修改。所以這里得知道河流的類型以及相對方向。

先來看看當前方向在河流曲線內彎的情況,即前一個方向和下一個方向上都有河流穿過,在這種情況下中心點就得移動到邊緣上去。

如果在下一個方向的邊界有河流穿過而不是前一個方向,就檢查一下是不是筆直的河流。如果是就需要把中心點向固定內六邊形的第一個角上移動。

修正了半邊的筆直河流

這能修正一半的問題,最后一種情況是當前方向的前一個方向上有河流并且是筆直河流,這就需要把中心點移向固定內六邊形的下一個角。

不再有重疊部分了

6 HexMesh廣義化

河道的的三角化已經完成,現在可以填充水了。因為水與陸地有很大不用,所以我們會使用不同的mesh,不同的頂點數據與材質。如果能用HexMesh同時處理陸地與水的mesh信息將會比較方便,所以我們把HexMesh這個類廣義化,用其專門處理mesh數據而不用關系它到底是用來干嘛的,HexGridChunk會去負責三角化它自己的單元格。

6.1移動頂點擾動方法

由于Petrurb方法比較通用,可能后面會用在其他地方,所以把它移動到HexMetrics中,重命名為HexMetrics.Perturb。(注:VS的重命名方法不能加上“.”,可以用文本替換功能,或者你也能人工一個個的修改)這是個無效的方法名,但是可以重命名所有的代碼讓其正確訪問。如果編輯器具有特殊的功能修改方法名,你可以用這個功能代替。

當這個方法處于HexMetrics的內部,就設置其為公共和靜態(tài)類型,然后修改名字。

下一步,重構所有HexMesh里調用Add..開頭方法的位置為terrain.Add..,然后把所有Triangulate..開頭的的方法移動到HexGridChunk中。這一步完成后就可以修改Add..類方法并設置為公共類型.其結果就是所有復雜的三角化方法現在都在HexGridChunk里了,并且簡單的添加數據到mesh中的方法仍然保持在HexMesh里。

這一步還沒做完,HexGridChunk.LateUpdate里現在調用它自己的Triangulate方法,再也不用傳遞單元格作為參數了,并且它應該委托清除和應用網格數據到HexMesh。

SetVertices,SetColors和SetTriangles是什么方法?
這些方法是unity最近添加到Mesh這個類中的,它能讓你直接傳遞Mesh數據到列表中.這意味著我們在更新數據時不需要再創(chuàng)建臨時存放數據的數組.
SetTriangles方法有第二個整數參數,即子網格的下標.我們不用子網格,所以它一直是零.

最后,手動關聯地圖塊預制體里子對象的Mesh,這里不再自動賦值,因為馬上就要添加第二個網格子對象,同樣重命名為Terrain指出其用途。

Terrain賦值
無法重命名預制體的子對象?
工程預覽中不會更新預制體名字的改動.你可以通過創(chuàng)建一個預制體的實例來更新它.修改實例,然后使用Apply按鈕把這些改動保存到預制體上.這是當前最好的修改預制體在層級窗口內信息的方法。

6.3 列表池

盡管我們已經移動了很多代碼的位置,但我們的地圖還是與之前的工作方式一樣。給每個地圖塊添加另一個mesh會改變它的工作方式,但是如果我們使用當前的HexMesh來做就會出錯。

問題在于,我們之前一直假設在一個時間點上只會對一個mesh進行修改,這就允許我們使用靜態(tài)列表存儲臨時mesh數據。但是當我們添加水面mesh的數據時,有可能就會在同一個時間點同時對兩個mesh做出改動,所以現在不能繼續(xù)用靜態(tài)列表了。

然而我們也不需要改回到為每一個HexMesh設置一個列表笨辦法,可以換成使用一個靜態(tài)的列表池,默認數據結構是沒有池這個類型的,所以我們自己創(chuàng)建一個泛型列表池的類。

ListPool<T>是如何工作的?
我們已經使用了好久的泛型列表了.比如List<int>是一個存儲整數的列表.通過在ListPool中聲明類型后使用,表明它是一個泛型類??梢詾榉盒筒糠质褂萌魏螛俗R符,但通常只使用T作為類型標識符。

可以用棧來存儲列表的集合,通常不使用棧是因為Unity沒有為其序列化,不過在這個情況中沒有關系。

現在可以在HexMesh中使用列表池了,把靜態(tài)列表換為非靜態(tài)的私有引用,標記為NonSerialized,這樣Unity就不會在重編譯時保存它們。寫作System.NonSerialized或者在腳本的頭部添加using System都行。

這就保證了無論同時填充多少mesh信息,列表都可以復用。

6.4 碰撞可選

我們的地形mesh需要添加碰撞,但河流的mesh并不需要,射線會穿過河面擊中河道底部。所以添加一個布爾類型的公共字段useCollider,并為地形mesh開啟。

6.6 UV坐標可選

到目前為止還能為UV坐標添加可選功能,雖然地形不需要,但是水面會用到。

7 河流視覺效果

終于到了制作河流效果的時候了!這里會用四邊形來代表河面,由于河水是會流動的,就用UV坐標表示流動方向。為了讓其可視化,創(chuàng)建一個新的標準著色器命名為River,修改它的UV坐標放到紅綠反射通道里。

地圖塊的預制體

創(chuàng)建一個河流的材質球,使用剛才新建的著色器,確保Rivers對象應用這個材質球。同時只勾選腳本的use UV coordinates。

河流子物體


7.1 水面的三角剖分

在三角化水面之前,首先要確定水面的高度。與河床的高度一樣,在HexMetrics里去定義水面的高度偏移。因為Y方向的擾動設置的是高度等級的一半,所以我們也以此為水面高度的偏移,這確保了水面永遠不會在地形之上。

TriangulateWithRiver是第一個添加河流四邊形的方法,第一個四邊形位于單元格的中心和中間邊界線之間,第二個位于中間邊界線和單元格邊界之間。這里就簡單的使用已經獲取的頂點作為參數,因為這些頂點較地形會低一些,頂點的位置會在傾斜的河道墻面的里面,所以我們不用關心水面的邊界頂點是否精確吻合河道。

為什么河面的寬度會變化?
這是因為單元格的高度被隨機擾動了,但是河床和河面的高度并沒有。單元格的高度越高,河道的墻面間距就越窄,這就使河面看起來變得狹窄了。


7.2 順流而行

我們當前需要考慮的問題是UV坐標是不是與河流的方向一致,先定義當看向下游方向時U坐標值0位河流的左邊,1為右邊,并且V坐標從0到1表示河流流向。

根據當面定義的規(guī)則,我們UV坐標在三角化流向單元格外的河流時是正確的,對流向單元格內的則是錯誤并且剛好顛倒的。為了更方便的定義,添加一個布爾類型的參數reversed到TriangulateRiverQuad里,當需要顛倒UV坐標時使用。

7.3 河流的起點和終點

在TriangulateWithRiverBeginOrEnd里,只需要檢測是否有流入的河流來確認河流的流向,這樣就能在中間邊界線和單元格邊界之間插入其他的四邊形。

結束的位置是否少了一些水面?
因為四邊形時由兩個三角形組成的,所以當四邊形不是平的時候,它們的形狀取決于方向。由于這個原因,河道兩邊的墻壁三角剖分是不對稱的。當水面與河道墻壁相交時,這一點尤為明顯。
可以通過鏡像四邊形來消除這種差異,但出現這種情況的原因明顯是因為暫時未應用頂點擾動,一旦這么做對稱性就被打破了。

7.4 單元格之間的河流部分

當要在單元格之間添加河流時,我們必須注意高度的差異。為了能讓水流下斜坡和懸崖,TriangulateRiverQuad需要應用兩個高度參數。

河流完成


7.5 拉伸V坐標

目前V坐標從0到1貫穿河流的每一段,單元格內是四段,再算上單元格之間的連接部分就是5段,那么使用相同材質貼圖賦值給河流,它就會重復很多次。

我們可以拉伸V坐標來減少重復性,讓從0到1表示單元格加上連接部分的所有河流。這可以通過每段河流之間的V坐標遞增0.2實現。如果單元格中心時0.4,中心線位置就是0.6,到達邊界時就是0.8,連接部分就是1。

如果河流流向是相反方向,中心位置依然是0.4不變,但中間線位置就變成了0.2,邊界就是0,繼續(xù)算連接處的V坐標就是-0.2。這沒有問題,因為這就等價于filter模式下的紋理設置重復時的0.8,就像0等價于1一樣。

現在為處理流出河流提供正確的UV坐標,首先在TriangulateWithRiver里。

然后是TriangulateConnection里。

為了正確看到V坐標的效果,確保其在著色器中保持正值。

8 河流動畫

處理好UV坐標后,開始處理河流動畫。這部分會由著色器負責,所以不用連續(xù)更新mesh信息表現動畫。

這篇教程不會教你如何創(chuàng)建一條有精細效果的河流,那些內容會放到后面?,F在用一個簡單的視覺效果讓你了解動畫是如何工作的。

河流動畫會通過滑動基于運行時間的V坐標實現,Unity中通過_Time在著色器中獲取這個變量,它的y分量中包含未修改的原始時間。我們就使用這個,其他的分量包含不同的時間縮放。

去掉V坐標的修正,現在不再需要它了,相應地從V坐標中減去當前時間,這將使坐標向下滑動,從而產生河流向前流動的錯覺。

一秒鐘后,所有地方的V坐標都會小于零,所以我們不會再看到差異。同樣,這是由于重復紋理過濾模式。但是為了看看到底發(fā)生了什么,我們可以取V坐標的小數部分。


8.1 使用噪聲紋理

現在河流能動起來了,但是方向和速度的過渡都很粗糙。我們的UV模式使這一點看起來非常明顯,但是當使用更像水的紋理時,就不會那么容易發(fā)現了。因此我們去采樣一個紋理,而不是顯示原始的UV??梢跃褪褂梦覀冇械哪菑堅肼晥D并對其采樣,把紋理的顏色乘上噪聲圖的第一個通道。

因為V坐標拉伸的太明顯,噪聲圖也沿著河流的方向被拉伸了,這個效果并不好看。通過縮小U坐標的比例來從另一個方向拉伸,十六分之一應該是個合適的值。這意味著只在噪聲圖的一條窄帶上進行采樣。


8.2 混合噪聲

已經看起來好多了,但是河流的樣子看起來一直都是一個樣,真正的水流看起來可不像這樣。

由于只使用了紋理圖上的一條窄帶,可以滑動這個窄帶的位置來改變樣式??梢酝ㄟ^時間滑動U坐標實現,但是要確保其變化緩慢,否則河流看起來就像是往邊上在流動。先設置這個縮放因子為0.005,這意味著200秒紋理才能完成一個循環(huán)。

不幸的是看起來不怎么樣,即使滑動很慢,側向移動也很明顯,并且水面的樣式看起來仍然是靜止的??梢酝ㄟ^組合兩個噪聲采樣來隱藏滑動,這兩個樣本都往相反的方向滑動,如果使用稍微不同的值來移動第二個樣本,它將產生一個微妙的變形動畫。

為了確保不會重疊一樣的噪聲,為第二個樣本使用噪聲圖的另一個通道。


8.3 半透明水面效果

水面的紋理效果已經足夠了,下一步是讓其變得半透明。

首先,確保水面不會投射陰影,設置河流mesh預制上渲染器的陰影投射為關閉狀態(tài)。

9 調整優(yōu)化

現在所有的東西看起來都工作正常,是時候重新啟用頂點擾動了,單元格的邊界變形也會使河流的形狀不規(guī)則。

頂點擾動與不擾動的對比

檢查一下地形,看看頂點擾動是不是會引起問題。事實證明確實會有問題,注意這些較高的瀑布。

河流被懸崖截斷

從高處落下的水會消失在懸崖的后面,當這種情況發(fā)生時是很明顯的,所以我們得做點什么。

不太明顯的是瀑布是傾斜而不是直線下降的,雖然水不是這么運動的,但也不會明顯感覺違和,所以忽略這個。

防止水消失的最簡單方法是使河道更深,在水面和河床之間創(chuàng)造更多空間,但這也使河道墻壁更偏向垂直,所以我們不修改太多。設置MexMetrics里的streamBedElevationOffset到-1.75,這就能解決大部分問題,并且河道不會顯得太深。一些水面仍然會被截斷,但不會出現整段都隱藏在懸崖后面的情況了。

更深一些的河道

本期工程地址: https://github.com/tank1018702/Hex-Map-Learning/tree/Rivers
下一篇教程是:https://catlikecoding.com/unity/tutorials/hex-map/part-7/


有想系統學習游戲開發(fā)的童鞋,歡迎訪問http://levelpp.com/??

有專業(yè)開發(fā)交(gao)流(ji)群等待大家強勢插入:869551769

HexMap學習筆記(六)——河流的評論 (共 條)

分享到微博請遵守國家法律
华容县| 常宁市| 宜阳县| 辉县市| 固原市| 宜春市| 玉山县| 吉隆县| 桂平市| 永登县| 汾西县| 大港区| 确山县| 石林| 安龙县| 读书| 龙山县| 禹城市| 武陟县| 蒙阴县| 浪卡子县| 祁阳县| 万州区| 昆山市| 漳浦县| 读书| 承德市| 普安县| 平顺县| 南雄市| 太湖县| 枞阳县| 珠海市| 南乐县| 宝鸡市| 竹山县| 商南县| 邵东县| 南澳县| 祁东县| 永胜县|