Unityの次世代エフェクトツール「VFX Graph (Visual Effect Graph)」は、GPUを活用して数万以上の大量のパーティクルを高速に処理・シミュレーションできる非常に強力な機能です。ゲーム開発において、プレイヤーが敵を斬りつけた時のヒットスパークや、魔法陣が起動した瞬間の光の粒子など、何かしらのゲームプレイのイベントに同期してエフェクトを再生したい場合、C#スクリプトから `VisualEffect.SendEvent("OnPlay")` のようにイベントを呼び出す制御が一般的です。しかし、スクリプトにエラーはなく、呼び出しコードも正しく実行されているにもかかわらず、「ゲーム中にパーティクルが一切放出されない」「アセットのインスペクター内にある手動テスト用の『Send Event』ボタンを押したときだけ正常に動く」という謎の不具合に遭遇したことはないでしょうか。本記事では、このイベント連携不良の根本原因を、「ラジオ局とDJ」の例え話を交えて解説し、型安全で高速なC#実装とVFX Graph内の正しいノード構築手順を解説します。

1. 例え話で理解する:『ラジオの周波数のすれ違いと、コンセント抜け』

C#からVFX Graphに再生命令が届かないバグを直感的に理解するために、『ラジオ局(C#スクリプト)と、受信スタジオのDJ(VFX Graph)』の関係を例えに考えてみましょう。

あなたがラジオ局の送信機から「音楽を流せ!(SendEvent)」と電波を送信しているとします。しかし、スタジオのラジオ受信機は静まり返ったままです。調べてみると、2つのすれ違いが起きていました。

1つ目は、「周波数の不一致(タイポ)」です。送信機は『FM 80.0(OnPlay)』で送信しているつもりでしたが、受信機側は『FM 80.1(onPlay)』で待機していました。大文字・小文字、あるいは不要なスペースが1文字あるだけで、電波は完全に虚空へ消え去り、受信機は沈黙し続けます。
2つ目は、「受信機のコンセント抜け(接続先のミス)」です。周波数は完璧に合っていたのですが、受信機の電源コードが「ターンテーブル(Initializeブロック)」ではなく、物置の棚(不適切なコンテキスト)に接続されていました。電波を受け取ってもスピーカー(Spawnコンテキスト)に信号が流れないため、いつまで経っても曲は再生されず、パーティクルは1つも飛び出しません。

これが、VFXにおける『イベント不通バグ』の正体です。C#側のスペルとVFX内のEventノードの定義を完全に一致させ、かつ受け取ったイベントを「パーティクルを生成する装置(Spawn)」へ正しくパイピングする必要があります。

2. 根本的な原因:文字列不一致とコンテキストの構造問題

技術的にC#からの `SendEvent` が無視される原因は、主に以下の3点に整理されます。

原因①:イベント文字列の大文字小文字・スペルの不一致

VFX Graphの `Event` ノードで定義されているイベント名(例:`OnStart`)と、C#スクリプトで渡している引数の文字列(例:`onstart`)が1文字でも異なると、イベントは完全に無視されます。Unityはコンソールに「イベントが見つかりません」といった親切なエラーログを出さないため、デバッグが非常に難しくなります。

原因②:Eventノードの接続先コンテキストの間違い

VFX Graphの構造において、パーティクルを生成するためには Spawn コンテキスト(または `Spawn` に繋がるイベントポート)がイベントの信号を受け取る必要があります。Eventノードが `Spawn` コンテキストを迂回して、直接 `Initialize` や `Update` などの「パーティクル初期化/更新」ブロックに接続されていると、すでに存在するパーティクルの状態更新は行われますが、肝心の「新規にパーティクルを生成して放出する(Spawn)」という動作自体がトリガーされません。

原因③:イベント属性(VFXEventAttribute)のバインドミス

C#から発生座標や放出方向などの「パラメータ付きイベント」を送る際、属性クラスのプロパティIDが正しく設定されていない場合、イベントは発火しても値が `(0, 0, 0)` などになり、エフェクトが地面の底の原点に小さく出てしまって画面に映らない現象が発生します。

3. 解決のためのステップ・バイ・ステップ手順

ステップ①:VFX Graph内の正しいノード配線

  1. VFX Graphエディタを開き、エフェクトの起点となる Event ノード(例:`OnPlay`)を作成します。
  2. このEventノードの出力を、必ず Spawn コンテキストの最上部にある Start(または再生トリガー)入力ポートに接続します。
  3. Spawnコンテキストの出力(`Spawn Event`)が、次に続く Initialize コンテキストの入力ポートへと流れていることをワイヤーで確認します。

ステップ②:C#側での型安全かつ高速なイベント送信実装

C#からイベントを送信する際、毎回文字列(`"OnPlay"`)をそのまま渡すと、文字列比較のオーバーヘッドが発生する上に、スペルミスの温床になります。実務では必ず `Shader.PropertyToID` を使用し、ハッシュIDに事前変換して送信する記述を採用してください。

using UnityEngine;
using UnityEngine.VFX;

[RequireComponent(typeof(VisualEffect))]
public class VfxEventTrigger : MonoBehaviour
{
    private VisualEffect vfxComponent;
    
    // イベント名を事前にID化してキャッシュ(タイポの防止と高速化)
    private static readonly int PlayEventId = Shader.PropertyToID("OnPlay");
    private static readonly int StopEventId = Shader.PropertyToID("OnStop");

    void Awake()
    {
        vfxComponent = GetComponent<VisualEffect>();
    }

    // ゲーム内イベント(ボタンクリックや攻撃アニメーション)から呼び出す
    public void TriggerEffect()
    {
        if (vfxComponent == null) return;

        // IDを指定して安全にイベントを送信
        vfxComponent.SendEvent(PlayEventId);
        Debug.Log("VFX OnPlay イベントを送信しました。");
    }

    public void StopEffect()
    {
        if (vfxComponent != null)
        {
            vfxComponent.SendEvent(StopEventId);
        }
    }
}

4. 動的パラメータ(座標・カラー)を同期して送る高度なC#コード

エフェクトの発生源(敵のヒット位置など)の座標をイベントと同時に送る場合は、`VFXEventAttribute` を使用します。

public void PlayHitEffectAt(Vector3 hitPosition, Color sparkColor)
{
    if (vfxComponent == null) return;

    // 1. イベント属性の空枠を作成
    VFXEventAttribute eventAttribute = vfxComponent.CreateVFXEventAttribute();

    // 2. 座標情報とカラー情報を属性にセット
    eventAttribute.SetVector3(Shader.PropertyToID("position"), hitPosition);
    eventAttribute.SetColor(Shader.PropertyToID("color"), sparkColor);

    // 3. 属性(データ)を添付してイベントを送信
    vfxComponent.SendEvent(PlayEventId, eventAttribute);
}