溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Catlike學習筆記(1.4)-使用Unity構建分形

發(fā)布時間:2020-06-27 22:34:15 來源:網絡 閱讀:400 作者:snatic 欄目:游戲開發(fā)

又兩個星期沒寫文章了,主要是沉迷 Screeps 這個游戲,真的是太好玩了導致我這兩個禮拜 Github 小綠點幾乎天天刷。其實想開一個新坑大概把自己寫 AI 的心路歷程記錄下,不過覺得因為要消耗太多時間暫時決定先不開,準備把過程中遇到的有趣的算法問題記錄下就好了。言歸正傳今天來到「構建分形」 這篇文章。比較簡單主要介紹遞歸的思想。我們就迅速一些,因為我還要繼續(xù)沉迷 Screeps,因為還要繼續(xù)學習嗯。。。再貼一次「原文鏈接」吧。。

PART 1 概述

「分形」這種東西,隨便了解一下大概就能想到作者要用遞歸的方法來完成。所以這一篇教程性質的文章主要是講在 Unity 里使用遞歸完成一些事情。鑒于大家應該上大學的時候隨隨便便上個課就差不多了解遞歸這種基本概念,因此我們就進展快一些~大概要完成以下事情:

  • 使用遞歸生成一大堆立方體和球體
  • 整理一下使其變成分形
  • 美化一下

PART 2 遞歸生成

首先我們要在 MonoBehaviour 里面生成一個立方體,需要如下代碼。

public class Fractal : MonoBehaviour
{
    public Mesh Mesh;
    public Material Material;

    // Use this for initialization
    void Start ()
    {
        gameObject.AddComponent<MeshFilter>().mesh = Mesh;
        gameObject.AddComponent<MeshRenderer>().material = Material;
    }
}

非常簡單,然后在場景里新建一個 GameObject 再掛上這個腳本,拖一些默認的 mesh 和 material 上去就好了,運行發(fā)現 OK 完美成功。那么說好的遞歸呢?非常簡單,我們只需要在Start()里面創(chuàng)建一個新的 GameObject 再給他掛上這個 MonoBehaviour 就好。當然要記得限制遞歸的次數不然要爆炸~每次遞歸都記得調整子物體的位置和大小,最后設置一下遞歸深度這樣就 OK 了,代碼如下

public class Fractal : MonoBehaviour
{
    public Mesh Mesh;
    public Material Material;

    public int Depth;

    // Use this for initialization
    void Start ()
    {
        gameObject.AddComponent<MeshFilter>().mesh = Mesh;
        gameObject.AddComponent<MeshRenderer>().material = Material;
        if (Depth > 0)
        {
            new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this);
        }

    }

    public void Initialize(Fractal parent)
    {
        Mesh = parent.Mesh;
        Material = parent.Material;
        Depth = parent.Depth - 1;
        transform.SetParent(parent.transform);
    }
}

那么這樣就可以生成一大堆疊在一起的立方體了。。接下來的目標就是對這段代碼修修補補讓這些立方體組成看起來像是分形的樣子。

PART 3 分形

首先我們嘗試讓每個立方體在除了底面的每一面生成一個比他小一半的立方體。首先需要讓Initialize()接收位置和方向以及大小的參數。

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;
}

非常簡單,然后在每個立方體執(zhí)行Start()的時候初始化 5 個小立方體,之所以我們需要設置小立方體的朝向是為了小立方體朝著其 Z 軸方向 (0, 0, 1) 生長,而不用考慮每次遞歸的時候的生長方向。代碼如下

private void Start ()
{
    gameObject.AddComponent<MeshFilter>().mesh = Mesh;
    gameObject.AddComponent<MeshRenderer>().material = Material;
    if (Depth <= 0) return;
    var posOffset = Size + Size / 2f;
    new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.left * posOffset, new Vector3(0, -90, 0));
    new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.right * posOffset, new Vector3(0, 90, 0));
    new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.up * posOffset, new Vector3(-90, 0, 0));
    new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.down * posOffset, new Vector3(90, 0, 0));
    new GameObject("Fractal Child").AddComponent<Fractal>().Initialize(this, Size, Vector3.forward * posOffset, new Vector3(0, 0, 0));
}

最后在場景里設置下Scale = (0.5, 0.5, 0.5)size = 0.5f Depth = 4,再設置初始物體 Z 向上,即(-90, 0, 0)運行一下效果如圖所示還不錯~

Catlike學習筆記(1.4)-使用Unity構建分形

嗯感覺還不錯~再多設置一下變成 6 呢?我的 Macbook Pro 風扇開始呼呼的轉。。。

Catlike學習筆記(1.4)-使用Unity構建分形

接下來稍微重構下代碼~之前的太丑了。我們把五行長得差不多的創(chuàng)建子物體的代碼提取一下關鍵參數,完整版如下:

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<MeshFilter>().mesh = Mesh;
        gameObject.AddComponent<MeshRenderer>().material = Material;
        if (Depth <= 0) return;
        var posOffset = Size + Size / 2f;
        for (int i = 0; i < 5; i++)
        {
            new GameObject("Fractal Child").AddComponent<Fractal>().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;
    }
}

PART 4 美化

感覺作者寫的美化一點都不美~不過我們還是按照教程順手做一些換個 Mesh 啊隨機旋轉啦,生成機率之類的事情吧也算是有個交代。

隨機 Mesh

這個非常簡單了我們把 Mesh 這個字段擴充成一個數組。然后在初始化MeshFilter的地方從里面隨機一個出來,像下面這樣。然后在拖一些 Mesh 進去。

public class Fractal : MonoBehaviour
{
    public Mesh[] Mesh;
    public Material Material;
    ...
    private void Start ()
    {
        gameObject.AddComponent<MeshFilter>().mesh = Mesh[Random.Range(0, Mesh.Length)];
        gameObject.AddComponent<MeshRenderer>().material = Material;
        ...
    }
    ...
}

這樣就可以了~運行起來每次都不太一樣。。圖就不截了變化并不大大家應該可以想象出來~

生成機率

也很簡單,添加一個機率然后在生成的地方每次隨機一下,隨到了就生成。。

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<Fractal>().Initialize(this, Size, _positions[i] * posOffset, _rotations[i] * 90);
            }
        }
    }

    public void Initialize(Fractal parent, float size, Vector3 pos, Vector3 rot)
    {
        ...
        Probability = parent.Probability;
        ...
    }
}

把機率調成 0.75 以后生成效果如下圖(跟上一條隨機 Mesh 一起展示了)

Catlike學習筆記(1.4)-使用Unity構建分形

旋轉起來吧

接下來要做的就是讓這些東西全部動起來。。。并且以隨機的速度。。嗯我已經可以想像出來大概是怎樣的鬼畜場景了,嘗試實現一下的話首先就是加一個最大旋轉速度。然后在Update()里面隨機好速度然后做一次旋轉就好了~

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);
    }

    ...
}

運行一下發(fā)現似乎總是在原地抖動的樣子。。。一定是我們速度變換的頻率太高了所以最終結果會趨近于原地不動,稍微限制一下加點隨機。。

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;
        ...
    }
}

哇畫面真的是太詭異了。。。

Catlike學習筆記(1.4)-使用Unity構建分形

PART 5 總結

好的這一篇文章就這樣成功的 水過去了 完成了~這一篇大概上就是遞歸的使用方法吧~其實沒怎么看原文自己摸索的時候還是要稍微花幾分鐘的,不過還是非常簡單啊大家隨便看看應該就可以了解的很透徹了~感興的同學的歡迎 follow 我的「Github」下載「項目工程」準備繼續(xù)去玩 Screeps 嘍~


原文鏈接:https://snatix.com/2018/07/07/022-constructing-a-fractal/

本文由 sNatic 發(fā)布于『大喵的新窩』 轉載請保留本申明

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI