ugui unity 图片缩放循环_Unity基础系列(四)——构造分形(递归的实现细节)...
目錄 | 1 如何構(gòu)建分形 2 展示內(nèi)容 3 構(gòu)造子節(jié)點 4 塑造子節(jié)點 5 創(chuàng)建多個子節(jié)點 6 更多的子節(jié)點,更好的代碼 7 爆炸性生長 8 添加顏色 9、隨機(jī)化Mesh 10 使分形不規(guī)則 11 旋轉(zhuǎn)分形 12 添加更多的不確定 |
本文重點:
1、實例化游戲?qū)ο?/p>
2、了解遞歸
3、使用協(xié)程
4、添加隨機(jī)性
分形是一個非常有意思的東西,而且大部分時候都很漂亮。在本教程中,我們將編寫一個小的C#腳本,讓它完成一些類似分形的行為。
這里假設(shè)你已經(jīng)能夠了解一些Unity的基本操作,并且能夠創(chuàng)建基本的C#腳本了。如果這些還不熟悉的話,可以再復(fù)習(xí)一下第一章 時鐘 相關(guān)的內(nèi)容。
這是一篇比較舊的教程,里面提到的漫反射和鏡面材質(zhì)可能已經(jīng)不使用與Unity2017了,所以可以忽略這些,但除此之外,這篇教程所展示的內(nèi)容還是很有意思的。
(創(chuàng)建隨機(jī)的3D分形)
1 如何構(gòu)建分形
在開始構(gòu)建3D分形之前,先要理解分形的概念。
簡單的來說就是一個粗糙的幾何物體,可以分為若干部分,每個部分都是(或者近似)該物體縮小后的形狀。可以將其應(yīng)用到Unity中的對象hierarchy中來實現(xiàn)這個效果。比如從某個根對象開始,然后向其中添加較小但在其他方面相同的子對象。
手動完成該操作將會非常麻煩,因此創(chuàng)建腳本來完成。
創(chuàng)建一個新項目和一個新場景。在里面放了一個方向光,把相機(jī)移到一個合適的角度,也可以隨意設(shè)置。
繼續(xù)創(chuàng)建一個用于分形的材質(zhì)。材質(zhì)很簡單,僅僅使用specular 著色器與默認(rèn)設(shè)置即可,比起漫反射,這個看起來更舒服一些。
創(chuàng)建一個新的空游戲?qū)ο蟛⑵浞胖迷谠c。這將是分形的母體。然后創(chuàng)建一個名為Fractal的新C#腳本,并將其添加到對象上。
(工程創(chuàng)建)
2 展示內(nèi)容
腳本有了,那么分形是什么樣子的呢?這里通過在 Fractal 組件腳本中添加一個公共的Mesh和材料material
來實現(xiàn)它的可配置性。然后插入一個Start方法,在其中添加一個新的MeshFilter組件和一個新的MeshRenderer組件。同時,直接分配對應(yīng)的網(wǎng)格和材料給它們。
什么是mesh?
按照傳統(tǒng)理解,mesh是圖形硬件用來繪制復(fù)雜東西的結(jié)構(gòu)。它是一個3D對象,要么從外部導(dǎo)入到Unity中,這是Unity的默認(rèn)形狀之一,要么是由代碼生成。mesh需要包含3D空間中的點集合,以及由這些點定義的一組三角形(最基本的2D形狀)。由三角形構(gòu)成網(wǎng)格所代表的任何表面。
大部分時候,你不會意識到你看到的其實是一堆三角形。
什么是材質(zhì)?
材質(zhì)用來定義物體的視覺特性。它們可以是非常簡單(比如一個恒定的顏色),也可以非常復(fù)雜。材質(zhì)一般要包括一個著色器和任何著色器需要的數(shù)據(jù)。
著色器基本作用是告訴顯卡如何繪制物體的多邊形。標(biāo)準(zhǔn)漫射著色器使用單一的顏色和可選的紋理,結(jié)合場景中的光源,來確定多邊形的外觀。這里使用的是稍微復(fù)雜的鏡面著色器,同時模擬了一個亮點。
Start函數(shù)什么時候調(diào)用組件創(chuàng)建之后,處于active狀態(tài),并且在第一次調(diào)用它的Update方法之前(如果它有的話),Start方法會被Unity調(diào)用。而且只調(diào)用一次。
AddComponent 怎么用?
AddComponent方法可以創(chuàng)建特定類型的新組件,并將其附加到游戲?qū)ο?#xff0c;返回對其的引用。這就是為什么我們可以立即訪問組件的值。當(dāng)然也可以使用中間變量。
MeshFilter Filter=gameObject.AddComponent();
filter.mesh=Mesh;
這里展示了一個特殊的語法。因為它是一個通用方法,實際上是可以處理一系列類型的模板。你可以通過在尖括號中傳入?yún)?shù)它來告訴它應(yīng)該使用什么類型。
現(xiàn)在可以把我們定制的材質(zhì)分配給fractal組件了。還可以通過單擊屬性旁邊的點并從彈出窗口中選擇Unity默認(rèn)的立方體來分配Mesh。弄完之后,進(jìn)入播放模式時,就會顯示一個立方體了。當(dāng)然,也可以在代碼里手動添加組件。
(運行時可以看到組件了)
3 構(gòu)造子節(jié)點
該如何為這個分形創(chuàng)作子節(jié)點呢?最簡單的方法就是在Start函數(shù)里創(chuàng)建一個新的Game Object并向其添加一個Fractal組件,試一下。
new 干了什么事情?
new 關(guān)鍵字用于構(gòu)造對象或結(jié)構(gòu)體的新實例。然后調(diào)用一個特殊的構(gòu)造函數(shù)方法,該方法與它所屬的類或結(jié)構(gòu)的名字相同。
現(xiàn)在問題是,每一個新的分形實例都會產(chǎn)生另一個分形實例。每一幀都會發(fā)生,無窮無盡,導(dǎo)致死循環(huán)。如果不手動關(guān)閉,運行一段時間,當(dāng)它把內(nèi)存耗盡了之后,你的電腦就會死機(jī)了。
但大部分時候,無法停止的遞歸算法幾乎會立即消耗完機(jī)器的資源,并導(dǎo)致堆棧溢出異常或崩潰。但在這個示例中,相對來說沒那么快,因為它的遞歸的比較慢。
為了防止這種情況發(fā)生,需要引入一個最大深度的概念。最開始的分形實例的深度為零。每個它的后代節(jié)點都會有一個深度值。比如它的孫節(jié)點會有一個2的深度值,以此類推,直到達(dá)到最大的深度。
在inspector 窗口中添加一個公共maxDepth整數(shù)變量并將其設(shè)置為4。再添加一個私有深度整數(shù)。然后,只有當(dāng)我們在最大深度以下時,才創(chuàng)建一個新的子級。
(最大深度)
現(xiàn)在進(jìn)入播放模式時會如何呢?
只有一個子節(jié)點被創(chuàng)造出來了。這是為什么呢?因為我們從來沒有給 depth 值,它總是零。因為零小于4,我們的根分形對象創(chuàng)建了一個子對象。孩子的深度值也是零。又因為,也沒有設(shè)置子節(jié)點的maxDepth,所以它也是零。因此,該子節(jié)點并沒有創(chuàng)造另一個。
除此之外,子節(jié)點也沒有分配材質(zhì)和Mesh。這些引用可以直接從它的父級復(fù)制。現(xiàn)在添加一個處理所有必要初始化的新方法。
this是什么意思?
this此關(guān)鍵字引用正在調(diào)用其方法的當(dāng)前對象或結(jié)構(gòu)。在引用同一個類的內(nèi)容時,它一直被隱式地使用。例如,每當(dāng)我們訪問深度時,我們也可以通過this.depth來完成。通常只在需要傳遞對對象本身的引用時才需要使用此方法,就像對Initialization所做的那樣。那又是為什么要這樣做呢?因為需要調(diào)用的是新的子對象的Initialization方法,而不是父對象的初始化方法。
Initialize 調(diào)用是否在 Start 之前?
是的。首先創(chuàng)建新的游戲?qū)ο蟆H缓髣?chuàng)建并添加一個新的分形組件。此時,如果存在其Awake和OnEnable方法,則將調(diào)用它們。然后AddComponent方法完成。在此之后,直接調(diào)用Initialization。Start的調(diào)用要到下一幀才會執(zhí)行了。
進(jìn)入游戲模式,如預(yù)期的邏輯,這一次會創(chuàng)建四個子孫代。但它們現(xiàn)在還不是真正的孩子,因為它們都出現(xiàn)在層次根節(jié)點中。游戲?qū)ο笾g的父子關(guān)系是由它們的轉(zhuǎn)換層次來定義的。因此,一個孩子需要使它的transform組件的parent等于它的分形父transform 。
(兩種不同的層次結(jié)構(gòu))
4 塑造子節(jié)點
到目前為止,子節(jié)點已經(jīng)被疊加在父節(jié)點上了,這意味著仍然只看到一個立方體。現(xiàn)在需要把他們移動到他們的本地空間中,讓它們也能被看到。
每個子節(jié)點都應(yīng)該比它們的父母小,所以我們也必須縮小它們的Scale值。
第一個要解決的是縮放。那么應(yīng)該縮放多少呢?用一個名為child Scale的新變量來配置它,并在inspector中給它賦值0.5。別忘了把這個值也從父節(jié)點傳給子節(jié)點。然后用它來設(shè)置子節(jié)點的local scale。
接下來,該把這些孩子節(jié)點搬到哪里去呢?那就直接向上移動吧,這樣它們就能接觸到它們的父節(jié)點。假設(shè)父節(jié)點在所有方向上的大小的單位是1,對于現(xiàn)在正在使用的立方體來說正好合適。向上移動一半,使父節(jié)點和子節(jié)點正好接觸在一起。因此,我們還需要移動一個額外的距離,距離相當(dāng)于子節(jié)點的一半大小。
(子節(jié)點縮放值為0.5,從0.3至0.7)
5 創(chuàng)建多個子節(jié)點
現(xiàn)在我們做出來的東西有點像一座塔,還不是真正的分形,要完成分形還需要將它分支化。每個父節(jié)點創(chuàng)建多個子節(jié)點比較容易。但它們必須朝著不同的方向發(fā)展。因此,需要向Initialization方法中添加一個方向參數(shù),并使用它將第二個子節(jié)點定位到右邊而不是上面。
…是什么意思?
這意味著我省略了一段沒有改變的代碼。應(yīng)該清除或更改代碼的位置,或者它的確切位置并不重要。
(每個父節(jié)點擁有2個子節(jié)點)
這看起來已經(jīng)有點感覺了!那么光從結(jié)果來看你能知道它是按照什么順序來建造的嗎?因為它們都是在幾幀之內(nèi)創(chuàng)建的,速度太快,無法看到它的創(chuàng)建的過程。如果能放慢這個過程應(yīng)該會很有意思,因為這樣就能看到它的發(fā)生的過程。要如何去完成放慢的過程呢?答案是可以通過協(xié)同線創(chuàng)建子節(jié)點來實現(xiàn)。
協(xié)程可以看做是可以插入暫停語句的方法。當(dāng)方法調(diào)用暫停時,程序的其余部分繼續(xù)進(jìn)行。雖然這個類比不太恰當(dāng),太過于簡單化,但我們現(xiàn)在只需要利用這個特點就可以了。
將創(chuàng)建兩個子節(jié)點的代碼行移動到一個名為CreateChildren的新方法中。此方法需要將IEnumerator作為返回類型,該類型存在于System.Collection命名空間中。這就是為什么Unity在他們默認(rèn)的腳本模板中包含它,以及為什么本示例在一開始也包括它的原因。
改變了方法類型之后,調(diào)用的方式也要調(diào)整,這里不能再用直接調(diào)用的方式了,取而代之,要使用Unity的StartCoroutine方法。
然后在創(chuàng)建每個子節(jié)點之前添加一個暫停指令。如代碼所示,每半秒鐘內(nèi)創(chuàng)建一個新的WaitForSecond對象,然后將其返回給Unity。
enumerator是什么?
枚舉是一次遍歷某個集合的概念,就像循環(huán)遍歷數(shù)組中的所有元素一樣。enumerator(枚舉器)或iterator(迭代器)是為此功能提供接口的對象。System.Collections.IEnumerator描述了這樣的接口。
為什么我們需要用這個呢?因為協(xié)程需要用。這也是Unity在默認(rèn)腳本模板中包含System.Collection的原因,也是本示例將它包括在內(nèi)的原因。
return 做了什么?
return關(guān)鍵字可以表示一個方法中斷或者已經(jīng)完成,把響應(yīng)的結(jié)果返回給調(diào)用者。返回的內(nèi)容必須與方法的類型匹配。如果它是一個空方法,那么也只需要返回空。
對于一個函數(shù)定義為空,可以省略return關(guān)鍵。
同樣的,一個方法中可能有多個return語句。在這種情況下,有多個可能的返回點。通常使用if語句來確定使用了哪些return。
yield有什么用?
yield語句被迭代器用來控制協(xié)程的生命周期。要使枚舉,就需要跟蹤它的進(jìn)度。這涉及到一些基本相同的樣模板碼。你真正想要的是只編寫類似于 return firstItem; return secondItem這樣的代碼,直到函數(shù)執(zhí)行結(jié)束。yield語句允許你準(zhǔn)確地做到這一點。
因此,無論何時使用yield,都會在幕后創(chuàng)建枚舉器對象,以處理繁瑣的部分。這就是為什么我們的CreateChildren方法將IEnumerator作為其返回類型的原因。
順便說一下,你還可以生成另一個迭代器。在這個示例里,另一個迭代器會被完全的處理,所以你其實可以用創(chuàng)造性的方式將它們縫合在一起。
協(xié)程怎么工作?
當(dāng)你在Unity中創(chuàng)建協(xié)程時,真正做的其是創(chuàng)建一個迭代器。當(dāng)你將它傳遞給StartCooutine方法時,它將被存儲,并被要求每幀都要它的下一個Item,直到它完成為止。
yield語句會產(chǎn)生Item。而這中間的部分就是你可以發(fā)揮的地方了。
當(dāng)你自己的代碼繼續(xù)運行時,你也可以產(chǎn)生一些特殊的協(xié)程,比如WaitForSecond,這樣就可以更好地控制代碼邏輯,但是總的來說都是一個迭代器而已。
現(xiàn)在可以看著它生長了!你能看出來這樣做有什么問題嗎?可能現(xiàn)在還不明顯,現(xiàn)在為每個父節(jié)點添加第三個子節(jié)點,這一次放在左邊。
(每個父節(jié)點3個子節(jié)點,正常和overdraw視角)
如果查看overdraw效果?
場景視圖的工具欄有一個下拉列表,默認(rèn)設(shè)置為RGB。它的另一個選擇是 Overdraw 。
其實問題是子節(jié)點和他們的父節(jié)點有著相同的參考點。這意味著,其父母本身就是右子節(jié)點的左子節(jié)點。可能有點繞,就是說,父節(jié)點和子節(jié)點在某些方向上重合了。
為了解決這個問題,需要對子節(jié)點進(jìn)行旋轉(zhuǎn),這樣他們的向上方向就會遠(yuǎn)離他們的父節(jié)點。
我通過向Initialization添加一個方向參數(shù)來解決這個問題。它將是一個四元數(shù),用于設(shè)置新子節(jié)點的local rotation。向上的子節(jié)點不需要旋轉(zhuǎn),右邊的子節(jié)點需要順時針旋轉(zhuǎn)90度,左邊的子節(jié)點需要向相反的方向旋轉(zhuǎn)。
(旋轉(zhuǎn)后的效果)
現(xiàn)在子節(jié)點已經(jīng)被旋轉(zhuǎn)了,但它們生成出來的卻不是分形了。一些最小的子節(jié)點最終仍然會消失在根立方體里面。這是因為如果Scale因子為0.5,這個分形將在四個步驟中產(chǎn)生了自相交。你可以通過減少縮放來解決這個問題,也可以使用球體代替立方體。
(子節(jié)點縮放為0.5的球體并沒有產(chǎn)生自相交)
6 更多的子節(jié)點,更好的代碼
現(xiàn)在的代碼已經(jīng)有些笨重了。可以通過將方向和方位數(shù)據(jù)移動到靜態(tài)數(shù)組來優(yōu)化。然后,再將CreateChildren簡化為一個短循環(huán),并使用子索引作為Initialization的參數(shù)。
數(shù)組如何工作?
數(shù)組是長度固定的對象,包含一個線性變量序列。在聲明變量時,將方括號放在其類型后面表示需要該類型的數(shù)組。所以int myVariable;讓你獲得一個整數(shù),而int[]myVariable;讓你獲得一個整數(shù)數(shù)組。
訪問數(shù)組中的一個條目的方法是將數(shù)組索引(而不是位置)放在變量后面的方括號中。MyVariable[0]獲取數(shù)組中的第一個條目,myVariable[1]獲取第二個條目,依此類推。
實際上,創(chuàng)建一個數(shù)組并將其賦值給變量是使用myVariable=newint[10]完成的;在本例中,該數(shù)組創(chuàng)建了一個包含10個條目空間的新數(shù)組。或者,您可以通過在花括號中列出它的初始值來隱式地創(chuàng)建一個,比如myVariable={1,2,3};。
for循環(huán)怎么工作?
for循環(huán)是編寫遍歷某些循環(huán)的一種緊湊方式。在本例中,我們使用一個名為i的整數(shù)作為迭代器。第一部分聲明迭代器整數(shù),第二部分檢查循環(huán)的條件,第三部分增加迭代器。您可以使用while循環(huán)來獲得完全相同的結(jié)果,但是迭代器代碼不方便分組。
對于(int i=0;i<10;i++){doStuff(I);}
與int i=0;
while(i<10){doStuff(I);i++}效果相同。
順便說一句,i++是i+=1的縮寫,它是i=i+1的縮寫。
現(xiàn)在,讓我們通過簡單地將數(shù)據(jù)添加到數(shù)組中,再引入兩個子元素。一個向前,另一個向后。
(完整的分形,每個父節(jié)點擁有5個子節(jié)點)
現(xiàn)在有了完整的分形結(jié)構(gòu)。但是根立方體的底部為什么沒有呢?可以這樣想,分形是從某種東西中生長出來的,比如一種植物。雖然我沒有,但如果你想的話,可以添加一個特殊的第六個子節(jié)點向下,但只是添加到根節(jié)點就好。添加到所有子節(jié)點的話又會變成第6個子分形了。
7 爆炸性生長
剛才的示例,我們實際創(chuàng)建了多少個立方體?因為我們總是為每個父節(jié)點創(chuàng)建五個子節(jié)點,當(dāng)完全成長的時候,立方體的總數(shù)將取決于最大的深度。最大深度為零只產(chǎn)生一個立方體,即初始的根節(jié)點。最大深度為一個,產(chǎn)生五個額外的孩子,總共有六個立方體。由于它是分形的,這個圖案重復(fù),我們可以把它寫成函數(shù)f(0)=1,f(N)=5×f(n-1)+1。
上述函數(shù)產(chǎn)生序列1、6、31、156、781、3906、19531、97656等。你將看到這些數(shù)字顯示為Unity游戲視圖中統(tǒng)計數(shù)據(jù)中的DrawCall的數(shù)量。如果啟用了動態(tài)批處理,則它將是DrawCall 和 Saved by batching 的總和。
Unity處理四五層的深度還綽綽有余。再高的話,你的幀率將急速下降。
除了數(shù)量,持續(xù)時間也是一個問題。現(xiàn)在,我們在創(chuàng)建一個新的子節(jié)點之前暫停了半秒鐘。這會產(chǎn)生幾秒鐘的同步增長。我們可以通過隨機(jī)延遲來更均勻地分配增長。這也導(dǎo)致了一個更不可預(yù)測和有機(jī)的模式,讓觀察更有意思。
把固定的延遲替換為0.1到0.5之間的隨機(jī)范圍。我還增加了最大深度到5,使效果更加明顯。
隨機(jī)范圍是如何工作的?
Random是一個實用工具類,它包含一些接口來創(chuàng)建隨機(jī)值。它的 Range 方法可用于在一定范圍內(nèi)生成隨機(jī)值。Range方法有兩個版本。可以使用兩個浮點數(shù)來調(diào)用它,在這種情況下,它會在最小值和最大值之間返回一個浮點數(shù),這兩者都包括在內(nèi)。或者,可以用兩個整數(shù)調(diào)用Range,在這種情況下,它返回一個整數(shù),介于最小、排除最大值之間的某個值。這個版本的典型用例是隨機(jī)選擇一個索引,比如某某數(shù)組[Random.Range(0,omeArray.Length)]。
8 添加顏色
這個分形沒有什么生氣。通過添加一些顏色變化來搞點氣氛。通過從根部的白色插入到最小的子節(jié)點的黃色來實現(xiàn)吧。Color.Lerp 接口是一種方便的方式。內(nèi)插器從0到1,我們通過將當(dāng)前深度除以最大深度來實現(xiàn)。因為這里不能用整數(shù)除法,所以我們首先將深度轉(zhuǎn)換為浮點數(shù)。
Lerp是干什么的?
LERP是線性插值的簡稱。它的典型特征是Lerp(a,b,t),它計算a+(b-a)*t,t在0-1范圍內(nèi)。有不同類型存在多個版本,包括浮點數(shù)、向量和顏色。
(上色了,但是沒有動態(tài)批處理)
這看起來有內(nèi)味了!但另一件事也發(fā)生了。動態(tài)批處理過去是起作用的,但現(xiàn)在不行了。我們該如何解決這個問題呢?
什么是動態(tài)批處理?
動態(tài)批處理是由Unity執(zhí)行的一種drawcall批處理形式。簡而言之,它將共享相同材料的網(wǎng)格組合成更大的網(wǎng)格。這樣做減少了CPU和GPU之間的通信量。
你可以通過 Edit/Projects Settings/Player/, 在 Other Settings 啟用或禁用它。
它只適用于小網(wǎng)格。比如,你會發(fā)現(xiàn)它適用于Unity默認(rèn)的立方體,但不適用于默認(rèn)的球面。
導(dǎo)致這個結(jié)果的問題是,因為調(diào)整子節(jié)點的材質(zhì)顏色,Unity默默地創(chuàng)造了一個復(fù)制的材質(zhì)。這其實是必要的,不然一切使用該材質(zhì)的都將以相同的顏色結(jié)束繪制。然而,批處理只有在相同的材質(zhì)被用于多個物體時才有效。不相等的不檢查也不合并--因為要檢查的話就太耗性能了,而且結(jié)果也不一定就滿足合批條件--所以它必須是同一種材質(zhì)。
那在每個深度都創(chuàng)建一個材質(zhì)的副本,而不是每個立方體。添加一個新的數(shù)組字段來保存材質(zhì)。然后Start時檢查是否存在數(shù)組,如果沒有,則調(diào)用一個新的InitializeMaterials方法。在這種方法中,我們將顯式復(fù)制我們的材料和改變每一深度的顏色。
null是什么?
非簡單值的變量的默認(rèn)值為NULL。這意味著變量沒有引用任何內(nèi)容。試圖從變量中調(diào)用或訪問任何為NULL的內(nèi)容都會導(dǎo)致錯誤。你需要判斷這個值,以確保不會發(fā)生這種情況。
你也可以自己將這樣的變量設(shè)置為NULL,以便處理你不再需要它所引用的任何內(nèi)容。注意,當(dāng)將對對象的引用設(shè)置為NULL時,對象并不會自動被銷毀。只有當(dāng)所有地方都不引用他們的時候,他們才會成為垃圾收集器收集。
還請注意,此方法適用于私有組件字段,但不適用于公共組件字段。這是因為Unity的序列化系統(tǒng)會為它創(chuàng)建一個空數(shù)組,而本例中它不會是空數(shù)組。
現(xiàn)在,不要將材料引用從父節(jié)點傳遞到子節(jié)點,而是只傳遞材料數(shù)組的引用。如果不這么做的話,每個子節(jié)點將被迫創(chuàng)造自己的材料數(shù)組,我們就不能解決問題了。
為什么不把 materials 設(shè)置為靜態(tài)?
之所以不把materials數(shù)組設(shè)置為靜態(tài),是因為它取決于最大深度,這可能不同于分形和分形之間。同一時間你可以有多個分形但他們可以有不同的最大深度。
(上色了 并且有了動態(tài)批處理)
批次合并又回來了,但是已經(jīng)和之前的不一樣了。但顏色還是沒那么豐富。一個很好的調(diào)整是給最深的層次一個完全不同的顏色。這可以揭示分形的模式,可能你這樣也沒注意到吧。
簡單地改變最后的顏色到洋紅之后。此外,調(diào)整內(nèi)插器,使我們?nèi)匀豢吹酵耆^渡到黃色。當(dāng)我們在做它的時候,它的平方會帶來一個稍微好一些的轉(zhuǎn)變。
(有洋紅色的提示了)
再添加第二個顏色級數(shù),例如從白色到青色的紅色提示。我們將使用一個單一的二維數(shù)組來容納它們,然后在需要材質(zhì)時隨機(jī)選擇一個。這樣,當(dāng)我們進(jìn)入游戲模式時,我們的分形看起來就會有所不同。如果愿意,可以隨意添加第三步。
(隨機(jī)顏色)
9、隨機(jī)化Mesh
除了顏色,我們還可以隨機(jī)選擇使用哪個Mesh。用數(shù)組替換公共網(wǎng)格變量,并從其中隨機(jī)選擇一個。
如果要在檢查器中的新數(shù)組屬性中只放置一個立方體,那么結(jié)果將和以前一樣。但是如果加上一個球體,你就會突然得到50%的幾率,形成一個立方體,或者每個分形元素中的一個球體。
隨意填充此數(shù)組。我把球體放了兩次,所以它被使用的可能性是立方體的兩倍。你也可以添加其他Mesh,膠囊和圓柱體不太好,因為它們是拉長的。
(隨機(jī)選擇立方體和球體)
10 使分形不規(guī)則
現(xiàn)在的分形完成的很好,很完整,但是可以通過切斷它的一些分支來使它更加有獨特。通過引入一個新的公共spawnProbability變量來實現(xiàn)。傳遞這個值,然后用它隨機(jī)地決定我們是產(chǎn)生一個子節(jié)點還是跳過。0的概率意味著根本沒有孩子會生長,而1的概率意味著所有的孩子都會產(chǎn)卵。即使數(shù)值略低于一個,也會大大改變我們分形的形狀。
靜態(tài)Random.value屬性在0到1之間產(chǎn)生一個隨機(jī)值。將它與 spawnProbability 相比較可以告訴我們是否應(yīng)該創(chuàng)建一個新的子節(jié)點。
(70%概率產(chǎn)生的分形效果)
11 旋轉(zhuǎn)分形
一我們的分形一直是個好孩子,一動不動。但是如果有一點動作是不是會更有趣。添加一個非常簡單的Update方法,它以每秒30度的速度圍繞當(dāng)前的Y軸旋轉(zhuǎn)。
有了這個簡單的方法,所有的分形部分現(xiàn)在都在快樂地旋轉(zhuǎn)。都是以同樣的速度。那么再次隨機(jī)化!并使最大速度也可配置。
注意,我們必須在start(而不是Initialization)中初始化我們的旋轉(zhuǎn)速度,因為根元素也應(yīng)該旋轉(zhuǎn)。
(配置速度)
12 添加更多的不確定
我們還能做更多的調(diào)整,以微妙的方式打破分形嗎?當(dāng)然!有很多!其中之一是通過增加一個微妙的旋轉(zhuǎn)來破壞分形元素的排列。我們稱之為扭曲。
(看起來不錯的扭曲)
另一種選擇是把子節(jié)點的比例弄得亂七八糟。或者有時跳過深度。擺造型?那就自己來嘗試下吧!
總結(jié)
以上是生活随笔為你收集整理的ugui unity 图片缩放循环_Unity基础系列(四)——构造分形(递归的实现细节)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python urllib3离线安装_全
- 下一篇: nessus导出报告格式有哪些_高分高能