【VRChat】UdonSharpのイベントと実行順:回る飾りを止めて確かめる

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

Start・Update・Interactがいつ動くかを、回転する飾りと停止ボタンで体験します。Time.deltaTimeが必要な理由と、Updateを使わずに済ませる考え方も解説します。

Udonのコードは、書いた順に上から下へ走るわけではありません。待ち受け です。

何かが起きたときに、その担当の場所だけが動きます。この「何か」を イベント と呼びます。ここが分からないまま書くと、「処理は正しいのに、いつまでも動かない」にぶつかります。

この記事では、回転する飾りと停止ボタンを作って、Start・Update・Interactがそれぞれいつ動くか を目で確かめます。

天井から吊るされた飾りが回り、その下に人形と壁のスイッチがある

この記事でわかること

  • Start・Update・Interactが動くタイミングの違い
  • Time.deltaTime を掛ける理由
  • Updateを使うべき場面と、避けたほうがいい場面
  • 実行順を当てにしてはいけない場所

最初のUdonSharp を試した状態から始めます。

Sponsored


3つのイベントは、動く回数が違う

まず使うのはこの3つです。動く「回数」で分けると覚えやすくなります。

Startは最初に1回、Updateは毎フレーム、Interactは押したときだけ動く
  • Start は、ワールドに入ったときに 1回だけ 動きます。初期状態をそろえる場所です
  • Update は、毎フレーム 動きます。90fpsなら1秒に90回です
  • Interact は、そのオブジェクトが 使われたときだけ 動きます
Start・Interact・Updateがそれぞれ担当する処理の役割

この3つで、たいていのギミックは書けます。「毎フレーム動かし続けたいもの」はUpdate、「押されたときだけ変えたいもの」はInteract、「最初にそろえておきたいもの」はStartです。

書く前に決めるのは、文法ではなく 「どのタイミングで動かしたいか」 です。ここが決まれば、書く場所も決まります。

Sponsored

実践:回る飾りを、ボタンで止める

天井から吊るした飾りがゆっくり回り、ボタンを押すと止まります。もう一度押すと回り出します。

完成すると、Consoleに Start が1回だけ出て、押すたびに Interact のログが増え、飾りは回り続けます。同じシーンの中で、1回だけの処理と毎フレームの処理が同居している のが目で見えます。

1. 飾りとボタンを置く

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

名前作り方と設定
SpinDisplayCube。Position (0, 1.5, 2)、Scale (1, 0.1, 0.3)
SpinButtonCube。Position (0, 1, 1)、Scale (0.4, 0.4, 0.4)
回る板とボタンの配置

SpinDisplay は板状にしておくと、回転しているのが分かりやすくなります。ボタンは手前に置いて、押しやすい位置にします。

2. コードを書く

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

using UdonSharp;
using UnityEngine;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class RotationSwitch : UdonSharpBehaviour
{
    [SerializeField] private Transform display;             // 回す相手
    [SerializeField] private float degreesPerSecond = 45f;  // 1秒あたり何度回すか

    private bool isRotating = true;
    private bool ready;

    // 入ったときに1回だけ
    private void Start()
    {
        ready = display != null;
        if (!ready)
        {
            Debug.LogWarning("[RotationSwitch] Displayが未設定です。");
            return;
        }
        Debug.Log("[RotationSwitch] Start");
    }

    // 押されたときだけ
    public override void Interact()
    {
        if (!ready) return;
        isRotating = !isRotating;
        Debug.Log("[RotationSwitch] rotating=" + isRotating);
    }

    // 毎フレーム
    private void Update()
    {
        if (!ready || !isRotating) return;
        display.Rotate(0f, degreesPerSecond * Time.deltaTime, 0f, Space.Self);
    }
}

Update() の中身に注目してください。Time.deltaTime を掛けています。

これは「前のフレームからの経過秒数」です。掛けないと、1フレームごとに45度ずつ回ってしまうので、120fpsのPCでは1秒に5400度、30fpsのPCでは1350度になります。同じワールドなのに、人によって回転速度が違う という状態です。

Time.deltaTime を掛けると、「1秒あたり45度」という意味になり、どのPCでも同じ速さになります。Updateの中で何かを動かすときは、必ず掛ける と覚えてください。

もう1つ、Update() の最初に if (!isRotating) return; を置いています。止まっているときに回転の計算をしないための書き方です。

3. 割り当てて動かす

SpinButton に Udon Behaviour を追加し、Program Source に RotationSwitch を指定します。

指定するもの
DisplayHierarchyの SpinDisplay
Degrees Per Second45
Interaction Text回転を切り替える

Playして確認します。

タイミング見えるはずの結果
Playした瞬間Consoleに Start1回だけ 出る。飾りが回り始める
ボタンを押すConsoleに rotating=False。飾りが止まる
もう一度押すrotating=True。また回り出す
そのまま眺めるConsoleは増えないのに、飾りは回り続けている

最後の1行が、この記事のポイントです。ログが増えなくてもUpdateは動いています。 毎フレームの処理は、目に見えないところで走り続けます。

4. deltaTimeを外して比べる

Time.deltaTime を一時的に外して、display.Rotate(0f, degreesPerSecond, 0f, Space.Self); にしてみてください。

猛烈な速さで回ります。1フレームごとに45度なので当然です。確認したら戻してください。

この違いを一度見ておくと、他の記事で deltaTime が出てきたときに、何のためのものか迷わなくなります。

Updateは、使うと決めてから使う

Updateは便利ですが、VRChatでは慎重に使います。理由は、同じ処理が全員のPCで走る からです。

VRなら1秒に90回。それが20個のオブジェクトに書いてあれば、1秒に1800回です。さらにワールドに10人いれば、10人分のPCでそれぞれ1800回動いています。

だから、判断はこうなります。

  • Updateが向くもの: 回転し続ける飾り、常に上下する浮遊物、毎フレーム位置を追いかける処理
  • Updateが向かないもの: 押されたら1回だけ変わるもの、数秒に1回でいいもの、条件を監視するだけのもの

「1秒に1回、何かを確認したい」ような処理は、Updateで毎フレーム確認する必要はありません。押されたときに動かすか、時間差で呼ぶ仕組みを使います(遅延イベント で扱います)。

判断の目安は、「これは本当に毎フレーム動く必要があるか?」 です。答えがノーなら、Updateは使いません。

実行順は当てにしない

もう1つ、はまりやすい落とし穴があります。

複数のスクリプトのStartが、どの順で動くかは決まっていません。 AのStartでBの値を読もうとしても、そのときBのStartがまだ走っていないかもしれません。

どのStartが先に動くかは決まっていない

対処はシンプルです。

  • Startには、自分の中で完結する初期化 だけを書く
  • 他のオブジェクトの値が必要なら、Startで読むのではなく、必要になったときに読む

同じ理由で、OnEnableOnDisable も気をつけます。GameObjectを無効にすると OnDisable が呼ばれ、そのオブジェクトのUpdateも止まります。有効に戻すと OnEnable が呼ばれますが、Start は2回目は呼ばれません。

「表示を消して軽くしよう」と思ってボタン自体を無効にすると、押せなくなって元に戻せません。無効にするのは、操作の対象であって、操作するボタンではありません。

Sponsored

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

  • Space.SelfSpace.World: 回転の基準が変わります。Self はそのオブジェクト自身の向き、World はワールドの座標です。傾いた物を回すときに結果が変わるので、思った向きに回らないときはここを疑います
  • FixedUpdateは物理向け: 一定の間隔で呼ばれるイベントで、物理演算に合わせた処理に使います。見た目を動かすだけなら不要です
  • Awakeは使わない: 通常のUnityで使う Awake は、UdonSharpでは使いません。初期化は Start に書きます
  • 時間差で呼ぶ方法がある: 「3秒後に何かをする」という処理は、Updateで秒数を数えるより、遅延イベントを使うほうが軽くて読みやすくなります
  • 重さを測る手段がある: 実際にどのくらい負荷がかかっているかは、後で計測できます。Udonと通信の最適化 で扱います

まとめ

イベントは、処理が始まるきっかけです。

  • Startは入ったとき1回、Updateは毎フレーム、Interactは押されたときだけ
  • Updateの中で動かすときは Time.deltaTime を掛ける
  • Updateは全員のPCで走る。本当に毎フレーム必要かを考えてから使う
  • 複数のスクリプトのStartの順番は決まっていない。他人の値をStartで読まない

書く場所に迷ったときの問いかけは、「これは、いつ動いてほしいのか?」 です。1回か、毎フレームか、押されたときか。それが決まれば、書く場所も決まります。

次は、処理を分けて使い回す方法です。メソッドとカスタムイベント で、2つのボタンから同じライトを操作してみましょう。1つの状態から複数の反応をそろえるなら、ボタンで切り替える が実践的です。

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