【VRChat】メソッドとカスタムイベント:2つのボタンから同じライトを操作する

作成: 2025-12-19最終更新: 2026-09-09

入口のボタンと奥のボタン、どちらからでも同じライトを操作できるようにします。処理をライト側にまとめる設計と、型付き参照とSendCustomEventの使い分けを解説します。

部屋の入口と奥の両方に照明スイッチを置きたい。どちらを押しても同じライトが切り替わってほしい。

素直に作ると、両方のボタンに同じコードをコピーすることになります。動きはしますが、あとで「明るさを変えたい」となったときに、2か所を直す羽目になります。片方を直し忘れれば、押す場所によって挙動が違う部屋になります。

この記事では、処理をライト側に1つだけ持たせて、ボタンはそれを呼ぶだけ にする書き方を解説します。

左右の壁のスイッチが、1本にまとまって天井の照明につながっている

この記事でわかること

  • 処理を1か所にまとめる設計
  • 別のUdonのメソッドを直接呼ぶ方法
  • SendCustomEvent との使い分け
  • nameof を使う理由

変数と参照 を試した状態から始めます。

Sponsored


押す側と、変える側を分ける

同じ処理を2か所に書かないための考え方は、役割を分ける ことです。

2つのボタンが、同じ1つの処理を呼び、1つのライトを変える
  • ボタン側 は「押された」ことだけを担当します。ライトの中身は知りません
  • ライト側 は「切り替える」処理だけを持ちます。誰が押したかは知りません

こうすると、ボタンを3個に増やしても、ライトの処理は1つのままです。明るさを変えたくなったら、ライト側の1か所を直せば全部に効きます。

呼ぶ側と、実際に変える側を分けた構成

この形は、ワールドが大きくなるほど効いてきます。入口・奥・リモコンの3か所から同じ扉を開けたいときも、扉側に処理を置いておけば済みます。

呼ぶ側が増えても、変える側は増えない。これがこの記事でいちばん大事な考え方です。

Sponsored

実践:2つのボタンから1つのライトを操作する

入口と奥にボタンを置き、どちらを押しても同じライトが切り替わるようにします。

1. ライトとボタンを置く

床のあるシーンに3つ置きます。

名前作り方と設定
RoomLightLight → Point Light。(0, 2.5, 0)、Range 6、Intensity 2、Mode「Realtime」
ButtonFrontCube。(-1.5, 1, -1)、Scale (0.4, 0.4, 0.4)
ButtonBackCube。(1.5, 1, 2)、Scale (0.4, 0.4, 0.4)
離れた2つのボタンから同じライトへ

ライトを操作する側のオブジェクトも用意します。「Create Empty」で LightController を作り、(0, 0, 0) に置きます。

2. ライト側のコードを書く

先に、変える側から作ります。Assets/Scripts で「Create → U# Script」を選び、RoomLightController を作ります。

using UdonSharp;
using UnityEngine;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class RoomLightController : UdonSharpBehaviour
{
    [SerializeField] private Light targetLight;
    [SerializeField] private float dimIntensity = 0.2f;
    [SerializeField] private float brightIntensity = 2f;

    private bool isBright;

    private void Start()
    {
        Apply();
    }

    // 外から呼ばれる入口。public にしておく
    public void ToggleLight()
    {
        isBright = !isBright;
        Apply();
        Debug.Log("[RoomLightController] bright=" + isBright);
    }

    public void TurnOn()
    {
        isBright = true;
        Apply();
    }

    public void TurnOff()
    {
        isBright = false;
        Apply();
    }

    // 状態を見た目へ反映する。ここだけが実際にライトを触る
    private void Apply()
    {
        if (targetLight == null) return;
        targetLight.intensity = isBright ? brightIntensity : dimIntensity;
    }
}

ToggleLight() TurnOn() TurnOff() の3つを public にしています。外から呼ばれる入口だからです。

Apply()private のままです。これは内部の処理で、外から直接呼ぶ必要がありません。外に見せる入口と、中の処理を分ける と、あとで読みやすくなります。

LightController に Udon Behaviour を追加し、このスクリプトを指定して、Target Light に RoomLight を割り当てます。

3. ボタン側のコードを書く

次に、押す側です。RoomLightButton という名前で作ります。

using UdonSharp;
using UnityEngine;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class RoomLightButton : UdonSharpBehaviour
{
    // 相手のクラス名で受け取る(型付き参照)
    [SerializeField] private RoomLightController controller;

    public override void Interact()
    {
        if (controller == null)
        {
            Debug.LogWarning("[RoomLightButton] Controllerが未設定です。");
            return;
        }

        controller.ToggleLight();  // 相手のメソッドを直接呼ぶ
    }
}

短いです。ボタンは「押されたら、あの人に頼む」だけをしています。

注目してほしいのが [SerializeField] private RoomLightController controller; です。相手のクラス名をそのまま型として使えます。 これを 型付き参照 と呼びます。

型が決まっているので、controller.ToggleLight() と書けます。もし ToggleLigt() のように打ち間違えたら、コンパイルの時点でエラーになります。 実行してから気づく、ということがありません。

4. 2つのボタンに割り当てる

ButtonFrontButtonBack の両方に、Udon Behaviour を追加して RoomLightButton を指定します。

どちらも Controller に LightController をドラッグ します。同じ相手を指します。

指定するもの
ControllerHierarchyの LightController
Interaction Text照明を切り替える

5. 動かして確かめる

Playして、両方のボタンを押してみてください。

操作期待する結果
入口のボタンを押すライトが明るくなる。Consoleに bright=True
奥のボタンを押すライトが暗くなる。bright=False
また入口のボタンを押す明るくなる

どちらのボタンからでも、同じ状態が切り替わっています。 状態を持っているのはライト側1か所だけなので、押す場所が変わってもずれません。

ここで、brightIntensity5 に変えてみてください。ボタンのコードは1文字も触っていないのに、両方のボタンで結果が変わります。変える側を1か所にまとめた効果 です。

SendCustomEventという別の呼び方

もう1つ、相手のメソッドを呼ぶ方法があります。

public override void Interact()
{
    if (controller == null) return;

    // メソッド名を文字列で指定して呼ぶ
    controller.SendCustomEvent(nameof(RoomLightController.ToggleLight));
}

SendCustomEvent は、メソッド名を文字列で渡して呼ぶ 方法です。条件が2つあります。

  • 呼ばれる側のメソッドが public であること
  • 引数を取らない メソッドであること

private のメソッドは呼べません。Apply() を呼ぼうとしても動きません。

型で直接呼ぶか、名前で呼ぶか

nameof() を使う理由 があります。"ToggleLight" と文字列で直接書くこともできますが、あとでメソッド名を変えたときに気づけません。実行して初めて「呼ばれない」と分かります。nameof ならコンパイルの時点でエラーになります。

では、どちらを使えばいいのか。

状況使うもの
相手のクラスが決まっている型付き参照で直接呼ぶcontroller.ToggleLight()
相手の種類が決まっていないSendCustomEvent
他のプレイヤーにも実行させたいSendCustomNetworkEventNetwork Events

この記事の場面では、型付き参照のほうが簡単で安全 です。SendCustomEventは、いろいろな種類のスクリプトを同じ扱いで呼びたいときや、ネットワーク越しに呼びたいときに使います。

引数を渡したいときは、先に相手の変数へ値を入れてから呼びます。

controller.targetBrightness = 3f;   // 先に値を入れる(publicな変数が必要)
controller.ApplyBrightness();       // それから呼ぶ

型付き参照なら、そもそも引数付きのメソッドをそのまま呼べます。ここでも型付き参照のほうが素直です。

Sponsored

うまくいかないときは

  • 押しても何も起きない → Controllerが None になっていないか。起動時の警告も確認します
  • 片方のボタンだけ効かない → そのボタンのControllerが空か、Udon Behaviourが付いていません
  • SendCustomEventで呼べない → 呼ばれる側のメソッドが public か、引数を取っていないかを確認します
  • 押すたびに別々の状態になる → 状態をボタン側に持たせています。状態は変える側(Controller)に1つだけ置きます

おまけ:先に知っておくと良いこと

  • 入口は少なく、中は自由に: 外から呼ばれるメソッドだけ public にして、あとは private にします。入口が少ないほど、後から読むときに追いやすくなります
  • 1つのメソッドは1つの仕事に: ToggleLight() が音も鳴らして扉も開けて、となると使い回せなくなります。分けておけば、必要な組み合わせで呼べます
  • 時間差で呼ぶ方法もある: 「押して3秒後に実行」は SendCustomEventDelayedSeconds で書けます。時間差で処理を呼ぶ で扱います
  • これはまだローカルです: どちらのボタンを押しても、変わるのは押した人の画面だけです。全員に伝えるには ネットワーク同期入門 の仕組みが必要です
  • 相手が無効だと動かない: 参照先のGameObjectを SetActive(false) にすると、そのUdonは止まります。呼んでも何も起きません

まとめ

同じ処理を2か所に書かないための形は、いつも同じです。

  • 押す側は「頼む」だけ、変える側が「処理」を持つ
  • 状態を持つのは、変える側の1か所だけ
  • 相手のクラス名で受け取れば、メソッドを直接呼べる
  • SendCustomEventpublic で引数なしのメソッドだけ。nameof で書く

作るときの問いかけは、「これと同じ処理を、あとで別の場所からも呼びたくなるか?」 です。なるなら、最初から分けておきます。

次は、時間差のある処理です。時間差で処理を呼ぶ でカウントダウンを作るか、この切り替えを全員に伝えるなら ネットワーク同期入門 へ進んでください。

VRChat このセクションのノート63