學習Unity(10)利用粒子系統制作光環

粒子系統概述

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

粒子系統的重要屬性

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


    Inspector中設置lifetime

之所以叫start是因為你設置的只是粒子默認的生命周期,在運行過程中lifetime還有可能被腳本改變。

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

實際發射粒子的時機有一定的隨機性,不一定是間隔均勻地發射。

以上兩個屬性描述的是整個粒子系統的狀態。我們還可以控制單個粒子的樣式和行為。

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

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

項目概述

這個項目制作一個簡單的粒子光環,光環中的粒子緩慢移動,看起來像太陽系的小行星帶。效果盡量類似http://i-remember.fr/en。要實現這個效果,需要使用腳本來控制每一個粒子。

效果圖

在自己的電腦上運行!

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

代碼

ParticleStatus 用于記錄某個粒子的狀態:

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控制一個含有粒子系統的對象,生成一個光環:

using UnityEngine;

public class ParticleHalo : MonoBehaviour
{

    private ParticleSystem particleSys;  // 粒子系統組件
    private ParticleSystem.Particle[] particleArr;  // 粒子數組  
    private ParticleStatus[] StatusArr; // 記錄粒子狀態的數組
    public int particleNum = 10000; // 粒子數量
    public float minRadius = 8.0f; // 光環最小半徑
    public float maxRadius = 12.0f; // 光環最大半徑
    public float maxRadiusChange = 0.02f; // 粒子軌道變化的平均值
    public bool clockwise = true;  // 光環是否順時針旋轉
    public float rotateSpeed = 0.3f;  // 光環旋轉速度
    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來設置粒子系統的maxParticles
        ma.maxParticles = particleNum;

        particleSys.Emit(particleNum);  // 同時發射particleNum個粒子
        particleSys.GetParticles(particleArr);  // 將發射的粒子存在particleArr數組中
        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++)
        {
            // 將所有粒子根據下標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; // 根據粒子的進度,給粒子的半徑賦予不同的值,這個值在0與StatusArr[i].radiusChange之間來回擺動

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

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

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

NormalDistribution 用于產生正態分布的隨機數,原理是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;
    }
}

代碼意義已經在注釋中解釋


兩層光環

為了做出兩層光環,只需要將同一份腳本掛載在兩個具有Particle System的對象上,然后在Inspector中調整一下參數,就可以實現內層逆時針、外層順時針、內層快、外層慢、內層稠密、外層稀疏的效果了。

外層參數

內層參數
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容