風などの物理的なエフェクトは、「ベクトル フィールド」を使ってモデリングします。これにより、特定の方向に吹いたり押し戻したりするなどの 3D の世界で定義される力が物理オブジェクトに作用する様子を表現できます。
ベクトル フィールドは、次のいずれかの方法で設定できます。
事前作成されたエンティティ コンポーネントとスクリプト モジュールのセットを使用してすべて Stingray エディタ内で設定する。「Stingray エディタ でベクトル フィールドを設定する」を参照してください。
ユーザ独自のカスタム Lua スクリプトで設定する。「Lua でベクトル フィールドを設定する」を参照してください。
ユーザ独自のベクトル フィールドを設定してゲーム ワールド内でアクティブにしたら、これらをゲーム内の可視オブジェクトに適用できます。「ベクトル フィールドをゲーム オブジェクトに適用する」を参照してください。
Stingray エディタでベクトル フィールドを設定するには:
ベクトル フィールドのエフェクトを表現するために、新しい空のエンティティを作成します。「新しいエンティティ アセットを作成する」を参照してください。Asset Browser で新しいアセットを選択します。
特定のレベル内でエンティティをスポーンします。ベクトル フィールドのエフェクトを表示するためのパーティクル エフェクトをまだ準備していない場合は、ここでパーティクル エフェクトを設定してスポーンする必要があります。
エンティティにトランスフォーム コンポーネントを追加します。これは、3D ワールドにおけるベクトル フィールドの配置、方向、範囲を表します。
そのエンティティを、ワールド内の風のエフェクトを発生させる中心の位置に移動し、拡大してベクトル フィールドがカバーするボリュームを収めるようにします。必要に応じてこのエンティティを回転して風向きをコントロールします。
実現したいエフェクトに応じて、使用可能なベクトル フィールド コンポーネントの 1 つをエンティティに追加します。
エフェクトの強さと速度をコントロールするには、コンポーネントのプロパティを設定します。
Effect 設定は、このエフェクトの名前を使用して、ベクトル フィールドがパーティクル システムのようなゲーム内の項目に影響を与える場合に、常に特定のエフェクトを参照させる方法を設定します。
エンティティにスクリプト コンポーネントを追加します。core リソース フォルダ内には、上記のエフェクトに対応するように事前作成された次のスクリプトがあります。これらのいずれかを指定するために、Script プロパティを設定します。
Lua でベクトル フィールドを使用するには、まず .vector_field リソース ファイル内でエフェクトを定義する必要があります。詳細については、「ベクトル フィールドのエフェクトを定義する」を参照してください。
各ベクトル フィールドのエフェクトは、stingray.VectorField オブジェクトを使用して Lua で表されます。これらのオブジェクトの 1 つを取得するには、stingray.World.vector_field() を呼び出して「風」などのエフェクトを参照するために使用する名前を渡します。
VectorField.add() を使用して、ベクトル フィールドに新しいエフェクトを追加できます。この呼び出しでは、エフェクトを定義する .vector_field リソースの名前を指定し、エフェクトのパラメータも設定することができます。VectorField.change() および VectorField.remove() を使用してエフェクトの変更や除去を行うこともできます。
また、VectorField.evaluate() を使用すると、ベクトル フィールドがスクリプトの動作に影響を与えるように設定したい場合に、いくつかの位置でベクトル フィールドを評価することができます。
現在、Stingray ではベクトル フィールドをパーティクル エフェクトや動的物理アクターに適用できます。
たとえば、立ち上がる煙の柱が風の方向に流れることを想定する場合などがあります。
ベクトル フィールドがパーティクル エフェクトに影響を与えるように設定するには:
Acceleration > Vector Field Wind コントローラをパーティクル エフェクトに追加します。
コントローラの Vector Field プロパティを、ベクトル フィールドのエフェクトの名前に設定します。
エンティティ コンポーネントを使用して、ベクトル フィールドを設定した場合、これはベクトル フィールド コンポーネントの Effect 設定と一致する必要があります。
Lua でベクトル フィールドを設定した場合は、これは stingray.World.vector_field() に渡した名前と一致する必要があります。
たとえば、あるレベルで強風が連続的に軽量なオブジェクトを吹き飛ばしている様子や、大規模な爆発が特定の領域から外にオブジェクトを吹き飛ばす様子をトリガしたい場合などがあります。
風力を適用するには Lua を使用する必要がありますが、エンティティとコンポーネントを使用して風のエフェクトを設定することができます。
既定では、風力によって物理オブジェクトが活動状態になることはありません。風によってレベル内のすべてのオブジェクトが活動状態になるとパフォーマンスがかなり低下するからです。つまり、大爆発をトリガした場合も、すでにスリープ状態に入っている動的アクターには影響が及びません。これを修正するには、stingray.PhysicsWorld.wake_actors() を使用して、風や爆発の影響を受ける動的オブジェクトを活動化します。こうすると、オブジェクトが活動状態になって、ベクトル フィールドから風力を受けることができます。
ベクトル フィールドを使用して物理オブジェクトに影響を与えるには、stingray.PhysicsWorld.apply_wind() 関数を呼び出して、ベクトル フィールドから物理オブジェクトに風力を適用します。この関数に適用するベクトル フィールドのエフェクトの名前を渡します。エンティティ コンポーネントを使用して、ベクトル フィールドを設定した場合、これはベクトル フィールド コンポーネントの Effect 設定と一致する必要があります。Lua でベクトル フィールドを設定した場合は、これは stingray.World.vector_field() に渡した名前と一致する必要があります。
注: 連続的な風力を適用するには、PhysicsWorld.apply_wind() をフレームごとに呼び出す必要があります。
ベクトル フィールドのエフェクトは .vector_field ファイルで定義されます。これらのエフェクトは、入力位置から出力の風を計算する、HLSL のような言語で記述されます。
次に、シンプルな例を示します。
const float4 value = float4(0,0,0,0); struct vf_in { float4 position : CHANNEL0; float4 wind : CHANNEL1; }; struct vf_out { float4 wind : CHANNEL1; }; void global(in vf_in in, out vf_out out) { out.wind = in.wind + value; }
このエフェクトは、空間内のポイントごとに定数値を返します。既定では、value は(0,0,0,0)に設定されます。この値では風はまったく発生しませんが、エフェクトを再生するときに、エフェクト内で定義された任意のグローバル変数をスクリプトで上書きできます。スクリプトは以下を使用して再生できます。
VectorField.add(vf, "global", {value = Vector3(2,0,0)})
これを行うと、レベル全体にわたって 2 m/s の x 方向の風が作成されます。
エフェクトの最後の行に注目してください。
out.wind = in.wind + value;
これは、エフェクトが適用される前に存在していた風に、エフェクトが風を追加することを意味します。この行は、常にエフェクト コードの最終行として配置する必要があります。エフェクトを積み重ねる場合(ユーザが風のエフェクトを複数再生する場合など)、ユーザが表示したいエフェクトは個々の風エフェクトがすべて加算されたものであるからです。
ベクトル フィールドの言語は HLSL と似ていますが、実際は表示方法に対して極めて厳格な、完全に独立したパーサーによって解析されます。この例のようにグローバル変数を定義する必要があります。存在できる関数は 1 つのみです。この関数は、例のように in および out パラメータを取る必要があります(ただし、必要なときにいつでも関数を呼び出すことができます)。一部が HLSL で機能するからといって、必ずしもこの言語で動作するわけではありません。
ベクトル フィールド エフェクトの入力および出力パラメータは、vf_in および vf_out の構造の定義で定義されます。これらは例のようになりますが、好みに応じて他の名前を使用することができます。
既定では、ベクトル フィールド エフェクトはワールド内のあらゆる場所に適用されます。限られた領域にのみ適用されるベクトル フィールド エフェクトを作成する場合は、この領域外のポイントに対して(0,0,0,0)を返すようにエフェクトを明示的に記述する必要があります。
前のエフェクトと同じように機能し、特定の球にのみ適用されるエフェクトを記述してみましょう。(残りの例では、構造体の定義はすべてのエフェクトで同じであるため、除外してあります。)
const float4 value = float4(0,0,0,0); const float4 center = float4(0,0,0,0); const float4 radius = float4(1,1,1,1); void global_sphere(in vf_in in, out vf_out out) { float4 r = in.position - center; float4 distance = dot(radius, radius) - dot(r,r); float4 wind = select_gt_0(distance, value); out.wind = in.wind + wind; }
ここでは、distance を計算します。項目が球内にある場合、この値はゼロより大きく、球の外側にある場合はゼロより小さくなります。次に、select_gt_0() 関数を使用して、distance > 0 の場合は wind を value に設定し、それ以外の場合は value を(0,0,0,0)に設定します。
エフェクトを再生するときに、エフェクトのブロードフェーズ カリングに使用されるエフェクトに対して AABB を指定します。最適な結果を得るには、この AABB が、エフェクト関数がゼロ以外の領域と一致する必要があります。そうでない場合、カリングの効率が低下します。
特殊なコンストレイントの time を使用して、時間に依存するエフェクトを作成できます。時間依存エフェクトは、ベクトル フィールド システムによって配置された後、自動的にこの時刻に設定されます。次に、このエフェクトを使用して正弦曲線の風を実装する例を示します。
const float4 amplitude = float4(1,1,1,0); const float4 wave_vector = float4(1,0,0,0); const float4 frequency = float4(1,1,1,0); const float4 phase = float4(0,0,0,0); const float4 time = float4(0,0,0,0); void sine(in vf_in in, out vf_out out) { out.wind = in.wind + amplitude * sin( dot(wave_vector, in.position) + frequency*time + phase ); }
ベクトル言語はループまたは if 文をサポートしません。次の式を使用することができます。
+ - * /
要素ごとに加算、減算、乗算、除算を行います。
dot(a, b)
a と b のドット積を計算して、それを結果のすべての要素に割り当てます。
cross(a, b)
a と b の乗積を計算します。乗積の w 要素は常に 0 です。
select_gt_0(a, b)
a > 0 となる要素に b、他の要素に 0 が配置されているベクトルを返します。
normalize(a)
正規化された値を返します。
sin(a)
要素内の各要素の sin が a であるベクトルを返します。
ベクトル フィールド定義のその他の例については、core/entities/vector_fields フォルダにある .vector_field ファイルを参照してください。これらの定義は、ベクトル フィールドのエンティティと「Stingray エディタ でベクトル フィールドを設定する」で説明されているスクリプトの基盤を提供しますが、また、Lua でユーザが作成した VectorField オブジェクトにこれらの定義を使用してエフェクトを追加することもできます。