學習Unity(10)利用粒子系統(tǒng)制作光環(huán)

粒子系統(tǒng)概述

根據(jù)官方文檔的解釋,粒子系統(tǒng)管理大量既小又簡單的圖片或網(wǎng)格(mesh)一起運動赛不,組成一個整體的效果睬辐。比如利用粒子系統(tǒng)可以制作出煙霧效果,每一個粒子是一個微小的煙云的圖片症汹,成千上萬個這樣的粒子就組成了一整塊煙霧的效果硫朦,用普通的方法就很難做出煙霧的效果。

粒子系統(tǒng)的重要屬性

  • 生命周期lifetime:每一個粒子都有一個生命周期背镇,表示粒子被發(fā)射(emit)出來以后能存活多長時間咬展。你可以在Inspector中設(shè)置Start lifetime來設(shè)置粒子的生命周期:


    Inspector中設(shè)置lifetime

之所以叫start是因為你設(shè)置的只是粒子默認的生命周期,在運行過程中l(wèi)ifetime還有可能被腳本改變瞒斩。

  • 發(fā)射速率emission rate:每秒鐘發(fā)射多少個粒子破婆。你可以在Inspector中找到Emission模塊的rate over time中設(shè)置發(fā)射速率。

實際發(fā)射粒子的時機有一定的隨機性济瓢,不一定是間隔均勻地發(fā)射荠割。

以上兩個屬性描述的是整個粒子系統(tǒng)的狀態(tài)。我們還可以控制單個粒子的樣式和行為旺矾。

  • 粒子個體的屬性:速度矢量蔑鹦、顏色、大小箕宙、朝向等嚎朽。有一些屬性除了可以設(shè)置常量值以外,還可以設(shè)置成隨著時間變化的值柬帕,或者在一定范圍內(nèi)隨機的值哟忍。你只需要點擊輸入框右邊的下拉三角按鈕,就可以改變設(shè)置方式陷寝。

由于整個粒子系統(tǒng)有很多的屬性可以自定義锅很,因此Unity將它劃分成了很多個模塊(Particle System modules),方便查找凤跑。這是粒子系統(tǒng)模塊的參考文檔爆安。

項目概述

這個項目制作一個簡單的粒子光環(huán),光環(huán)中的粒子緩慢移動仔引,看起來像太陽系的小行星帶扔仓。效果盡量類似http://i-remember.fr/en。要實現(xiàn)這個效果咖耘,需要使用腳本來控制每一個粒子翘簇。

效果圖

在自己的電腦上運行!

我的github下載項目資源儿倒,將所有文件放進你的項目的Assets文件夾(如果有重復(fù)則覆蓋)版保,然后在Unity3D中雙擊“hw9”,就可以運行了!

代碼

ParticleStatus 用于記錄某個粒子的狀態(tài):

public class ParticleStatus {
    public float radius = 0f, angle = 0f, time = 0f, radiusChange = 0.02f; // 粒子軌道變化范圍;  
    public ParticleStatus(float radius, float angle, float time, float radiusChange)  
    {  
        this.radius = radius;   // 半徑  
        this.angle = angle;     // 角度
        this.time = time;       // 變化的進度條彻犁,用于軌道半徑的變化
        radiusChange = this.radiusChange;       // 軌道半徑的變化程度
    }  
}

ParticleHalo控制一個含有粒子系統(tǒng)的對象蹈垢,生成一個光環(huán):

using UnityEngine;

public class ParticleHalo : MonoBehaviour
{

    private ParticleSystem particleSys;  // 粒子系統(tǒng)組件
    private ParticleSystem.Particle[] particleArr;  // 粒子數(shù)組  
    private ParticleStatus[] StatusArr; // 記錄粒子狀態(tài)的數(shù)組
    public int particleNum = 10000; // 粒子數(shù)量
    public float minRadius = 8.0f; // 光環(huán)最小半徑
    public float maxRadius = 12.0f; // 光環(huán)最大半徑
    public float maxRadiusChange = 0.02f; // 粒子軌道變化的平均值
    public bool clockwise = true;  // 光環(huán)是否順時針旋轉(zhuǎn)
    public float rotateSpeed = 0.3f;  // 光環(huán)旋轉(zhuǎn)速度
    public int speedLevel = 5; // 速度有多少個層次
    private NormalDistribution normalGenerator; // 高斯分布生成器
    public Gradient colorGradient;  // 控制粒子的透明度


    void Start()
    {
        particleSys = GetComponent<ParticleSystem>();
        particleArr = new ParticleSystem.Particle[particleNum];
        StatusArr = new ParticleStatus[particleNum];

        var ma = particleSys.main;  // 通過ma來設(shè)置粒子系統(tǒng)的maxParticles
        ma.maxParticles = particleNum;

        particleSys.Emit(particleNum);  // 同時發(fā)射particleNum個粒子
        particleSys.GetParticles(particleArr);  // 將發(fā)射的粒子存在particleArr數(shù)組中
        normalGenerator = new NormalDistribution(); // 初始化高斯分布生成器

        // 初始化梯度顏色控制器  
        GradientAlphaKey[] alphaKeys = new GradientAlphaKey[5];
        alphaKeys[0].time = 0.0f; alphaKeys[0].alpha = 1.0f;
        alphaKeys[1].time = 0.4f; alphaKeys[1].alpha = 0.4f;
        alphaKeys[2].time = 0.6f; alphaKeys[2].alpha = 1.0f;
        alphaKeys[3].time = 0.9f; alphaKeys[3].alpha = 0.4f;
        alphaKeys[4].time = 1.0f; alphaKeys[4].alpha = 0.9f;
        GradientColorKey[] colorKeys = new GradientColorKey[2];
        colorKeys[0].time = 0.0f; colorKeys[0].color = Color.white;
        colorKeys[1].time = 1.0f; colorKeys[1].color = Color.white;
        colorGradient.SetKeys(colorKeys, alphaKeys);

        initParticle();
    }

    void initParticle()
    {
        for (int i = 0; i < particleNum; i++)
        {
            // 普通的隨機半徑生成
            // float midRadius = (maxRadius + minRadius) / 2;
            // float minRate = Random.Range(1.0f, midRadius / minRadius);
            // float maxRate = Random.Range(midRadius / maxRadius, 1.0f);
            // float radius = Random.Range(minRadius * minRate, maxRadius * maxRate);

            // 使用高斯分布生成半徑, 均值為midRadius袖裕,標準差為0.7
            float midRadius = (maxRadius + minRadius) / 2;
            float radius = (float)normalGenerator.NextGaussian(midRadius, 0.7);

            float angle = Random.Range(0.0f, 360.0f);
            float theta = angle / 180 * Mathf.PI;
            float time = Random.Range(0.0f, 360.0f);    // 給粒子生成一個隨機的初始進度
            float radiusChange = Random.Range(0.0f, maxRadiusChange);   // 隨機生成一個軌道變化大小
            StatusArr[i] = new ParticleStatus(radius, angle, time, radiusChange);
            particleArr[i].position = computePos(radius, theta);
        }
        particleSys.SetParticles(particleArr, particleArr.Length);
    }

    Vector3 computePos(float radius, float theta)
    {
        return new Vector3(radius * Mathf.Cos(theta), 0f, radius * Mathf.Sin(theta));
    }

    void Update()
    {
        for (int i = 0; i < particleNum; i++)
        {
            // 將所有粒子根據(jù)下標i曹抬,給5個不同的速度,分別是rotateSpeed的1/5急鳄、2/5……5/5
            if (!clockwise)
            {
                StatusArr[i].angle += (i % speedLevel + 1) * (rotateSpeed / speedLevel);
            }
            else
            {
                StatusArr[i].angle -= (i % speedLevel + 1) * (rotateSpeed / speedLevel);
            }

            // angle range guarantee
            StatusArr[i].angle = (360.0f + StatusArr[i].angle) % 360.0f;
            float theta = StatusArr[i].angle / 180 * Mathf.PI;

            StatusArr[i].time += Time.deltaTime;    // 增加粒子的進度
            StatusArr[i].radius += Mathf.PingPong(StatusArr[i].time / maxRadius / maxRadius, StatusArr[i].radiusChange) - StatusArr[i].radiusChange / 2.0f; // 根據(jù)粒子的進度谤民,給粒子的半徑賦予不同的值,這個值在0與StatusArr[i].radiusChange之間來回擺動

            particleArr[i].position = computePos(StatusArr[i].radius, theta);

            particleArr[i].color = colorGradient.Evaluate(StatusArr[i].angle / 360.0f); // 根據(jù)粒子的angle疾宏,給粒子賦予不同的透明度(顏色)张足,使某一些角度上的粒子暗一些
        }

        particleSys.SetParticles(particleArr, particleArr.Length);
    }
}

NormalDistribution 用于產(chǎn)生正態(tài)分布的隨機數(shù),原理是Marsaglia polar method

using System;

public class NormalDistribution {
    // use Marsaglia polar method to generate normal distribution
    private bool _hasDeviate;
    private double _storedDeviate;
    private readonly Random _random;

    public NormalDistribution(Random random = null)
    {
        _random = random ?? new Random();
    }

    public double NextGaussian(double mu = 0, double sigma = 1)
    {
        if (sigma <= 0)
            throw new ArgumentOutOfRangeException("sigma", "Must be greater than zero.");

        if (_hasDeviate)
        {
            _hasDeviate = false;
            return _storedDeviate*sigma + mu;
        }

        double v1, v2, rSquared;
        do
        {
            // two random values between -1.0 and 1.0
            v1 = 2*_random.NextDouble() - 1;
            v2 = 2*_random.NextDouble() - 1;
            rSquared = v1*v1 + v2*v2;
            // ensure within the unit circle
        } while (rSquared >= 1 || rSquared == 0);

        // calculate polar tranformation for each deviate
        var polar = Math.Sqrt(-2*Math.Log(rSquared)/rSquared);
        // store first deviate
        _storedDeviate = v2*polar;
        _hasDeviate = true;
        // return second deviate
        return v1*polar*sigma + mu;
    }
}

代碼意義已經(jīng)在注釋中解釋


兩層光環(huán)

為了做出兩層光環(huán)坎藐,只需要將同一份腳本掛載在兩個具有Particle System的對象上为牍,然后在Inspector中調(diào)整一下參數(shù),就可以實現(xiàn)內(nèi)層逆時針岩馍、外層順時針碉咆、內(nèi)層快、外層慢蛀恩、內(nèi)層稠密疫铜、外層稀疏的效果了。

外層參數(shù)

內(nèi)層參數(shù)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末双谆,一起剝皮案震驚了整個濱河市壳咕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌顽馋,老刑警劉巖谓厘,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寸谜,居然都是意外死亡竟稳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門程帕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來住练,“玉大人地啰,你說我怎么就攤上這事愁拭。” “怎么了亏吝?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵岭埠,是天一觀的道長。 經(jīng)常有香客問我,道長惜论,這世上最難降的妖魔是什么许赃? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮馆类,結(jié)果婚禮上混聊,老公的妹妹穿的比我還像新娘。我一直安慰自己乾巧,他們只是感情好句喜,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沟于,像睡著了一般咳胃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上旷太,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天展懈,我揣著相機與錄音,去河邊找鬼供璧。 笑死存崖,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的睡毒。 我是一名探鬼主播金句,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吕嘀!你這毒婦竟也來了违寞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤偶房,失蹤者是張志新(化名)和其女友劉穎趁曼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棕洋,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡挡闰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掰盘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摄悯。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖愧捕,靈堂內(nèi)的尸體忽然破棺而出奢驯,到底是詐尸還是另有隱情,我是刑警寧澤次绘,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布瘪阁,位于F島的核電站撒遣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏管跺。R本人自食惡果不足惜义黎,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望豁跑。 院中可真熱鬧廉涕,春花似錦、人聲如沸艇拍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淑倾。三九已至馏鹤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間娇哆,已是汗流浹背湃累。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碍讨,地道東北人治力。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像勃黍,于是被迫代替她去往敵國和親宵统。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容