5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【VRC】Public SDK APIを使って自作拡張メニューからワールドをアップロードする

Posted at

概要

VRChatのSDKにはPublicSDKAPIというものが公開されています。
これを使うことでワールドやアバターのビルド・アップロードに関連する拡張エディタを作ることができます。

あまり情報がなかったので備忘録として簡単に使い方を書いておきます。

作るもの

VRChat SDKの「Build and Upload」に相当するボタンを作ってみます。
image.png

スクリプト全文

WorldUploader.cs
using System;
using UnityEditor;
using UnityEngine;
using VRC.Core;
using VRC.SDK3.Editor;
using VRC.SDKBase.Editor;
using VRC.SDKBase.Editor.Api;

public class WorldUploader : MonoBehaviour
{

    [InitializeOnLoadMethod]
    public static void RegisterSDKCallback()
    {
        VRCSdkControlPanel.OnSdkPanelEnable += AddBuildHook;
    }

    private static void AddBuildHook(object sender, EventArgs e)
    {
        if (VRCSdkControlPanel.TryGetBuilder<IVRCSdkBuilderApi>(out var builder))
        {
            builder.OnSdkUploadProgress += ShowProgress;
            builder.OnSdkUploadSuccess += NoticeUploadSccess;
            builder.OnSdkUploadError += NoticeUploadSccess;
        }
    }

    private static void ShowProgress(object sender, (string status, float percentage) e)
    {
        Debug.Log("ワールドアップロード中.." + e.status + ":" + e.percentage * 100 + "%");
        // DisplayProgressBarを使うとなぜかアップロードに失敗する
        //EditorUtility.DisplayProgressBar("ワールドアップロード中..", e.status, e.percentage);//プログレスバー表示

    }

    private static void NoticeUploadSccess(object sender, string message)
    {
        EditorUtility.DisplayDialog("アップロード成功", message, "OK");
    }
    private static void NoticeUploadError(object sender, string message)
    {
        EditorUtility.DisplayDialog("アップロード失敗", message, "OK");
    }


    [MenuItem("Tools/UploadWorld")]
    private static async void UploadWorld()
    {
        bool IsNewWorld = false;
        PipelineManager[] _pipelineManagers = VRC.Tools.FindSceneObjectsOfTypeAll<PipelineManager>();

        var worldId = _pipelineManagers[0].blueprintId;
        VRCWorld _worldData = new VRCWorld();
        if (string.IsNullOrWhiteSpace(worldId))
        {
            IsNewWorld = true;
        }
        else
        {
            try
            {
                _worldData = await VRCApi.GetWorld(worldId, true);

                if (APIUser.CurrentUser != null && _worldData.AuthorId != APIUser.CurrentUser?.id)
                {
                    IsNewWorld = true;
                }
            }
            catch (Exception ex)
            {
                Debug.LogException(ex);
            }
        }

        if (IsNewWorld) throw new Exception("アップロード済みのワールドが見つかりません");

        // VRCのワールドビルド

        if (!VRCSdkControlPanel.TryGetBuilder<IVRCSdkWorldBuilderApi>(out var builder)) return;

        try
        {
            await builder.BuildAndUpload(_worldData);
        }
        catch (Exception e)
        {
            Debug.Log(e.Message);
        }
    }
}

使ってみる場合はEditorフォルダを作ってその中に格納してください。

解説

イベントの追加

[InitializeOnLoadMethod]
public static void RegisterSDKCallback()
{
    VRCSdkControlPanel.OnSdkPanelEnable += AddBuildHook;
}

private static void AddBuildHook(object sender, EventArgs e)
{
    if (VRCSdkControlPanel.TryGetBuilder<IVRCSdkBuilderApi>(out var builder))
    {
        builder.OnSdkUploadProgress += ShowProgress;
        builder.OnSdkUploadSuccess += NoticeUploadSccess;
        builder.OnSdkUploadError += NoticeUploadSccess;
    }
}

private static void ShowProgress(object sender, (string status, float percentage) e)
{
    Debug.Log("ワールドアップロード中.." + e.status + ":" + e.percentage * 100 + "%");
    // DisplayProgressBarを使うとなぜかアップロードに失敗する
    //EditorUtility.DisplayProgressBar("ワールドアップロード中..", e.status, e.percentage);//プログレスバー表示

}

private static void NoticeUploadSccess(object sender, string message)
{
    EditorUtility.DisplayDialog("アップロード成功", message, "OK");
}
private static void NoticeUploadError(object sender, string message)
{
    EditorUtility.DisplayDialog("アップロード失敗", message, "OK");
    }

ビルド・アップロードの処理の前後にイベントを追加することができます。
今回はアップロード時のイベントとしてアップロード処理の進捗をログに出力するShowProgress、アップロード完了後にダイアログを表示するNoticeUploadSccessNoticeUploadErrorの3つを作りました。
RegisterSDKCallback関数をInitializeOnLoadMethodのタイミングで呼び出し、VRCSDKパネルが開いた際に作成した3つのイベントを登録するためのAddBuildHookを登録しています。

ややこしいですがタイミングとしては順に

  1. RegisterSDKCallback:エディタを開いたときにAddBuildHookイベントをSDKパネルが開いたときのイベントとして登録
  2. AddBuildHook:SDKパネルが開いたときに3つのイベントをアップロード時のイベントとして登録
  3. アップロード処理のそれぞれのタイミングで3つのイベントが呼び出される
    となります。

アップロード処理

ワールド情報の取得

bool IsNewWorld = false;
PipelineManager[] _pipelineManagers = VRC.Tools.FindSceneObjectsOfTypeAll<PipelineManager>();

var worldId = _pipelineManagers[0].blueprintId;
VRCWorld _worldData = new VRCWorld();
if (string.IsNullOrWhiteSpace(worldId))
{
    IsNewWorld = true;
}
else
{
    try
    {
        _worldData = await VRCApi.GetWorld(worldId, true);

        if (APIUser.CurrentUser != null && _worldData.AuthorId != APIUser.CurrentUser?.id)
        {
            IsNewWorld = true;
        }
    }
    catch (Exception ex)
    {
        Debug.LogException(ex);
    }
}

if (IsNewWorld) throw new Exception("アップロード済みのワールドが見つかりません");

ワールドアップロードにはVRCWorldというワールド情報を持ったクラスのオブジェクトが必要です。
Sceneに設置するPrefabとは別なので気を付けてください。

VRC.Tools.FindSceneObjectsOfTypeAll<PipelineManager>()でSceneに設置されたPipelineManagerを取得し、ブループリントIDを読み込んだのちにawait VRCApi.GetWorld(worldId, true)でVRCのサービスへ問い合わせることで該当のブループリントIDをもったオブジェクトが返ってきます。

アップロード済みのワールドがない場合は新規でVRCWorldオブジェクトを作成し、ワールド名などの情報を入力もできますが今回はExceptionで処理を中断しています。
VRChatSDK内のVRCSdkControlPanelWorldBuilderに正式な処理があるので気になったら参照してみてください。

ワールドをアップロードする

        if (!VRCSdkControlPanel.TryGetBuilder<IVRCSdkWorldBuilderApi>(out var builder)) return;

        try
        {
            await builder.BuildAndUpload(_worldData);
        }
        catch (Exception e)
        {
            Debug.Log(e.Message);
        }

ワールド情報をもってアップロードを行ないます。

まとめ

動かしてみるとわかるのですが、この拡張エディタでのアップロードは一度VRChatSDKのパネルを開きBuilderタブに移動してからでないとエラーが出ます。
つまりこれが動くときはもとからある「Build and Upload」ボタンが押せる状態なので基本的にこのエディタ拡張だけだと意味はないです。
ビルド時に何か作業が発生する場合に前後に処理を加えるなどすることで価値が出てくるかもしれません。ライトベイクを忘れないようにするとか。1

  1. builder.OnSdkBuildStartにライトベイク処理を入れてみましたが、その時点ですでにビルドが始まっているため通常のアップロードにベイク処理の差し込みはできませんでした。
    専用のビルドボタンを作るとうまくいくと思います。

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?