【VRChat】同期ボタンを実装する:ローカル・トグル・ワンタイムの作り分け

作成: 2025-12-19

ワールド制作の基本ギミック、ボタンの実装パターン集。ローカルボタン、全プレイヤー同期トグルスイッチ、一度だけ押せるボタンの作り方。

概要

ワールド内のギミックを起動する最も直感的で基本的な方法は「ボタン」です。プレイヤーがボタンをクリック(Interact)することで、ライトを点灯させたり、ドアを開けたり、ゲームを開始したりと、様々なアクションのきっかけを作ることができます。

本記事では、これまでに学んだ知識を総動員し、UdonSharpでインタラクティブなボタンを実装する具体的な手順を、複数の実践的なパターンに分けて解説します。

  1. ローカルボタン: 押したプレイヤーにだけ影響がある、最もシンプルなボタン。
  2. 同期トグルボタン: 押すたびにON/OFFが切り替わり、その状態が全プレイヤーで共有されるボタン。
  3. 一度だけ押せるボタン: ワールド内で一度しか実行できない処理を起動するボタン。

Unityエディタでの基本設定(全パターン共通)

まず、ボタンとして機能するGameObjectをUnityエディタで準備します。ここでは例として、シンプルなキューブをボタンとして使用します。

  1. オブジェクトの作成: Hierarchyウィンドウで右クリックし、[3D Object] > [Cube] を選択してキューブを作成します。名前を「InteractiveButton」などに変更しておくと分かりやすいです。大きさや位置を調整してください。
  2. Colliderの確認: 作成したキューブには、デフォルトでBox Colliderコンポーネントが付いています。これがプレイヤーのクリックを検知するために必要です。
  3. Udon Behaviourの追加: ボタンオブジェクトを選択し、Inspectorウィンドウで [Add Component] をクリック。「Udon Behaviour」を追加します。
  4. スクリプトの割り当て: 対応するUdonSharpスクリプトを作成し、Udon BehaviourコンポーネントのProgram Sourceフィールドにドラッグ&ドロップで割り当てます。
  5. Interaction Textの設定: スクリプトを割り当てると、InspectorにInteraction Textフィールドが表示されます。ここに「押す」や「Click」などのテキストを入力します。これがプレイヤーに表示されるツールチップになり、この設定がないとInteractイベントは発火しません
Interaction Textの設定

パターン1: ローカルボタン(サウンド再生ボタン)

押したプレイヤーのクライアントでのみサウンドを再生するボタンです。他のプレイヤーには影響を与えません。

スクリプト: LocalSoundButton.cs

using UdonSharp;
using UnityEngine;

public class LocalSoundButton : UdonSharpBehaviour
{
    [Tooltip("クリック時に再生するオーディオソースを割り当てます。")]
    public AudioSource targetSound;

    public override void Interact()
    {
        // targetSoundが設定されている場合のみ処理を実行
        if (targetSound != null)
        {            
            targetSound.Play();
        }
        else
        {
            Debug.LogWarning("再生するサウンドが設定されていません。");
        }
    }
}

Unityでの設定

  1. LocalSoundButton.csスクリプトを作成し、ボタンオブジェクトのUdon Behaviourに割り当てます。
  2. シーン内に空のGameObjectを作成し、「SoundPlayer」などの名前を付けます。このオブジェクトにAudioSourceコンポーネントを追加します。
  3. AudioSourceコンポーネントのAudioClipに再生したい音声ファイルを割り当て、Play On Awakeのチェックは外しておきます。
  4. ボタンオブジェクトのInspectorに戻り、Local Sound ButtonコンポーネントのTarget Soundフィールドに、先ほど作成したAudioSourceを持つGameObjectをドラッグ&ドロップします。

これで、ボタンをクリックしたプレイヤーの環境でのみ、指定したサウンドが再生されるようになります。

パターン2: 同期トグルボタン(部屋の照明スイッチ)

照明スイッチの動作テスト

押すたびに部屋の照明がON/OFFし、その状態がインスタンス内の全プレイヤーで同期されるスイッチです。ネットワーク同期の基本パターンを実践します。

スクリプト: SyncedLightSwitch.cs

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;

public class SyncedLightSwitch : UdonSharpBehaviour
{
    [Header("設定")]
    [Tooltip("ON/OFFを切り替えるライトのGameObjectを割り当てます。")]
    public GameObject roomLight;

    // ライトの状態を全プレイヤーで同期するための変数
    [UdonSynced]
    private bool isLightOn = true; // 初期状態はON

    void Start()
    {
        // ワールド参加時に、現在の同期状態をライトに反映する
        UpdateLightState();
    }

    public override void Interact()
    {
        // このオブジェクトの所有権を自分に要求する
        Networking.SetOwner(Networking.LocalPlayer, this.gameObject);

        // ライトの状態を反転させる
        isLightOn = !isLightOn;

        // 変更後の状態を自分自身に即時反映
        UpdateLightState();

        // 変更を他の全プレイヤーに通知する
        RequestSerialization();
    }

    // 他のプレイヤーのデータを受信したときに呼び出される
    public override void OnDeserialization()
    {
        // 同期された最新の状態をライトに反映する
        UpdateLightState();
    }

    // ライトのGameObjectのON/OFFを切り替える処理をまとめたメソッド
    private void UpdateLightState()
    {
        if (roomLight != null)
        {
            roomLight.SetActive(isLightOn);
        }
    }
}

Unityでの設定

  1. SyncedLightSwitch.csスクリプトを作成し、ボタンオブジェクトのUdon Behaviourに割り当てます。
  2. シーン内に照明となるLightコンポーネントを持つGameObject(例: Point Light)を配置します。
  3. ボタンオブジェクトのInspectorで、Synced Light SwitchコンポーネントのRoom Lightフィールドに、作成したライトオブジェクトをドラッグ&ドロップします。

テスト時のヒント: ライトのON/OFFを確認しやすくするには、シーンを暗くしておくと効果的です。

  1. メニューの [Window] > [Rendering] > [Lighting] を開く
  2. 「Environment」タブで Skybox MaterialNone に設定
  3. Environment Lighting > SourceColor に変更
  4. Ambient Color を黒(#000000)に設定
  5. シーン内の Directional Light を無効化または削除

これで完全に暗くなり、ライトの効果が分かりやすくなります。

これで、誰かがボタンを押すたびに、インスタンス内の全員の視界でライトがON/OFFするようになります。

パターン3: 一度だけ押せるボタン(ゲーム開始ボタン)

ゲームの開始など、一度実行されたら再度押されるべきではない処理を起動するボタンです。同期を利用して、誰かが一度押したら全プレイヤーで押せなくなるようにします。

スクリプト: OneTimeGameStartButton.cs

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;

public class OneTimeGameStartButton : UdonSharpBehaviour
{
    [Header("設定")]
    [Tooltip("ボタンとして表示するメッシュレンダラー")]
    public MeshRenderer buttonRenderer;
    [Tooltip("ボタンが押された後のマテリアル")]
    public Material pressedMaterial;

    // ボタンが既に押されたかどうかを同期する変数
    [UdonSynced]
    private bool isPressed = false;

    void Start()
    {
        // 起動時に状態をチェック
        CheckButtonState();
    }

    public override void Interact()
    {
        // まだ押されていない場合のみ処理を実行
        if (!isPressed)
        {
            Networking.SetOwner(Networking.LocalPlayer, this.gameObject);
            isPressed = true;
            
            // 状態を更新し、変更を通知
            CheckButtonState();
            RequestSerialization();

            // --- ここにゲームを開始する処理を記述 --- 
            Debug.Log("ゲームを開始します!");
            // 例: SendCustomEventでGameManagerに通知するなど
        }
    }

    public override void OnDeserialization()
    {
        // 他の誰かが押した情報を受け取って状態を更新
        CheckButtonState();
    }

    private void CheckButtonState()
    {
        // 既に押されている場合
        if (isPressed)
        {
            // ボタンのインタラクションを無効にする
            this.DisableInteractive = true;
            // 見た目を「押された後」のマテリアルに変更する
            if (buttonRenderer != null && pressedMaterial != null)
            {
                buttonRenderer.material = pressedMaterial;
            }
        }
    }
}

Unityでの設定

  1. OneTimeGameStartButton.csスクリプトを作成し、ボタンオブジェクトのUdon Behaviourに割り当てます。
  2. ボタンが押された後の見た目となるMaterialアセットを別途作成しておきます(例: 暗い色のマテリアル)。
  3. ボタンオブジェクトのInspectorで、Button Rendererにボタン自身のMeshRendererを、Pressed Materialに作成したマテリアルを割り当てます。

これで、インスタンス内で誰かが一度ボタンを押すと、そのボタンはインタラクト不可になり、見た目も変化します。これにより、全プレイヤーが「このボタンは既に使用済みである」と認識できます。

まとめ

  • ボタンはInteractイベントを起点として実装するのが基本です。
  • ローカルな処理か、同期的な処理かを意識して、適切な実装パターンを選択することが重要です。
  • 同期ボタンでは、「所有権取得 → 値の変更 → シリアライズ要求 → 受信して更新」という一連の流れを正確に実装する必要があります。
  • DisableInteractiveプロパティやマテリアルの変更を組み合わせることで、ボタンの状態をプレイヤーに視覚的に伝えることができます。

ボタンは単純なギミックですが、UdonSharpの基本的な要素が凝縮されています。これらのパターンを応用することで、さらに複雑なインタラクションを構築していくことが可能になります。