イベントの 開始時刻が変わるたびに、ワールドをアップロードし直している。掲示板の文字を1行変えたいだけなのに、毎回ビルドが必要です。
案内文をWebに置いて、ワールドがそれを読みに行く形にすれば、この手間はなくなります。テキストファイルを書き換えるだけで掲示が変わります。
この記事では、Web上の文章を読んで表示する掲示板 を作ります。読み込みに失敗しても、前の案内が消えないようにします。
この記事でわかること
- 頼んで、あとから受け取るという流れ
- 本文と状態表示を分ける理由
- 失敗したときに前の内容を残す作り
- 読み込みの間隔を空ける理由
ネットワーク同期入門 を試した状態から始めます。
頼んでから、届くまでに間がある
いつものコードなら、値は呼んだその場で返ってきます。
int a = GetSomething(); // ここで結果が手に入る
Webから読むときは、そうなりません。注文して、あとから届きます。

書き方もそれに合わせた形になります。
// 「取りに行って」と頼むだけ。ここでは何も返らない
VRCStringDownloader.LoadUrl(noticeUrl, (IUdonEventReceiver)this);
結果は、あとから呼ばれるメソッドで受け取ります。
| メソッド | 呼ばれるとき |
|---|---|
OnStringLoadSuccess(IVRCStringDownload result) | 読めた。result.Result に文章が入っている |
OnStringLoadError(IVRCStringDownload result) | 読めなかった。result.Error に理由が入っている |
出口が2つあること、そして どちらか片方しか通らないこと を頭に入れておいてください。ここを意識すると、次の話がすんなり入ります。
URLの扱いは動画と同じです。渡すのは VRCUrl 型で、実行中に文字 列から作ることはできません。インスペクタに入れておくか、プレイヤーが入力欄に貼ったものを使います。
もう1つ、読み込みには間隔の制限があります。 連打すると弾かれるので、自分で間隔を空ける作りにします。
本文と、状態を分ける
ここが、この記事でいちばん伝えたい設計です。
掲示板のテキストを1つしか置かないと、こういうコードを書きたくなります。
// これをすると、案内が消える
noticeText.text = "読み込み中...";
そして読み込みに失敗すると、掲示板には「読み込み中」だけが残ります。昨日まで出ていた案内が、来場者から見えなくなります。

欄を2つに分ければ解決します。
| 欄 | いつ変えるか |
|---|---|
| 本文 | 読み込みに成功したときだけ |
| 状態 | 読み込みを始めたとき、成功したとき、失敗したとき |
こうしてお くと、通信が不安定な日でも掲示は生きたままです。状態の欄に小さく「更新できませんでした」と出るだけで、案内文はそのまま読めます。
古い情報でも、何も出ていないよりはるかにましです。 これは掲示板に限らず、外から何かを持ってくる仕掛け全般に当てはまります。
実践:お知らせ掲示板を作る
Webに置いた文章を掲示板に出して、更新ボタンで読み直せるようにします。
1. 掲示板を置く
床のあるシーンに、次を置きます。
| 名前 | 作り方と設定 |
|---|---|
NoticeCanvas | UI Canvas(World Space)。(0, 2, 4)、Scale (0.004, 0.004, 0.004) |
NoticeText | NoticeCanvas の子に TextMeshPro。大きめの文字 |
StatusText | NoticeCanvas の子に TextMeshPro。小さめの文字、下端に配置 |
UpdateButton | Cube。(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/Scripts で NoticeBoard を作ります。
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 Text | NoticeText |
| Status Text | StatusText |

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の文章が出て、状態が「更新しました」 |
| 2 | Web側のファイルを書き換えて、更新ボタンを押す | 新しい文章に変わる |
| 3 | 更新ボタンを連打する | 「少し待ってから、もう一度」が出る |
| 4 | URLを存在しないものに変えて、入り直す | 本文は最初に入れた文章のまま。状態だけ「更新できませんでした」 |
4番目が、この記事のいちばん大事な確認です。 読み込みに失敗しても、掲示板は読める状態のまま残ります。
うまくいかないときは
- 何も表示されない → URLが閲覧ページになっていないか確認します。文章そのものが返るURLが必要です
- HTMLのタグが表示される → Webページを読んでいます。テキストファイルの直リンクにします
- 連打すると読めなくなる → 制限に当たっています。間隔を空ける処理を確認します
- 文字化けする → ファイルの文字コードをUTF-8にします
- エディタでは動くのに、ビルドすると読めない → URLの許可設定を確認します。まずは自分だけ のインスタンスで試します
- 失敗すると本文が消える →
OnStringLoadErrorの中で本文を触っています
おまけ:先に知っておくと良いこと
- 読み込みは各自が行う: 誰かが更新ボタンを押しても、他の人の掲示板は変わりません。全員をそろえたいなら、読み込みのきっかけを Network Events で全員に送ります
- 区切り文字を決めると項目を分けられる: 1行目をタイトル、2行目以降を本文、のように決めておけば、
Split()で分けて別々の欄に出せます。複雑にしたくなったらJSONも使えますが、まずは行で足ります - エディタで試すときも本物のURLで: ローカルのファイルパスは読めません。公開されているURLで確かめてください
- 文章は短めに: 掲示板に出す量なら問題になりませんが、何万文字も読ませる作りにはしないでください
- 使いどころはいろいろ: イベントの開催予定、ルール説明、更新履歴、日替わりのひとこと。「ビルドし直さずに変えたい文章」がすべて対象です
まとめ
外の文章を読む仕掛けは、頼んであとから受け取る形になります。
LoadUrl()は頼むだけ。結果は成功と失敗の2つの出口から届く- 本文は成功したときだけ差し替える。失敗しても案内を消さない
- 状態は別の欄に出す
- 読み込みの間隔は自分で空ける
作るときの問いかけは、「読めなかったとき、来場者には何が見えるか」 です。前の案内が見えているなら、その設計は正しいです。
外の画像を貼るなら 外部画像でポスターを貼り替える、動画を流すなら 動画プレイヤーを置く へ進んでください。