2016年4月21日 星期四

粒子系統模組Particle System Modules - FAQ

作者:KARL JONES 原文連結

Unity 5.3開始,開發者可以透過腳本來存取粒子系統中所有的模組了。我們了解這些新的腳本特性可能會讓大家困惑,為什麼要用非常規的方式來使用結構?本文中你會找到一些答案。

存取模組(Accessing Modules)


範例:
這是一個典型的修改Emission Module中的速率的範例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using UnityEngine;
public class AccessingAParticleSystemModule : MonoBehaviour
{
  // Use this for initialization
  void Start ()
  {
    // Get the emission module.
    var forceModule = GetComponent<ParticleSystem>().forceOverLifetime;
    // Enable it and set a value
    forceModule.enabled = true;
    forceModule.x = new ParticleSystem.MinMaxCurve(15.0f);
  }
}
如果你熟悉.NET,你會注意到我們抓了結構體並且設了它的值,但沒有將值貼回粒子系統中。那麼粒子系統是如何知道這個改變呢?怎麼辦到的?


只是一個介面


Unity中的粒子系統完全包含在引擎的C++部分中。當呼叫粒子系統或者其中的一個模組時,會直接呼叫C++端。
在內部,粒子系統模組(Particle system modules)只是粒子系統中一個屬性。他們並非獨立,且無法在系統間改變模組所有權或共用模組。所以雖然可以在腳本中抓取一個模組並傳遞,但模組永遠只會屬於同一個系統。

為了便於理解,讓我們看一下例子
當系統接收到一個對emission module的請求時,引擎會新建一個EmissionModule結構並且作為唯一的參數傳遞給它的擁有者。這麼做是因為存取模組的屬性必需要有粒子系統在上層。

1
2
3
4
5
6
7
8
public sealed class ParticleSystem : Component
{
.....
  public EmissionModule emission { get { return new EmissionModule(this); } }
....
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public partial struct EmissionModule
{
  private ParticleSystem m_ParticleSystem; // Direct access to the particle system that owns this module.
  EmissionModule(ParticleSystem particleSystem)
  {
    m_ParticleSystem = particleSystem;
  }
  public MinMaxCurve rate
  {
    set
    {
      // In here we call down to the c++ side and perform what amounts to this:
      m_ParticleSystem->GetEmissionModule()->SetRate(value);
    }
  }
......
}
當我們設定速率時,變數m_ParticleSystem用來存取這個模組並直接設定速率值。因此我們完全不需要重新賦予值到粒子系統中,因為它永遠是粒子系統的一部分,任何改變都會立即生效。所以模組做的只是呼叫擁有它的粒子系統,模組結構體本身只是一個進入粒子系統內部的接口。

在內部,模組存儲他們各自的屬性並保留狀態資訊,這就是為何無法在不同的粒子系統中共用模組。
如果你想將屬性從一個系統拷貝到另外一個,建議只拷貝相對應的值而不要拷貝整個類別,這樣就可以讓C++和C#之間拷貝的資料盡可能的少。

The MinMaxCurve


有很多模組的屬性都是由MinMaxCurve類別來驅動的。用來描述隨時間的變化的值,支援4種模式。以下是如何在腳本中使用它們的指南。

Constant


這是最簡單的模式,Constant只使用單一的值。這個值不會隨著時間改變而改變。如果你想要透過腳本改變一個值,改變這個值是一種方法。

在腳本中存取Constant的程式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using UnityEngine;
public class MinMaxCurveConstantMode : MonoBehaviour
{
  ParticleSystem myParticleSystem;
  ParticleSystem.EmissionModule emissionModule;
  void Start()
  {
    // Get the system and the emission module.
    myParticleSystem = GetComponent<ParticleSystem>();
    emissionModule = myParticleSystem.emission;
    GetValue();
    SetValue();
  }
  void GetValue()
  {
    print("The constant value is " + emissionModule.rate.constantMax);
  }
  void SetValue()
  {
    emissionModule.rate = new ParticleSystem.MinMaxCurve(10.0f);
  }
}

Random between two constants 


這個模式在兩個值之間產生一個亂數。在內部我們會將這兩個值作為關鍵分別存到最小和最大的曲線中。我們透過在兩個值之間進行線性插值計算來獲取新值,它使用一個標準劃的亂數作為插值量。這取樣方式和Random between two curves幾乎相同。

如何存取模組兩個constant的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using UnityEngine;
public class ParticleModuleExamples : MonoBehaviour
{
  ParticleSystem myParticleSystem;
  ParticleSystem.EmissionModule emissionModule;
  void Start()
  {
    // Get the system and the emission module.
    myParticleSystem = GetComponent<ParticleSystem>();
    emissionModule = myParticleSystem.emission;
    GetRandomConstantValues();
    SetRandomConstantValues();
  }
  void GetRandomConstantValues()
  {
    print(string.Format("The constant values are: min {0} max {1}.", emissionModule.rate.constantMin, emissionModule.rate.constantMax));
  }
  void SetRandomConstantValues()
  {
    // Assign the curve to the emission module
    emissionModule.rate =new ParticleSystem.MinMaxCurve(0.0f, 1.0f);
  }
}

Curve


在這個模式中,屬性值會透過對一個曲線的查詢來改變,使用腳本中MinMaxCurve的曲線時有一些注意事項。
首先,如果試著在一個Curve模式下讀取曲線值時會得到如下的錯誤:“Reading particle curves from script is unsupported unless they are in constant mode”。
由於曲線在引擎中是壓縮的狀態無法存取到MinMaxCurve,除非使用兩個constant模式之一,我們正著手計畫改進這點。主要原因是系統內部我們沒有實際保存一個AnimationCurve而是從兩個路徑中選擇一個。如果曲線很簡單(不超過三個key並在每個端點都有一個)。那麼我們會用一個性能較好的優化曲線。如果曲線很複雜我們會回頭使用未優化的曲線。在Inspector視窗中,未優化的曲線會在右下顯示一個小圖示讓你手動優化這個曲線。

優化的曲線



未優化的曲線


雖然不能用腳本從模組中存取曲線,但可以先存一個自己的曲線然後在需要的時候貼到到模組中,像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using UnityEngine;
public class MinMaxCurveCurveMode : MonoBehaviour
{
  ParticleSystem myParticleSystem;
  ParticleSystem.EmissionModule emissionModule;
  // We can "scale" the curve with this value. It gets multiplied by the curve.
  public float scalar = 1.0f;
  AnimationCurve ourCurve;
  void Start()
  {
    // Get the system and the emission module.
    myParticleSystem = GetComponent<ParticleSystem>();
    emissionModule = myParticleSystem.emission;
    // A simple linear curve.
    ourCurve = new AnimationCurve();
    ourCurve.AddKey(0.0f, 0.0f);
    ourCurve.AddKey(1.0f, 1.0f);
    // Apply the curve
    emissionModule.rate = new ParticleSystem.MinMaxCurve(scalar, ourCurve);
    // In 5 seconds we will modify the curve.
    Invoke("ModifyCurve", 5.0f);
  }
  void ModifyCurve()
  {
    // Add a key to the current curve.
    ourCurve.AddKey(0.5f, 0.0f);
   // Apply the changed curve
   emissionModule.rate = new ParticleSystem.MinMaxCurve(scalar, ourCurve);
  }
}


Random between 2 curves


這個模式從最小和最大曲線之間產生一個亂數,它用時間來決定在X軸上採樣的位置。陰影區表示可能的值。這個模式和曲線模式類似,不能在腳本中存取曲線也會幫你優化曲線(在允許情況下)。想要用這個模式看到效果,兩條曲線都必須被優化,也就是說和曲線模式一樣每條曲線不超過三個key並在每個端點各有一個key,你可以透過inspector視窗檢查右下角來確定曲線是否被優化。



下面的範例和Curve曲線模式很相似,只不過現在多設定了最小曲線。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using UnityEngine;
public class MinMaxCurveRandom2CurvesMode : MonoBehaviour
{
  ParticleSystem myParticleSystem;
  ParticleSystem.EmissionModule emissionModule;
  AnimationCurve ourCurveMin;
  AnimationCurve ourCurveMax;
  // We can "scale" the curves with this value. It gets multiplied by the curves.
  public float scalar = 1.0f;
  void Start()
  {
    // Get the system and the emission module.
    myParticleSystem = GetComponent<ParticleSystem>();
    emissionModule = myParticleSystem.emission;
    // A horizontal straight line at value 1
    ourCurveMin = new AnimationCurve();
    ourCurveMin.AddKey(0.0f, 1.0f);
    ourCurveMin.AddKey(1.0f, 1.0f);
    // A horizontal straight line at value 0.5
    ourCurveMax = new AnimationCurve();
    ourCurveMax.AddKey(0.0f, 0.5f);
    ourCurveMax.AddKey(1.0f, 0.5f);
    // Apply the curves
    emissionModule.rate = new ParticleSystem.MinMaxCurve(scalar, ourCurveMin, ourCurveMax);
    // In 5 seconds we will modify the curve.
    Invoke("ModifyCurve", 5.0f);
  }
  void ModifyCurve()
  {
    // Create a "pinch" point.
    ourCurveMin.AddKey(0.5f, 0.7f);
    ourCurveMax.AddKey(0.5f, 0.6f);
    // Apply the changed curve
    emissionModule.rate = new ParticleSystem.MinMaxCurve(scalar, ourCurveMin, ourCurveMax);
  }
}

效能


我們做了一些簡單的效能比對來看看這些模式之間的差異。這些樣本都是在我們的SIMD優化前獲取的,SIMD優化能讓效能明顯提升。在特定測試場景結果如下:


解除你的苦痛


從MinMaxCurve中讀取曲線


或許你想要從腳本中讀取粒子系統曲線,不管何種模式。我們正努力讓這個功能實現,讓你可以在腳本中存取這些可愛的曲線。還有,現在無法檢查出是否模組正在用曲線。這也正在改進中!

將模組從Struct轉換為Class


我們現在正著手將所有的結構轉換到類別的工作。從功能上他們的行為是一樣的,但是類別是參考類型,這會更加明確模組從屬一個系統。同樣也可以在不保持一個臨時變數的情況下設定/獲取值。但是這就代表在構造時就會分配記憶體,在初始化時會產生GC。
例如:

1
2
3
4
5
6
var em = ps.emission;
em.enabled = true;
// Could be written as
ps.emission.enabled = true;


結尾


希望本文講解能幫到你,可以到這裡參與討論。文中提到的粒子模組資訊也會加到Unity說明文件中。

著作人