【Unity3d學習】粒子光環的製作——粒子編程初體驗

寫在前面

實驗步驟

首先回顧粒子海洋的製作過程:

  1. 添加空對象並且添加粒子系統的部件
  2. 簡單調整一下粒子部件中的粒子材料(選個默認粒子材料就好,不要讓其爲空)
  3. 寫代碼編程,掛到空對象上。並且將粒子系統部件拖入代碼的公共變量中去。
  4. 添加顏色漸變部件,手動修改顏色
  5. 。。。。

所以其實對於粒子系統部件,我們不需要太多的操作,大部分的變化都是在代碼中完成的,只要清楚各個屬性對應的api。

粒子光環製作

首先添加一個空對象,並且添加部件ParticleSystem,然後簡單設置一下屬性:
在這裏插入圖片描述
還有發射器Render的屬性,這裏最重要就是選擇材料!選擇材料!選擇材料!不然的話就會出現一堆粉紫色的小方塊,不免太難看了:
在這裏插入圖片描述
至於其他的屬性可以先不用管,我們開始編程:

粒子光環的屬性

	public ParticleSystem myparticleSystem;
    private ParticleSystem.Particle[] particleArray;
    private SingleParticle[] points;
    public Gradient grad;
    int count = 1000;
    public float size = 0.5f;
    public float minRadius = 3.0f;
    public float maxRadius = 6.0f;
    public bool rotate_way = false; // 決定圈擴大還是縮小
    private float rotate_speed = -1; // 顏色旋轉速度(正負代表方向)
    public float speed = 0.5f; // 速度參數
    private float time = 0;
  • ParticleSystem myparticleSystem
    首先最重要肯定是粒子系統本身,沒有這個類又談何粒子編程呢?
  • ParticleSystem.Particle[] particleArray
    其次是粒子數組,保存了每一個粒子的狀態,這裏需要規定一個數量count,也就是數組的大小,我設置爲了1000.
  • SingleParticle[] points
    由於是光環,所以每個粒子的位置肯定需要一定規律地排序,而不是隨機亂跑,所以這裏記錄了每個粒子對於整個光環的狀態,兩個最重要的屬性角度與半徑,因爲每個粒子都是繞中心點運動,所以運動軌跡會有一個半徑,運動到什麼地方呢?就需要角度來記錄。這裏用到自己定義的一個類SingleParticle,稍後會說明。
  • Gradient grad
    顏色漸變器,Unity自帶的漸變器,只要選定顏色變化的區間就能在有一段漸變的夜色。
  • 還有一些比較雜的變量屬性:粒子大小,光環內徑外徑,轉圈速度,旋轉方向等。

粒子的位置屬性:

public class SingleParticle {
    public float angle;
    public float radius;

    private float x = 0.0f;
    private float y = 0.0f;


    public void CalPosition() {
        float temp = angle / 180.0f * Mathf.PI;
        y = radius * Mathf.Sin(temp);
        x = radius * Mathf.Cos(temp);
    }
    public SingleParticle(float angle, float radius) {
        this.angle = angle;
        this.radius = radius;
    }

    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }
}

主要是靠角度和半徑來估算粒子所在的xy平面的面積,xy座標就是靠基本的數學三角函數來計算出來。其中定義了一些外面能夠調用的方法。

粒子系統初始化

在Start函數中,首先設置粒子系統的一些基本屬性,如粒子數目等,這些需要在一開始就規定好並且不再改變。

void Start () {
        // myparticleSystem = this.GetComponent<ParticleSystem>();
        particleArray = new ParticleSystem.Particle[count];
        points = new SingleParticle[count];
        var m = myparticleSystem.main;
        m.startSpeed = 0;
        m.startSize = size;
        m.maxParticles = count;
        myparticleSystem.Emit(count);
        myparticleSystem.GetParticles(particleArray);

        Init();
    }

注意到代碼第一行,對於粒子系統的賦值,我們可以直接通過GetComponent的方法來獲取掛載在同一對象上的部件,但是這裏我們用手動拖的方式,給腳本得公共變量賦值,好像更有體驗感一點
然後就是各種初始化,新建一個粒子位置的數組,由於我們數量設置了1000,所以數組長度就是1000,除了這個數組外,還有粒子系統中的真正的粒子數組,設定size、最大粒子數、發射器發射數量,設置完之後還需要使用粒子系統Get一下,才能真正設置成功了。
這裏有一個點值得注意的,就是舊版本里面通常使用ParticleSystem.startSize來設置,但是在新版中,需要先獲取用一個變量獲取ParticleSystem.main,再對main的startSize、maxParticles 進行設置。

其中,Init的函數就是作爲粒子位置的初始化:

private void Init() {
        int i;
        for (i = 0; i < count; i++) {
            float midRadius = (minRadius + maxRadius) / 2.0f;
            float minRate = Random.Range(1.0f, midRadius / minRadius);
            float maxRate = Random.Range(midRadius / maxRadius, 1.0f);
            float radius = Random.Range(minRadius * minRate, maxRadius * maxRate);
            float angle = Random.Range(0.0f, 360.0f);
            points[i] = new SingleParticle(angle, radius);
            points[i].CalPosition();
            particleArray[i].position = new Vector3(points[i].getX(), points[i].getY(), 0f);
        }

        myparticleSystem.SetParticles(particleArray, particleArray.Length);
    }

通過內徑和外徑,中間劃分一個分截,粒子分別在這兩個部分中隨機分佈,角度則是360度隨機。

然後就是每次Update時候的變化了:

void Update () {
        int i;
        int level = 10;
        for (i = 0; i < count; i++) {

            if (i % level < 3 || i % level > 6)
            {
                points[i].angle -= rotate_speed * (i % level + 1) * speed;
            } else {
                points[i].angle += rotate_speed * (i % level + 1) * speed;
            }

            points[i].angle = (points[i].angle + 360.0f) % 360.0f; 
            points[i].CalPosition();
            float value = Time.realtimeSinceStartup % 1.0f;
            value -= rotate_speed * points [i].angle /360.0f;
            while (value > 1)
                value--;
            while (value < 0)
                value ++;
            particleArray[i].startColor = grad.Evaluate(value);
            particleArray[i].position = new Vector3(points[i].getX(), points[i].getY(), 0.0f);
        }
        myparticleSystem.SetParticles(particleArray, particleArray.Length);
    }

每次更新都需要循環遍歷1000個粒子,對於每個粒子的狀態都做一次改變,像角度顏色等。具體操作如下

  • 利用取模的方式將1000個粒子分爲10層,每一層轉動的角度(相當於速度)都略微不一樣,然後又將整體分成3部分:第一部分和第三部分是同一個方向旋轉,第二部分是另一個方向旋轉。使得整體的轉動效果不太單一。當然最後需要對增加後的角度取模,以免溢出360度,或者小於0度。最後利用粒子位置的類中的方法,算出xy座標。
  • 然後是顏色的改變,首先我們需要利用漸變器進行顏色的調整,其實這一步也依然是可以通過代碼來調整的,但是跟前面說的一樣,~~增強體驗感(其實是麻煩)~~使用手動調整的方法。首先看下漸變器的基本操作:
    在這裏插入圖片描述
    這個就是主體部分,可以看到一個類似進度條的東西,上下都有一些點,其中上方的點表示透明度的漸變(可以理解爲亮度的變化),下方的點是顏色的選擇,添加點只需要在對應空白的地方點擊一下,刪除點就需要點中某個點按刪除鍵即可。
    然後需要更改顏色就需要點擊下方的color欄,然後選擇適合的顏色,全部選擇完畢之後就可以看到兩個顏色點之間自動漸變。透明度也類似。
    調整好之後就可以通過代碼來選擇其中的顏色,從0~1就是開始的顏色到最後的顏色了。
    所以我們的想法是不僅顏色隨着角度的變化而變化,而且要隨着時間的變化而變化,所以需要對時間進行一個估算,以上代碼中:
    float value = Time.realtimeSinceStartup % 1.0f;
    value -= rotate_speed * points [i].angle /360.0f;
    while (value > 1)
        value--;
    while (value < 0)
        value ++;
    
    這部分就是來計算的,首先利用實時時間,也就是程序運行的時間模上一個浮點數(注意是模浮點數,這樣得出來的結果纔會是浮點數),得到一個區間內的數,這裏模1.0,說明顏色變化會在一秒內跑一圈,如果是模比一大的數,還需要進行歸一化處理,也就是模多少就除以多少。
    然後還需要加上當前角度歸一化的結果,最後利用一個循環加減令其最終結果在0~1之間。用Gradient漸變器的Evaluate獲取對應的顏色
  • 最後,最關鍵的一步當然就是將這些計算出來的顏色,和位置設置到粒子本身中去,SetParticles函數就是這裏使用的。

好了大功告成之後看看效果吧~

基本效果

在這裏插入圖片描述
就看到光圈在轉呀轉~ 轉呀轉~
其實仔細看的話還是能夠看到有的粒子慢有的粒子快,還能分出順時針和逆時針變化。

改進版本

既然光轉圈不過癮,那麼就來個放大縮小吧,轉着轉着就放大,有轉着轉着縮小,總比光轉有趣多了吧。
那麼就需要先利用幾個變量來幫助我們:

public bool rotate_way = false; // 決定圈擴大還是縮小
private float rotate_speed = -1; // 顏色旋轉速度(正負代表方向)
private float time = 0;

這是之前就定義了可是沒有怎麼用到的變量,首先需要知道什麼是否放大,什麼時候縮小,其次旋轉的速度是可變的,就不會呆板的轉圈,有必要還可以反方向轉動。至於時間的話,作爲一個計時器用,決定了放大縮小的時間。

好下面就來寫代碼吧!
在Update的開頭加入以下代碼:

time += Time.deltaTime;
if (time < 10) {
    if (time < 5) {
        rotate_way = false;
        rotate_speed += 0.01f;
    }
    else {
        rotate_way = true;
        rotate_speed -= 0.01f;
    }
} else {
    time = 0;
    rotate_speed = -1;
}

總時長爲10,然後分成兩段,一段是放大,一段是縮小的,在此期間,旋轉速度也做一點變化,隨着時間增加或者減少,但是這個沒有固定的變化規律,也就是說不一定增加的量和減少的量能夠統一(值得改進),所以在每段時間結束的時候都需要進行一個reset處理,也就是將旋轉速度迴歸原始狀態,至於光環的大小就不一定了。。。
在對粒子屬性的循環中,加入以下代碼:

if (i % level > 5) {
    float tmp = rotate_way? 1 : -1;
    points[i].radius += tmp * 0.05f;
}
if (i % level <= 5) {
    float tmp = rotate_way? 1 : -1;
    points[i].radius += tmp * 0.052f;
}

依然是將粒子分成兩部分(還是熟悉的配方,不知道爲什麼這麼執迷於分層),然後一部分圈半徑改變的速度快一點,一部分改變得慢一點,其實差別不大,因爲差別大了的話會出現斷層的現象。

添加完之後看看效果:
在這裏插入圖片描述
感覺好很多了,不過這裏有一點問題就是圈的擴大縮小過程是不對稱的,而且因爲是Update函數中計時執行,所以不同電腦也會有變化細節,但是總體的變化是一定的,就是放大縮小,然後轉~~。

添加粒子軌跡

做到這裏感覺還是不太好看,於是就自己東搞搞西搞搞,然後想到了軌跡這個東西,也就是說,讓我們的粒子加上一條尾巴,看起來會不會好看一點呢?
首先在粒子系統的Render屬性裏面,添加軌跡材料(別忘了設置,不然就是很醜的粉紫色長條):
在這裏插入圖片描述
然後再軌跡的選項裏,調整一下參數:
在這裏插入圖片描述
重點調整一下軌跡與粒子的大小比例,還有軌跡壽命,也就是軌跡能拖多長。

設置好之後就運行看看效果吧。
在這裏插入圖片描述
是不是看起來酷炫多了!!

最後總結一下,其實這個編程有一些bug,也就是之前提到了變化過程不是對稱的,也就是有的變化不可逆,可能變着變着就和原來的有很大區別了,這也是需要不斷調整參數,不斷研究的一點,不過鑑於粒子編程太多可鑽研的了,同時也有大量的資源、插件可使用,這一點也不必太擔心吧。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章