【VRChat】外部テキストで掲示板を更新する:失敗しても前の案内を残す

作成: 2026-09-08最終更新: 2026-09-09

Web上のテキストをワールドの掲示板に表示します。頼んであとから受け取る仕組み、本文と状態表示を分ける理由、読み込みに失敗しても案内が消えない作り方を解説します。

イベントの開始時刻が変わるたびに、ワールドをアップロードし直している。掲示板の文字を1行変えたいだけなのに、毎回ビルドが必要です。

案内文をWebに置いて、ワールドがそれを読みに行く形にすれば、この手間はなくなります。テキストファイルを書き換えるだけで掲示が変わります。

この記事では、Web上の文章を読んで表示する掲示板 を作ります。読み込みに失敗しても、前の案内が消えないようにします。

Web上の文章が、ワールドの掲示板に届いている

この記事でわかること

  • 頼んで、あとから受け取るという流れ
  • 本文と状態表示を分ける理由
  • 失敗したときに前の内容を残す作り
  • 読み込みの間隔を空ける理由

ネットワーク同期入門 を試した状態から始めます。

Sponsored


頼んでから、届くまでに間がある

いつものコードなら、値は呼んだその場で返ってきます。

int a = GetSomething();   // ここで結果が手に入る

Webから読むときは、そうなりません。注文して、あとから届きます。

読み込みを頼み、あとから成功か失敗のどちらかが届く

書き方もそれに合わせた形になります。

// 「取りに行って」と頼むだけ。ここでは何も返らない
VRCStringDownloader.LoadUrl(noticeUrl, (IUdonEventReceiver)this);

結果は、あとから呼ばれるメソッドで受け取ります。

メソッド呼ばれるとき
OnStringLoadSuccess(IVRCStringDownload result)読めた。result.Result に文章が入っている
OnStringLoadError(IVRCStringDownload result)読めなかった。result.Error に理由が入っている

出口が2つあること、そして どちらか片方しか通らないこと を頭に入れておいてください。ここを意識すると、次の話がすんなり入ります。

URLの扱いは動画と同じです。渡すのは VRCUrl 型で、実行中に文字列から作ることはできません。インスペクタに入れておくか、プレイヤーが入力欄に貼ったものを使います。

もう1つ、読み込みには間隔の制限があります。 連打すると弾かれるので、自分で間隔を空ける作りにします。

Sponsored

本文と、状態を分ける

ここが、この記事でいちばん伝えたい設計です。

掲示板のテキストを1つしか置かないと、こういうコードを書きたくなります。

// これをすると、案内が消える
noticeText.text = "読み込み中...";

そして読み込みに失敗すると、掲示板には「読み込み中」だけが残ります。昨日まで出ていた案内が、来場者から見えなくなります。

本文は成功したときだけ差し替え、状態は別の欄に出す

欄を2つに分ければ解決します。

いつ変えるか
本文読み込みに成功したときだけ
状態読み込みを始めたとき、成功したとき、失敗したとき

こうしておくと、通信が不安定な日でも掲示は生きたままです。状態の欄に小さく「更新できませんでした」と出るだけで、案内文はそのまま読めます。

古い情報でも、何も出ていないよりはるかにましです。 これは掲示板に限らず、外から何かを持ってくる仕掛け全般に当てはまります。

実践:お知らせ掲示板を作る

Webに置いた文章を掲示板に出して、更新ボタンで読み直せるようにします。

1. 掲示板を置く

床のあるシーンに、次を置きます。

名前作り方と設定
NoticeCanvasUI Canvas(World Space)。(0, 2, 4)、Scale (0.004, 0.004, 0.004)
NoticeTextNoticeCanvas の子に TextMeshPro。大きめの文字
StatusTextNoticeCanvas の子に TextMeshPro。小さめの文字、下端に配置
UpdateButtonCube。(0, 1, 3)、Scale (0.4, 0.4, 0.4)
本文と状態欄を分けた掲示板の配置

NoticeText には、あらかじめ「ようこそ」など、何か文章を入れておきます。読み込む前でも掲示板が空にならないようにするため です。

2. 読ませる文章を用意する

テキストを置く場所は、次の条件を満たすものにします。

  • その文章そのものが返ってくること(ページの装飾やHTMLが混ざらない)
  • HTTPSであること

手軽なのはGitHub Gistです。テキストを1つ作って公開し、Rawボタンを押したときのURLを使います。GitHub Pagesに .txt を置く方法でも構いません。

ファイルの閲覧ページのURLは使えません。 それはWebページであって、文章そのものではないためです。

3. コードを書く

Assets/ScriptsNoticeBoard を作ります。

using UdonSharp;
using UnityEngine;
using TMPro;
using VRC.SDK3.StringLoading;
using VRC.SDKBase;
using VRC.Udon.Common.Interfaces;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class NoticeBoard : UdonSharpBehaviour
{
    [SerializeField] private VRCUrl noticeUrl;
    [SerializeField] private TextMeshProUGUI noticeText;   // 本文
    [SerializeField] private TextMeshProUGUI statusText;   // 状態
    [SerializeField] private float cooldownSeconds = 10f;

    private float nextAllowedTime;

    private void Start()
    {
        Fetch();
    }

    // 更新ボタンから呼ぶ
    public void RequestUpdate()
    {
        if (Time.time < nextAllowedTime)
        {
            SetStatus("少し待ってから、もう一度");
            return;
        }
        Fetch();
    }

    private void Fetch()
    {
        nextAllowedTime = Time.time + cooldownSeconds;
        SetStatus("読み込み中...");

        // 頼むだけ。結果はあとから届く
        VRCStringDownloader.LoadUrl(noticeUrl, (IUdonEventReceiver)this);
    }

    public override void OnStringLoadSuccess(IVRCStringDownload result)
    {
        // 本文を差し替えるのは、ここだけ
        if (noticeText != null) noticeText.text = result.Result;
        SetStatus("更新しました");
    }

    public override void OnStringLoadError(IVRCStringDownload result)
    {
        // 本文には触らない
        SetStatus("更新できませんでした");
        Debug.LogWarning("[NoticeBoard] " + result.Error);
    }

    private void SetStatus(string message)
    {
        if (statusText != null) statusText.text = message;
    }
}

読みどころは2つです。

noticeText に触っているのは1か所だけ です。成功したときにしか本文が変わらないことが、コードを眺めるだけで分かります。設計を形で表しておくと、あとから機能を足しても壊れにくくなります。

nextAllowedTime は、読み込みを始める前に更新しています。 成功してから更新すると、失敗したときに間隔が空かず、連打できてしまいます。

4. つなぐ

NoticeCanvas に Udon Behaviour を追加し、NoticeBoard を指定します。

指定するもの
Notice Url用意したテキストの直リンク
Notice TextNoticeText
Status TextStatusText
本文と状態、2つのテキストを別々の欄へ

UpdateButton には中継のスクリプトを付けます。

using UdonSharp;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class NoticeUpdateButton : UdonSharpBehaviour
{
    [SerializeField] private NoticeBoard board;

    public override void Interact()
    {
        if (board != null) board.RequestUpdate();
    }
}

5. 確かめる

Build & Test で起動します。

順序操作期待する結果
1入る掲示板にWebの文章が出て、状態が「更新しました」
2Web側のファイルを書き換えて、更新ボタンを押す新しい文章に変わる
3更新ボタンを連打する「少し待ってから、もう一度」が出る
4URLを存在しないものに変えて、入り直す本文は最初に入れた文章のまま。状態だけ「更新できませんでした」

4番目が、この記事のいちばん大事な確認です。 読み込みに失敗しても、掲示板は読める状態のまま残ります。

Sponsored

うまくいかないときは

  • 何も表示されない → URLが閲覧ページになっていないか確認します。文章そのものが返るURLが必要です
  • HTMLのタグが表示される → Webページを読んでいます。テキストファイルの直リンクにします
  • 連打すると読めなくなる → 制限に当たっています。間隔を空ける処理を確認します
  • 文字化けする → ファイルの文字コードをUTF-8にします
  • エディタでは動くのに、ビルドすると読めない → URLの許可設定を確認します。まずは自分だけのインスタンスで試します
  • 失敗すると本文が消えるOnStringLoadError の中で本文を触っています

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

  • 読み込みは各自が行う: 誰かが更新ボタンを押しても、他の人の掲示板は変わりません。全員をそろえたいなら、読み込みのきっかけを Network Events で全員に送ります
  • 区切り文字を決めると項目を分けられる: 1行目をタイトル、2行目以降を本文、のように決めておけば、Split() で分けて別々の欄に出せます。複雑にしたくなったらJSONも使えますが、まずは行で足ります
  • エディタで試すときも本物のURLで: ローカルのファイルパスは読めません。公開されているURLで確かめてください
  • 文章は短めに: 掲示板に出す量なら問題になりませんが、何万文字も読ませる作りにはしないでください
  • 使いどころはいろいろ: イベントの開催予定、ルール説明、更新履歴、日替わりのひとこと。「ビルドし直さずに変えたい文章」がすべて対象です

まとめ

外の文章を読む仕掛けは、頼んであとから受け取る形になります。

  • LoadUrl() は頼むだけ。結果は成功と失敗の2つの出口から届く
  • 本文は成功したときだけ差し替える。失敗しても案内を消さない
  • 状態は別の欄に出す
  • 読み込みの間隔は自分で空ける

作るときの問いかけは、「読めなかったとき、来場者には何が見えるか」 です。前の案内が見えているなら、その設計は正しいです。

外の画像を貼るなら 外部画像でポスターを貼り替える、動画を流すなら 動画プレイヤーを置く へ進んでください。

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