【VRChat】外部画像でポスターを貼り替える:頼む係を持ち続ける

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

Web上の画像をワールドのパネルに表示します。ダウンローダーをフィールドで保持する理由、Disposeの扱い、失敗しても前の絵を残す作り方を解説します。

ワールドの壁にポスターを貼りたい。ただし、内容は月ごとに変えたい。

画像をプロジェクトに入れてしまうと、貼り替えのたびにビルドとアップロードが必要になります。Webに置いた画像を読みに行く形にすれば、ファイルを差し替えるだけで済みます。

この記事では、Web上の画像を1枚、壁のパネルに表示する ところまでを作ります。

壁のパネルに、Webから届いたポスターが貼られている

この記事でわかること

  • 画像を頼んで、あとから受け取る流れ
  • ダウンローダーを持ち続ける理由
  • Dispose() をいつ呼ぶか
  • 失敗しても前の絵を残す作り

外部テキストで掲示板を更新する を試した状態から始めます。

Sponsored


絵も、頼んであとから届く

流れは文章のときと同じです。頼んで、あとから成功か失敗のどちらかが届きます。

頼んで、成功なら貼り替わり、失敗なら前の絵が残る
メソッド呼ばれるとき
OnImageLoadSuccess(IVRCImageDownload result)読めた
OnImageLoadError(IVRCImageDownload result)読めなかった

違うのは、貼り先をあらかじめ渡しておける ことです。

downloader.DownloadImage(posterUrl, posterMaterial, (IUdonEventReceiver)this, textureInfo);

2つ目にマテリアルを渡しておくと、読み込みに成功した時点で そのマテリアルにもう貼られています。 成功したときに自分で貼り直す処理は要りません。

失敗したときも都合がよくなります。貼り替えが起きないので、前の絵がそのまま残ります。 掲示板で「本文は成功したときだけ差し替える」と決めたのと同じ結果が、何もしなくても手に入ります。

扱える画像には条件があります。

  • PNG または JPEG の直リンク
  • 2048ピクセル四方あたりが上限の目安
  • URLは VRCUrl 型。実行中に文字列からは作れない
Sponsored

頼む係を持ち続ける

ここが、画像の読み込みでいちばんつまずくところです。

VRCImageDownloader は自分で new して作ります。このとき、ローカル変数にしてはいけません。

// これは動かない
private void Fetch()
{
    VRCImageDownloader downloader = new VRCImageDownloader();
    downloader.DownloadImage(posterUrl, posterMaterial, (IUdonEventReceiver)this, textureInfo);
}

メソッドを抜けた時点で、この変数はどこからも参照されなくなります。すると、まだ届いていない画像ごと片付けられます。

ダウンローダーを持ち続けると絵が残り、捨てると絵も消える

頼んだ本人がその場を離れると、届いた荷物を受け取る人がいなくなる。そう考えると覚えやすいと思います。

正しくはフィールドで持ちます。

private VRCImageDownloader downloader;   // ずっと持っておく

private void Start()
{
    downloader = new VRCImageDownloader();
}

そして、ワールドを離れるときに片付けます。

private void OnDestroy()
{
    if (downloader != null) downloader.Dispose();
}

Dispose() は「もう使いません」という宣言です。表示中に呼んではいけません。 呼んだ瞬間、そのダウンローダーが持っている画像も一緒に消えます。呼ぶのは片付けのときだけです。

表示中には呼ばない。片付けのときだけ呼ぶ

実践:ポスターパネルを作る

壁のパネルにWebの画像を表示して、更新ボタンで読み直せるようにします。

1. パネルを用意する

Projectウィンドウで右クリックし、「Create → Material」で PosterMaterial を作ります。Shaderを Unlit/Texture にします。

シーンに次を置きます。

名前作り方と設定
PosterPanel3D Object → Quad。(0, 2, 4)、Scale (1.4, 2, 1)。マテリアルに PosterMaterial
StatusCanvasUI Canvas(World Space)。(0, 0.7, 3.95)、Scale (0.003, 0.003, 0.003)
StatusTextStatusCanvas の子に TextMeshPro。小さめの文字
UpdateButtonCube。(1, 1, 3)、Scale (0.4, 0.4, 0.4)
ポスターのパネルと状態表示、更新ボタ��ンの配置

Quadの 1.4 × 2 はB2ポスターに近い縦長の比率です。画像の縦横比と合わせておかないと、絵が伸びます。

PosterMaterial には、あらかじめ適当な画像を入れておくと、読み込み前でもパネルが真っ白になりません。

2. コードを書く

Assets/ScriptsPosterPanel を作ります。

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

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class PosterPanel : UdonSharpBehaviour
{
    [SerializeField] private VRCUrl posterUrl;
    [SerializeField] private Material posterMaterial;
    [SerializeField] private TextMeshProUGUI statusText;
    [SerializeField] private float cooldownSeconds = 10f;

    // 頼む係。ずっと持っておく
    private VRCImageDownloader downloader;
    private TextureInfo textureInfo;

    private float nextAllowedTime;

    private void Start()
    {
        downloader = new VRCImageDownloader();

        textureInfo = new TextureInfo();
        textureInfo.GenerateMipMaps = true;   // 斜めから見てもざらつかない

        Fetch();
    }

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

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

        downloader.DownloadImage(posterUrl, posterMaterial, (IUdonEventReceiver)this, textureInfo);
    }

    public override void OnImageLoadSuccess(IVRCImageDownload result)
    {
        // マテリアルにはもう貼られている
        SetStatus("貼り替えました");
    }

    public override void OnImageLoadError(IVRCImageDownload result)
    {
        // 何もしない。前の絵がそのまま残る
        SetStatus("貼り替えられませんでした");
        Debug.LogWarning("[PosterPanel] " + result.Error);
    }

    private void OnDestroy()
    {
        if (downloader != null) downloader.Dispose();
    }

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

見どころは3つです。

downloadertextureInfo はフィールドです。 どちらも Start() で1回だけ作って、読み込みのたびに使い回します。

成功したときのメソッドが空に近いこと に注目してください。貼る処理はVRChat側がやってくれるので、こちらは状態を出すだけです。

失敗したときも、絵には何もしていません。 これで前のポスターが残ります。掲示板のときは自分で「本文を触らない」と決める必要がありましたが、画像は放っておけばそうなります。

3. つなぐ

PosterPanel に Udon Behaviour を追加し、PosterPanel スクリプトを指定します。

指定するもの
Poster UrlPNGかJPEGの直リンク
Poster MaterialPosterMaterial
Status TextStatusText

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

using UdonSharp;

[UdonBehaviourSyncMode(BehaviourSyncMode.None)]
public class PosterUpdateButton : UdonSharpBehaviour
{
    [SerializeField] private PosterPanel panel;

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

4. 確かめる

Build & Test で起動します。

順序操作期待する結果
1入る少し待って、パネルにWebの画像が出る。状態が「貼り替えました」
2斜めから見る絵がざらつかない(ミップマップの効果)
3更新ボタンを連打する「少し待ってから、もう一度」が出る
4URLを存在しないものに変えて、入り直すパネルは最初に入れた画像のまま。状態だけ「貼り替えられませんでした」

4番目が、この記事のいちばん大事な確認です。 読み込みに失敗しても、壁が真っ白にはなりません。

Sponsored

うまくいかないときは

  • いつまでも表示されない → ダウンローダーをローカル変数で作っています。フィールドにします
  • 一度表示されたのに消えた → 表示中に Dispose() を呼んでいます。片付けは OnDestroy だけです
  • 絵が伸びる → Quadの縦横比と画像の縦横比が違います。合わせるか、余白のある画像を用意します
  • パネルが暗い → マテリアルのShaderがStandardのままです。Unlitにします
  • 読み込みに失敗する → URLが直リンクか、PNGかJPEGか、サイズが大きすぎないかを確認します
  • 連打すると読めなくなる → 制限に当たっています。間隔を空ける処理を確認します

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

  • エディタで試すとマテリアルが書き換わる: Playモードで読み込むと、そのマテリアルの見た目が残ることがあります。気になる場合は、試す用にマテリアルを複製しておいてください
  • UIに出すならRawImage: Canvasの中に出したいときは、マテリアルの代わりに result.ResultRawImagetexture に入れます。Image はSpriteを扱う部品なので、こちらでは使いません
  • 複数枚を切り替えるなら: URLを配列で持って、ボタンで切り替えます。ただし切り替えるたびに読み込みが走るので、間隔の制限に気をつけてください
  • 貼り替えは各自が行う: 誰かが更新しても、他の人のパネルは変わりません。全員をそろえたいなら、きっかけを Network Events で送ります
  • 使いどころはいろいろ: イベントの告知ポスター、写真ギャラリー、月替わりのアート展示、看板の差し替え。「ビルドし直さずに変えたい絵」が対象です

まとめ

外の画像を貼る仕掛けは、頼む係を持ち続けることが要です。

  • VRCImageDownloader はフィールドで保持する。ローカル変数だと届く前に片付けられる
  • マテリアルを渡しておくと、成功した時点で貼られている
  • 失敗しても何もしないので、前の絵が残る
  • Dispose() は片付けのときだけ。表示中に呼ばない

作るときの問いかけは、「読めなかったとき、壁には何が見えるか」 です。前のポスターが見えているなら、その設計は正しいです。

各自の設定を保存するなら PlayerDataで音量を保存する、動画を流すなら 動画プレイヤーを置く へ進んでください。

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