作者:KARL JONES 原文連結
從Unity 5.3開始,開發者可以透過腳本來存取粒子系統中所有的模組了。我們了解這些新的腳本特性可能會讓大家困惑,為什麼要用非常規的方式來使用結構?本文中你會找到一些答案。
範例:
這是一個典型的修改Emission Module中的速率的範例。
如果你熟悉.NET,你會注意到我們抓了結構體並且設了它的值,但沒有將值貼回粒子系統中。那麼粒子系統是如何知道這個改變呢?怎麼辦到的?
從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); } } |
只是一個介面
Unity中的粒子系統完全包含在引擎的C++部分中。當呼叫粒子系統或者其中的一個模組時,會直接呼叫C++端。
在內部,粒子系統模組(Particle system modules)只是粒子系統中一個屬性。他們並非獨立,且無法在系統間改變模組所有權或共用模組。所以雖然可以在腳本中抓取一個模組並傳遞,但模組永遠只會屬於同一個系統。
為了便於理解,讓我們看一下例子
當系統接收到一個對emission module的請求時,引擎會新建一個EmissionModule結構並且作為唯一的參數傳遞給它的擁有者。這麼做是因為存取模組的屬性必需要有粒子系統在上層。
當我們設定速率時,變數m_ParticleSystem用來存取這個模組並直接設定速率值。因此我們完全不需要重新賦予值到粒子系統中,因為它永遠是粒子系統的一部分,任何改變都會立即生效。所以模組做的只是呼叫擁有它的粒子系統,模組結構體本身只是一個進入粒子系統內部的接口。
在內部,模組存儲他們各自的屬性並保留狀態資訊,這就是為何無法在不同的粒子系統中共用模組。
如果你想將屬性從一個系統拷貝到另外一個,建議只拷貝相對應的值而不要拷貝整個類別,這樣就可以讓C++和C#之間拷貝的資料盡可能的少。
有很多模組的屬性都是由MinMaxCurve類別來驅動的。用來描述隨時間的變化的值,支援4種模式。以下是如何在腳本中使用它們的指南。
這是最簡單的模式,Constant只使用單一的值。這個值不會隨著時間改變而改變。如果你想要透過腳本改變一個值,改變這個值是一種方法。
在腳本中存取Constant的程式如下:
在內部,粒子系統模組(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); } } ...... } |
在內部,模組存儲他們各自的屬性並保留狀態資訊,這就是為何無法在不同的粒子系統中共用模組。
如果你想將屬性從一個系統拷貝到另外一個,建議只拷貝相對應的值而不要拷貝整個類別,這樣就可以讓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的範例:
如何存取模組兩個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說明文件中。