部屋の入口と 奥の両方に照明スイッチを置きたい。どちらを押しても同じライトが切り替わってほしい。
素直に作ると、両方のボタンに同じコードをコピーすることになります。動きはしますが、あとで「明るさを変えたい」となったときに、2か所を直す羽目になります。片方を直し忘れれば、押す場所によって挙動が違う部屋になります。
この記事では、処理をライト側に1つだけ持たせて、ボタンはそれを呼ぶだけ にする書き方を解説します。
この記事でわかること
- 処理を1か所にまとめる設計
- 別のUdonのメソッドを直接呼ぶ方法
SendCustomEventとの使い分けnameofを使う理由
変数と参照 を試した状態から始めます。
押す側と、変える側を分ける
同じ処理を2か所に書かないための考え方は、役割を分ける ことです。

- ボタン側 は「押された」ことだけを担当します。ライトの中身は知りません
- ライト側 は「切り替える」処理だけを持ちます。誰が押したかは知りません
こうすると、ボタンを3個に増やしても、ライトの処理は1つのままです。明るさを変えたくなったら、ライト側の1か所を直せば全部に効きます。

この形は、ワールドが大きくなるほど効いてきます。入口・奥・リモコンの3か所から同じ扉を開けたいときも、扉側に処理を置いておけば済みます。
呼ぶ側が増えても、変える側は増えない。これがこの記事でいちばん大事な考え方です。
実践:2つのボタンから1つのライトを操作する
入口と奥にボタンを置き、どちらを押しても同じライトが切り替わるようにします。
1. ライトとボタンを置く
床のあるシーンに3つ置きます。
| 名前 | 作り方と設定 |
|---|---|
RoomLight | Light → Point Light。(0, 2.5, 0)、Range 6、Intensity 2、Mode「Realtime」 |
ButtonFront | Cube。(-1.5, 1, -1)、Scale (0.4, 0.4, 0.4) |
ButtonBack | Cube。(1.5, 1, 2)、Scale (0.4, 0.4, 0.4) |

ライトを操作する側のオブジェクトも用意します。「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つのボタンに割り当てる
ButtonFront と ButtonBack の両方に、Udon Behaviour を追加して RoomLightButton を指定します。
どちらも Controller に LightController をドラッグ します。同じ相手を指します。
| 欄 | 指定するもの |
|---|---|
| Controller | Hierarchyの LightController |
| Interaction Text | 照明を切り替える |
5. 動かして確かめる
Playして、両方のボタンを押してみてください。
| 操作 | 期待する結果 |
|---|---|
| 入口のボタンを押す | ライトが明るくなる。Consoleに bright=True |
| 奥のボタンを押す | ライトが暗くなる。bright=False |
| また入口のボタンを押す | 明るくなる |
どちらのボタンからでも、同じ状態が切り替わっています。 状態を持っているのはライト側1か所だけなので、押す場所が変わってもずれません。
ここで、brightIntensity を 5 に変えてみてください。ボタンのコードは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 |
| 他のプレイヤーにも実行させたい | SendCustomNetworkEvent(Network Events) |
この記事の場面では、型付き参照のほうが簡単で安全 です。SendCustomEventは、いろいろな種類のスクリプトを同じ扱いで呼びたいときや、ネットワーク越しに呼びたいときに使います。
引数を渡したいときは、先に相手の変数へ値を入れてから呼びます。
controller.targetBrightness = 3f; // 先に値を入れる(publicな変数が必要)
controller.ApplyBrightness(); // それから呼ぶ
型付き参照なら、そもそも引数付きのメソッドをそのまま呼べます。ここでも型付き参照のほうが素直です。
うまくいかないときは
- 押しても何も起きない → Controllerが
Noneになっていないか。起動時の警告も確認します - 片方のボタンだけ効かない → そのボタンのControllerが空か、Udon Behaviourが付いていません
- SendCustomEventで呼べない → 呼ばれる側のメソッドが
publicか、引数を取っていないかを確認します - 押すたびに別々の状態になる → 状態をボタン側に持たせています。状態は変える側(Controller)に1つだけ置きます
おまけ:先に知っておくと良いこと
- 入口は少なく、中は自由に: 外から呼ばれるメソッドだけ
publicにして、あとはprivateにします。入口が少ないほ ど、後から読むときに追いやすくなります - 1つのメソッドは1つの仕事に:
ToggleLight()が音も鳴らして扉も開けて、となると使い回せなくなります。分けておけば、必要な組み合わせで呼べます - 時間差で呼ぶ方法もある: 「押して3秒後に実行」は
SendCustomEventDelayedSecondsで書けます。時間差で処理を呼ぶ で扱います - これはまだローカルです: どちらのボタンを押しても、変わるのは押した人の画面だけです。全員に伝えるには ネットワーク同期入門 の仕組みが必要です
- 相手が無効だと動かない: 参照先のGameObjectを
SetActive(false)にすると、そのUdonは止まります。呼んでも何も起きません
まとめ
同じ処理を2か所に書かないための形は、いつも同じです。
- 押す側は「頼む」だけ、変える側が「処理」を持つ
- 状態を持つのは、変える側の1か所だけ
- 相手のクラス名で受け取れば、メソッドを直接呼べる
SendCustomEventはpublicで引数なしのメソッドだけ。nameofで書く
作るときの問いかけは、「これと同じ処理を、あとで別の場所からも呼びたくなるか?」 です。なるなら、最初から分けておきます。
次は、時間差のある処理です。時間差で処理を呼ぶ でカウントダウンを作るか、この切り替えを全員に伝えるなら ネットワーク同期入門 へ進んでください。