又兩個(gè)星期沒(méi)寫(xiě)文章了,主要是沉迷 Screeps 這個(gè)游戲,真的是太好玩了導(dǎo)致我這兩個(gè)禮拜 Github 小綠點(diǎn)幾乎天天刷。其實(shí)想開(kāi)一個(gè)新坑大概把自己寫(xiě) AI 的心路歷程記錄下,不過(guò)覺(jué)得因?yàn)橐奶鄷r(shí)間暫時(shí)決定先不開(kāi),準(zhǔn)備把過(guò)程中遇到的有趣的算法問(wèn)題記錄下就好了。言歸正傳今天來(lái)到「構(gòu)建分形」 這篇文章。比較簡(jiǎn)單主要介紹遞歸的思想。我們就迅速一些,因?yàn)槲疫€要繼續(xù)沉迷 Screeps,因?yàn)檫€要繼續(xù)學(xué)習(xí)嗯。。。再貼一次「原文鏈接」吧。。
「分形」這種東西,隨便了解一下大概就能想到作者要用遞歸的方法來(lái)完成。所以這一篇教程性質(zhì)的文章主要是講在 Unity 里使用遞歸完成一些事情。鑒于大家應(yīng)該上大學(xué)的時(shí)候隨隨便便上個(gè)課就差不多了解遞歸這種基本概念,因此我們就進(jìn)展快一些~大概要完成以下事情:
首先我們要在 MonoBehaviour 里面生成一個(gè)立方體,需要如下代碼。
public class Fractal : MonoBehaviour
{
public Mesh Mesh;
public Material Material;
// Use this for initialization
void Start ()
{
gameObject.AddComponent().mesh = Mesh;
gameObject.AddComponent().material = Material;
}
}
非常簡(jiǎn)單,然后在場(chǎng)景里新建一個(gè) GameObject 再掛上這個(gè)腳本,拖一些默認(rèn)的 mesh 和 material 上去就好了,運(yùn)行發(fā)現(xiàn) OK 完美成功。那么說(shuō)好的遞歸呢?非常簡(jiǎn)單,我們只需要在Start()
里面創(chuàng)建一個(gè)新的 GameObject 再給他掛上這個(gè) MonoBehaviour 就好。當(dāng)然要記得限制遞歸的次數(shù)不然要爆炸~每次遞歸都記得調(diào)整子物體的位置和大小,最后設(shè)置一下遞歸深度這樣就 OK 了,代碼如下
public class Fractal : MonoBehaviour
{
public Mesh Mesh;
public Material Material;
public int Depth;
// Use this for initialization
void Start ()
{
gameObject.AddComponent().mesh = Mesh;
gameObject.AddComponent().material = Material;
if (Depth > 0)
{
new GameObject("Fractal Child").AddComponent().Initialize(this);
}
}
public void Initialize(Fractal parent)
{
Mesh = parent.Mesh;
Material = parent.Material;
Depth = parent.Depth - 1;
transform.SetParent(parent.transform);
}
}
那么這樣就可以生成一大堆疊在一起的立方體了。。接下來(lái)的目標(biāo)就是對(duì)這段代碼修修補(bǔ)補(bǔ)讓這些立方體組成看起來(lái)像是分形的樣子。
首先我們嘗試讓每個(gè)立方體在除了底面的每一面生成一個(gè)比他小一半的立方體。首先需要讓Initialize()
接收位置和方向以及大小的參數(shù)。
public void Initialize(Fractal parent, float size, Vector3 pos, Vector3 rot)
{
Mesh = parent.Mesh;
Material = parent.Material;
Depth = parent.Depth - 1;
Size = size;
transform.SetParent(parent.transform);
transform.localPosition = pos;
transform.localEulerAngles = rot;
transform.localScale = Vector3.one * size;
}
非常簡(jiǎn)單,然后在每個(gè)立方體執(zhí)行Start()
的時(shí)候初始化 5 個(gè)小立方體,之所以我們需要設(shè)置小立方體的朝向是為了小立方體朝著其 Z 軸方向 (0, 0, 1) 生長(zhǎng),而不用考慮每次遞歸的時(shí)候的生長(zhǎng)方向。代碼如下
private void Start ()
{
gameObject.AddComponent().mesh = Mesh;
gameObject.AddComponent().material = Material;
if (Depth <= 0) return;
var posOffset = Size + Size / 2f;
new GameObject("Fractal Child").AddComponent().Initialize(this, Size, Vector3.left * posOffset, new Vector3(0, -90, 0));
new GameObject("Fractal Child").AddComponent().Initialize(this, Size, Vector3.right * posOffset, new Vector3(0, 90, 0));
new GameObject("Fractal Child").AddComponent().Initialize(this, Size, Vector3.up * posOffset, new Vector3(-90, 0, 0));
new GameObject("Fractal Child").AddComponent().Initialize(this, Size, Vector3.down * posOffset, new Vector3(90, 0, 0));
new GameObject("Fractal Child").AddComponent().Initialize(this, Size, Vector3.forward * posOffset, new Vector3(0, 0, 0));
}
最后在場(chǎng)景里設(shè)置下Scale = (0.5, 0.5, 0.5)
且size = 0.5f Depth = 4
,再設(shè)置初始物體 Z 向上,即(-90, 0, 0)
運(yùn)行一下效果如圖所示還不錯(cuò)~
嗯感覺(jué)還不錯(cuò)~再多設(shè)置一下變成 6 呢?我的 Macbook Pro 風(fēng)扇開(kāi)始呼呼的轉(zhuǎn)。。。
接下來(lái)稍微重構(gòu)下代碼~之前的太丑了。我們把五行長(zhǎng)得差不多的創(chuàng)建子物體的代碼提取一下關(guān)鍵參數(shù),完整版如下:
public class Fractal : MonoBehaviour
{
public Mesh Mesh;
public Material Material;
public int Depth;
public float Size;
private readonly Vector3[] _positions = {Vector3.left, Vector3.right, Vector3.up, Vector3.down, Vector3.forward};
private readonly Vector3[] _rotations = {Vector3.down, Vector3.up, Vector3.left, Vector3.right, Vector3.zero};
// Use this for initialization
private void Start ()
{
gameObject.AddComponent().mesh = Mesh;
gameObject.AddComponent().material = Material;
if (Depth <= 0) return;
var posOffset = Size + Size / 2f;
for (int i = 0; i < 5; i++)
{
new GameObject("Fractal Child").AddComponent().Initialize(this, Size, _positions[i] * posOffset, _rotations[i] * 90);
}
}
public void Initialize(Fractal parent, float size, Vector3 pos, Vector3 rot)
{
Mesh = parent.Mesh;
Material = parent.Material;
Depth = parent.Depth - 1;
Size = size;
transform.SetParent(parent.transform);
transform.localPosition = pos;
transform.localEulerAngles = rot;
transform.localScale = Vector3.one * size;
}
}
感覺(jué)作者寫(xiě)的美化一點(diǎn)都不美~不過(guò)我們還是按照教程順手做一些換個(gè) Mesh 啊隨機(jī)旋轉(zhuǎn)啦,生成機(jī)率之類(lèi)的事情吧也算是有個(gè)交代。
這個(gè)非常簡(jiǎn)單了我們把 Mesh 這個(gè)字段擴(kuò)充成一個(gè)數(shù)組。然后在初始化MeshFilter
的地方從里面隨機(jī)一個(gè)出來(lái),像下面這樣。然后在拖一些 Mesh 進(jìn)去。
public class Fractal : MonoBehaviour
{
public Mesh[] Mesh;
public Material Material;
...
private void Start ()
{
gameObject.AddComponent().mesh = Mesh[Random.Range(0, Mesh.Length)];
gameObject.AddComponent().material = Material;
...
}
...
}
這樣就可以了~運(yùn)行起來(lái)每次都不太一樣。。圖就不截了變化并不大大家應(yīng)該可以想象出來(lái)~
也很簡(jiǎn)單,添加一個(gè)機(jī)率然后在生成的地方每次隨機(jī)一下,隨到了就生成。。
public class Fractal : MonoBehaviour
{
...
public float Probability;
...
private void Start ()
{
...
for (int i = 0; i < 5; i++)
{
if (Random.Range(0, 1f) <= Probability)
{
new GameObject("Fractal Child").AddComponent().Initialize(this, Size, _positions[i] * posOffset, _rotations[i] * 90);
}
}
}
public void Initialize(Fractal parent, float size, Vector3 pos, Vector3 rot)
{
...
Probability = parent.Probability;
...
}
}
把機(jī)率調(diào)成 0.75 以后生成效果如下圖(跟上一條隨機(jī) Mesh 一起展示了)
接下來(lái)要做的就是讓這些東西全部動(dòng)起來(lái)。。。并且以隨機(jī)的速度。。嗯我已經(jīng)可以想像出來(lái)大概是怎樣的鬼畜場(chǎng)景了,嘗試實(shí)現(xiàn)一下的話首先就是加一個(gè)大旋轉(zhuǎn)速度。然后在Update()
里面隨機(jī)好速度然后做一次旋轉(zhuǎn)就好了~
public class Fractal : MonoBehaviour
{
...
public float MaxRotateSpeed;
...
private void Start ()
{
...
}
private void Update()
{
var rotationSpeed = Random.Range(-MaxRotateSpeed, MaxRotateSpeed);
transform.Rotate(0f, rotationSpeed * Time.deltaTime, 0f);
}
...
}
運(yùn)行一下發(fā)現(xiàn)似乎總是在原地抖動(dòng)的樣子。。。一定是我們速度變換的頻率太高了所以最終結(jié)果會(huì)趨近于原地不動(dòng),稍微限制一下加點(diǎn)隨機(jī)。。
public class Fractal : MonoBehaviour
{
...
public float MaxRotateSpeed;
public float RotateSpeedChangeRate;
private float RotateSpeed;
...
private void Update()
{
Random.InitState(Depth * (int)Mathf.Ceil(Time.time * 100));
if (Random.Range(0, 1f) <= RotateSpeedChangeRate)
{
RotateSpeed = Random.Range(-MaxRotateSpeed, MaxRotateSpeed);
}
transform.Rotate(0f, 0f, RotateSpeed * Time.deltaTime);
}
public void Initialize(Fractal parent, float size, Vector3 pos, Vector3 rot)
{
...
MaxRotateSpeed = parent.MaxRotateSpeed;
RotateSpeedChangeRate = parent.RotateSpeedChangeRate;
...
}
}
哇畫(huà)面真的是太詭異了。。。
好的這一篇文章就這樣成功的 水過(guò)去了 完成了~這一篇大概上就是遞歸的使用方法吧~其實(shí)沒(méi)怎么看原文自己摸索的時(shí)候還是要稍微花幾分鐘的,不過(guò)還是非常簡(jiǎn)單啊大家隨便看看應(yīng)該就可以了解的很透徹了~感興的同學(xué)的歡迎 follow 我的「Github」下載「項(xiàng)目工程」準(zhǔn)備繼續(xù)去玩 Screeps 嘍~
原文鏈接:https://snatix.com/2018/07/07/022-constructing-a-fractal/
本文由 sNatic 發(fā)布于『大喵的新窩』 轉(zhuǎn)載請(qǐng)保留本申明
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。