粒子系統概述
根據官方文檔的解釋,粒子系統管理大量既小又簡單的圖片或網格(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中調整一下參數,就可以實現內層逆時針、外層順時針、內層快、外層慢、內層稠密、外層稀疏的效果了。