1. 程式人生 > >unity3d粒子系統之行星帶

unity3d粒子系統之行星帶

Unity3d實戰之粒子系統

選題要求

實現效果

  • 靜態圖
    這裡寫圖片描述
  • 動態圖
    這裡寫圖片描述

知識準備

粒子系統有許多大模組,比如最常用的有初始化模組、發射模組、粒子群形狀模組,顏色隨存活時間、速度變化的模組等。這裡重點學習了一下最重要的初始化模組中各個引數的作用。

引數 功能
持續時間(Duration) 粒子系統發射粒子的持續時間
迴圈(Looping) 粒子系統是否迴圈
預熱(Prewarm) 當looping開啟時,才能啟動預熱(Prewarm),遊戲開始時粒子已經發射了一個週期
初始延遲(Start Delay) 粒子系統發射粒子之前的延遲。注意在prewarm(預熱)啟用下不能使用此項
初始生命(Start Lifetime) 以秒為單位,粒子存活數量
初始速度(Start Speed) 粒子發射時的速度
初始大小(Start Size) 粒子發射時的大小
初始旋轉(Start Rotation) 粒子發射時的旋轉值
初始顏色(Start Color) 粒子發射時的顏色
重力修改器(Gravity Modifier) 粒子在發射時受到的重力影響
繼承速度(Inherit Velocity) 控制粒子速率的因素將繼承自粒子系統的移動(對於移動中的粒子系統)
模擬空間(Simulation Space) 粒子系統在自身座標系還是世界座標系
喚醒時播放(Play On Awake) 如果啟用粒子系統當在建立時,自動開始播放
最大粒子數(Max Particles) 粒子發射的最大數量

思路確定

http://i-remember.fr/en 上的例子進行了一定的學習後,最後打算做一些改進和創新,利用其原理實現一個衛星帶,再用這個衛星帶實現一個衛星帶圍繞行星轉的效果。詳細過程請見實現步驟。

實現步驟

  • 第一步,新建一個空物件StarRing,注意將其位置設定成(0,0,0),然後新增一個粒子系統的元件,叫ParticleSystem,組建裡面的各種引數先不用改動,推薦通過程式碼來修改
    ,可以批量修改,方便快捷而且有利於團隊合作、給別人參考等方面。如果直接在元件裡改,其他人每次都需要載入對應的場景才看得到修改的引數,而且粒子系統引數眾多,這樣也不能很直觀地看出修改了哪些引數。因此還是推薦寫在程式碼裡。
    這裡寫圖片描述
    這裡寫圖片描述
  • 第二步,從上一步可以看出,最開始預設的粒子似乎出現了材質丟失的情況,可能是因為新老版本材質球不相容的原因導致的,也正好,我們可以自己找一些想要的材質,將其拖到承載ParticleSystem的物件上,就完成了對粒子的貼圖。我在網上找了一張土壤表面的材質,大致充當一些小衛星的樣子(拉近看可能效果沒那麼好)
    這裡寫圖片描述
  • 第三步,為了匹配星空的背景,原來這樣的預設背景肯定是不合適的,無法營造出那種效果。因此要將天空盒改成純黑色的模仿宇宙中的光吞噬。更改步驟是上面選單欄Window->Lighting->Settings->Skybox Material的材質改成黑色的即可,這裡我是改成Defualt Material。
    這裡寫圖片描述
    這裡寫圖片描述
  • 第四步,建立一個球體Star,貼上木星貼圖,充當大行星,位置設定在(0,0,0),位於粒子群中心,並讓其自轉,目的是模仿木星環圍繞著木星轉的情景,這裡就沒有設定木星的公轉了,涉及到鏡頭跟隨等其他一系列操作,不是這次作業的主要重點。
    這裡寫圖片描述
  • 第五步,新增一個c#指令碼,掛載到Star上,來控制Star的自轉。這個程式碼只需要在update中新增如下程式碼,即可實現自轉,相關原理有在之前的太陽系作業中學習過,這裡就算是鞏固複習了。
this.transform.Rotate(Vector3.up * 30 * Time.deltaTime);
  • 第六步,新增一個c#指令碼,用來掛載到StarRing上,開始主要程式碼的編寫,實現對粒子系統的控制。
    這裡寫圖片描述
  • 第七步,為方便起見,建立一個類,來記錄每個粒子所應該具有的基本資訊。這樣有利於提升程式碼美觀度可讀性,也使得可擴充套件性大大增強,如果往後需要給每個粒子加基本屬性資訊的話,只需要在類中加一個成員,不用大幅度改動重要程式碼。
public class ParticleInfo
{
    public float radius = 0;
    public float angle = 0;
    public ParticleInfo(float radius, float angle)
    {
        this.radius = radius;   // 半徑  
        this.angle = angle;     // 角度  
    }
}
  • 第八步,確定並新增和粒子系統初始化及管理有關的成員變數。成員變數應該越簡越好,防止成員冗餘造成額外的空間開銷。這裡其實最重要的就是前面三個成員變數,分別是粒子系統、粒子陣列以及粒子資訊陣列。粒子系統就是大Boss,粒子陣列主要是用來更細緻地定義每個粒子的屬性,最後可以通過粒子陣列來設定粒子系統。而粒子資訊陣列則更多地扮演著一個對每個粒子資訊進行統一整合和管理的角色,儲存著隨機生成的粒子半徑和角度,可以用來計算每個粒子的位置,已確定粒子陣列中每個元素的position。
private ParticleSystem particleSys;  // 粒子系統  
private ParticleSystem.Particle[] particleArr;  // 粒子陣列  
private ParticleInfo[] info; // 粒子資訊陣列  

float speed = 0.25f;            // 速度    
public int count = 8000;       // 粒子數量 
  • 第九步,初始化粒子系統。主要是要申請兩個重要陣列,以及設定整個粒子系統的初始速度和最大粒子量。
void Start () {
    // 初始化粒子陣列  
    particleArr = new ParticleSystem.Particle[count];
    info = new ParticleInfo[count];

    // 初始化粒子系統  
    particleSys = this.GetComponent<ParticleSystem>();
    particleSys.loop = false;              // 取消粒子迴圈
    particleSys.startSpeed = 0;            // 設定粒子初速度      
    particleSys.maxParticles = count;      // 設定最大粒子量  
    particleSys.Emit(count);               // 發射粒子  
    particleSys.GetParticles(particleArr);

    IniAll();   // 初始化所有粒子
}
  • 第十步,初始化所有粒子。這裡主要隨機產生一些粒子的資訊,比如粒子半徑,角度,大小等。
void IniAll()
{                
    for (int i = 0; i < count; ++i)
    {   
        // 隨機每個粒子半徑,集中於平均半徑附近  
        float midRadius = 8.0f;
        float radius = Random.Range(midRadius - 2, midRadius + 2);

        // 隨機每個粒子的角度  
        float angle = Random.Range(0, 360);
        // 轉換成弧度制
        float radian = angle / 180 * Mathf.PI;

        // 隨機每個粒子的大小
        float size = Random.Range(0.01f, 0.03f);

        info[i] = new ParticleInfo(radius, angle);            

        particleArr[i].position = new Vector3(info[i].radius * Mathf.Cos(radian), 0f, info[i].radius * Mathf.Sin(radian));
        particleArr[i].size = size;            
    }
    // 通過初始化好的粒子陣列設定粒子系統
    particleSys.SetParticles(particleArr, particleArr.Length);
}
  • 最後一步!重寫update方法,每幀重新整理以實現粒子群移動的效果,對i模2是為了是粒子運動效果更加豐富,可以同時有順時針和逆時針移動的粒子。但是在真實情況中衛星帶中的小衛星旋轉方向應該都是大致一致的,不然會出大事,這裡主要是為了體驗一下粒子系統強大運算能力的魅力。
void Update()
{
    for (int i = 0; i < count; i++)
    {
        // 除以半徑是為了使速度更加多樣化
        float rotateSpeed = (speed / info[i].radius) * (i % 10 + 1);

        // 一半粒子順時針轉,一半粒子逆時針轉
        if (i % 2 == 0)
        {
            info[i].angle -= rotateSpeed;
        }
        else
        {
            info[i].angle += rotateSpeed;
        }                

        // 保證角度合法
        info[i].angle %= 360.0f;
        // 轉換成弧度制
        float radian = info[i].angle * Mathf.PI / 180;     

        particleArr[i].position = new Vector3(info[i].radius * Mathf.Cos(radian), 0f, info[i].radius * Mathf.Sin(radian));
    }
    // 通過粒子陣列設定粒子系統
    particleSys.SetParticles(particleArr, particleArr.Length);
}
  • 經歷過之前的所有方法後,我們就可以把程式碼跑起來了,內心激動,但是一看效果,賊尬,衛星帶邊緣如此明顯,看著極其不自然。
    這裡寫圖片描述
  • 於是上網搜了搜柔滑邊緣的方法,看到大神是這麼處理的。巧妙地使用比例隨機來柔滑邊緣,一看似乎很有道理,決定參照模仿一下,效果驚人。主要改動如下:
void IniAll()
{          
    float minRadius = 6.0f;  // 最小半徑  
    float maxRadius = 10.0f; // 最大半徑           
    for (int i = 0; i < count; ++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);

        ...         
    }
    // 通過初始化好的粒子陣列設定粒子系統
    particleSys.SetParticles(particleArr, particleArr.Length);
}

改後效果:
這裡寫圖片描述
- 優化,採用類似的原理,可以讓每個粒子在一個很小的位置範圍內抖動,讓畫面更加真實帶感,實現方法是在update方法中可以加入一下隨機的偏移量。

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

        // 粒子在半徑方向上抖動           
        float offset = Random.Range(-0.01f, 0.01f);  // 偏移範圍
        info[i].radius += offset;

        particleArr[i].position = new Vector3(info[i].radius * Mathf.Cos(radian), 0f, info[i].radius * Mathf.Sin(radian));
    }
    // 通過粒子陣列設定粒子系統
    particleSys.SetParticles(particleArr, particleArr.Length);
}

到此便和一開始展示的效果完全一致了。

其他

  • 以上為個人理解,可能有誤,僅供參考。
  • 如感興趣,可訪問筆者Gayhub地址—傳送門
  • 視訊演示地址—傳送門