【VRChat】ボタンで照明・音・表示を切り替える:1つの状態から反応をそろえる

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

1つのboolから、ライト・BGM・表示の3つをまとめて切り替えます。状態と反応を分ける設計、Startで初期状態をそろえる理由、ボタン自身を消してしまう失敗の避け方を解説します。

ワールドに「スイッチを押したら雰囲気が変わる」を作りたくなります。照明が落ちて、BGMが止まって、閉店の札が出る。

ところが3つを別々に書くと、すぐにずれます。ライトは消えたのに音が鳴り続ける。もう一度押したら、今度は札だけが残る。押すたびに、どれかが取り残されます。

この記事では、1つの状態から3つの反応をそろえて切り替える 書き方を解説します。

壁のスイッチを押すと、照明とスピーカーと壁の板が同時に切り替わる

この記事でわかること

  • 「状態は1つ、反応は複数」という書き方
  • 起動時に見た目をそろえておく理由
  • ボタン自身を消してしまう失敗の避け方
  • Light.enabledSetActive の使い分け

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

Sponsored


状態は1つ、反応は3つ

ずれる原因は、いつも同じです。それぞれの反応が、自分の状態を持ってしまう からです。

ライトが「点いているか」を覚え、音が「鳴っているか」を覚え、札が「出ているか」を覚える。3つの記憶が別々にあると、どこかで食い違います。

解決は単純です。覚えるのは1つだけにします。

isOnという1つの値から、ライト・音・表示の3つへ分かれる

isOn という真偽の値を1つ持ち、その値を見て3つの見た目を作ります。押されたら isOn を反転して、また3つを作り直します。

こうすると、どこから何度呼んでも結果が同じになります。「いまの状態を、見た目に反映するだけ」の処理だからです。

押す操作、覚えておく状態、反映する反応の3つを分ける

この形は、このあと何度も出てきます。同期を入れるときも、途中参加の人に状態を伝えるときも、土台はこれと同じです。

Sponsored

実践:照明・音・表示をまとめて切り替える

ボタンを1回押すと、ライトが点き、BGMが流れ、壁の札が現れます。もう一度押すと、3つとも同時に消えます。

1. ボタンと3つの対象を置く

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

名前作り方と設定
RoomButtonCube。Position (0, 1, 1)、Scale (0.4, 0.4, 0.4)
RoomLightLight → Point Light。Position (0, 2.5, 0)、Range 6、Intensity 2、Mode「Realtime」
RoomSignCube。Position (0, 1.8, 2.8)、Scale (1.2, 0.5, 0.05)
RoomAudioCreate Empty に Audio Source を追加。Position (0, 1, 0)
ライト・看板・音源・ボタンの配置

RoomAudio の Audio Source には、ループする短い音を入れます。Play On Awake は必ずオフ にしてください。オンのままだと、ワールドに入った瞬間から鳴り始めて、ボタンの意味がなくなります。Loopはオンにします。

ライトのModeは Realtime にします。Bakedだと、実行中に消しても焼き込んだ光が残ります。

2. コードを書く

Assets/Scripts で「Create → U# Script」を選び、LocalRoomToggle という名前で作ります。

using UdonSharp;
using UnityEngine;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class LocalRoomToggle : UdonSharpBehaviour
{
    [SerializeField] private Light targetLight;      // 照明
    [SerializeField] private GameObject targetVisual; // 表示する札
    [SerializeField] private AudioSource targetAudio; // BGM
    [SerializeField] private bool startOn;            // 起動時にONにするか

    private bool isOn;   // 覚えるのはこの1つだけ
    private bool ready;

    private void Start()
    {
        ready = targetLight != null && targetVisual != null && targetAudio != null;
        if (!ready)
        {
            Debug.LogWarning("[LocalRoomToggle] 3つの対象を指定してください。");
            return;
        }

        // ボタン自身や親を消すと、押せなくなってしまう
        if (targetVisual == gameObject || transform.IsChildOf(targetVisual.transform))
        {
            ready = false;
            Debug.LogWarning("[LocalRoomToggle] 表示対象にボタン自身や親を指定しないでください。");
            return;
        }

        isOn = startOn;
        ApplyState();   // 起動時に、見た目を状態へそろえる
    }

    public override void Interact()
    {
        if (!ready) return;
        isOn = !isOn;
        ApplyState();
        Debug.Log("[LocalRoomToggle] isOn=" + isOn);
    }

    // 状態を見て、3つの見た目を作る。何度呼んでも同じ結果になる
    private void ApplyState()
    {
        targetLight.enabled = isOn;
        targetVisual.SetActive(isOn);

        if (isOn)
        {
            if (!targetAudio.isPlaying) targetAudio.Play();
        }
        else
        {
            targetAudio.Stop();
        }
    }
}

見どころが3つあります。

ApplyState() に反応をまとめています。 ここには「切り替える」処理を書きません。いまの isOn を見て、そのとおりの見た目を作る だけです。だから起動時にも押されたときにも、同じ関数を呼べます。

音は isPlaying を確認してから鳴らします。 すでに鳴っているときに Play() を呼ぶと、頭から鳴り直してしまいます。ループするBGMでは、これがぶつ切りに聞こえます。

ボタン自身を対象にしていないか確認しています。 targetVisual にボタン自身を入れると、押した瞬間に自分が消えて、二度と押せなくなります。この確認があると、設定ミスのときに理由がConsoleに出ます。

3. 参照を割り当てる

RoomButton に Udon Behaviour を追加し、Program Source に LocalRoomToggle を指定します。

指定するもの
Target LightHierarchyの RoomLight
Target VisualHierarchyの RoomSign
Target AudioHierarchyの RoomAudio
Start Onオフ(消えた状態から始める)
Interaction Text部屋の設定を切り替える
Hierarchyから3つの欄へドラッグして割�り当てる

Start On は、起動時の状態を決める設定です。オンにすれば、入った瞬間から明るくてBGMが流れている部屋になります。

4. 動かして確かめる

Playして、RoomButton を押します。

タイミング期待する結果
Playした直後ライトが消えていて、札が見えず、音も鳴っていない
1回押す3つとも同時にONになる。Consoleに isOn=True
もう一度押す3つとも同時にOFFになる。isOn=False
何度も押す毎回3つがそろって切り替わる
ボタンを押すたびに部屋の照明が切り替わる

以前の作例を撮影したものです。この記事では照明に加えて音と表示も同時に切り替わります。

Start Onをオンにして、もう一度Playしてみてください。 入った瞬間から3つともONになります。Start()ApplyState() を呼んでいるからです。この1行がないと、Inspectorでライトを消しておいたのに札だけ出ている、といった食い違いが起きます。

切り替える対象の選び方

Light.enabledGameObject.SetActive() は似ていますが、効く範囲が違います。

方法何が止まるか使いどころ
Light.enabled = falseライトの発光だけ照明だけ消して、ランプの見た目は残す
GameObject.SetActive(false)そのオブジェクトの全部と子札やモデルごと消す

SetActive(false) にしたオブジェクトは、付いているUdonも止まります。 Updateも動かず、外から呼びかけることもできません。だから、消したオブジェクトを自力で復活させることはできません。

これが「ボタンを押したら押せなくなった」の正体です。消す対象と、操作するボタンは分けます。

同じ型は、いろいろな場面で使えます。

  • カフェや居酒屋のワールド: 営業と閉店。照明・店内BGM・看板をまとめて切り替える
  • ギャラリー: 作品のスポットライト・環境音・キャプションの板
  • ホラーワールド: 山小屋の電灯。点けると机の上のメモが見えて、消すと闇に戻る
  • ステージ: 客席の照明を落とし、舞台照明と効果音、幕を出す

作例は1部屋・1スイッチ・3反応ですが、対象を差し替えるだけでこれらになります。

Sponsored

うまくいかないときは

  • 押しても何も起きない → Consoleに isOn= のログが出ているか。出ないならInteractまで届いていません
  • どれか1つだけ変わらない → その参照が None か、対象を間違えています。起動時の警告も確認します
  • 押すたびに音が最初から鳴り直すisPlaying の確認を飛ばしています
  • 入った瞬間からずれているStart()ApplyState() を呼んでいるか確認します
  • 押したら押せなくなった → 表示対象にボタン自身か親を指定しています

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

  • これはローカルの処理です: 押した人の画面だけが変わります。全員でそろえるには ネットワーク同期入門 の仕組みが必要です
  • 一度きりの処理は別の形: 「1回押したら二度と戻らないレバー」は、isOn の反転では作れません。押したかどうかを別に覚えます
  • 時間差の演出も別の形: 「点いてから2秒後に音」は、状態1つでは表せません。遅延イベント を使います
  • Updateで毎フレーム反映しない: Update() の中に targetLight.enabled = isOn; と書いても動きますが、毎フレーム同じ処理を繰り返すだけで無駄です。変わったときだけ反映します
  • 3D空間の音にはVRC Spatial Audio Source: 部屋のどこにいても同じ音量でいいならAudio Sourceだけで足りますが、近づくと大きくなる音にしたいときは追加します。BGMと空間音響 で扱います

まとめ

複数の反応をそろえる書き方は、1つの型で済みます。

  • 覚えるのは状態1つ。反応はその状態から作る
  • 反映する処理を関数にまとめ、起動時と操作時の両方から呼ぶ
  • Light.enabled は光だけ、SetActive はオブジェクト全部
  • 消したオブジェクトのUdonは止まる。ボタン自身を消さない

作るときの問いかけは、「この見た目は、どの状態から決まるのか?」 です。答えが1つの値になれば、何個の反応を足してもずれません。

次は、複数のボタンから同じ対象を操作します。メソッドとカスタムイベント へ進むか、触れる操作の種類を見渡すなら プレイヤーの操作を受け取る、この切り替えを全員に伝えるなら ネットワーク同期入門 へ進んでください。

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