Unity
VirtualCast
VCI

VCIをビルドしたクライアントからExportする

概要

先日、画像を差し替えてVCIを吐き出してくれる ArtFrameGenerator 0.01 を作りました。
VRMのようにエクスポーター、インポーターのサンプルがないので、ついでにVCIのエクスポーターのサンプルを置いておきます。
※公式のサンプルが出た場合、そちらを使用しましょう。

UnityEditorの機能はビルドすると使えない

当たり前ですが AssetDatabase.Refresh() とか EditorUtility とか便利な機能が使えません。
知ってる人にとっては当たり前ですが、自分は知らなかったのでビルドする時になってハマりました。

エクスポーターのサンプル

VciExporterSample.cs
using System;
using System.IO;
using System.Windows.Forms;
using UnityEngine;
using VCI;
using VCIGLTF;

public class VciExporterSample : MonoBehaviour {

    [SerializeField, Tooltip("VCI Root Object")]
    private GameObject _exportVci;

    private void Start()
    {
        if (_exportVci == null)
        {
            _exportVci = FindObjectOfType<VCIObject>().gameObject;
            Debug.Log(_exportVci.name + "をエクスポートのターゲットに設定しました。");
        }
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.E))
        {
            VciExporter();
        }
    }

    //Exporter
    public void VciExporter()
    {
        //Path
        string exportPath = GetExportPath();
        //cancel
        if (exportPath == "")
        {
            Debug.Log("ファイルの保存がキャンセルされました。");
            return;
        }

        //Vci Binary
        byte[] vciBinary = GetVciObject();

        //Write
        File.WriteAllBytes(exportPath, vciBinary);
        Debug.Log("保存先 : " + exportPath);
    }

    //_exportVciに設定されたVCIをByteにする
    private byte[] GetVciObject()
    {
        //Export
        var gltf = new glTF();

        using (var exporter = new VCI.VCIExporter(gltf))
        {
            exporter.Prepare(_exportVci);
            exporter.Export();
        }
        var bytes = gltf.ToGlbBytes();
        return bytes;
    }

    //保存先のファイルパスを取得
    private string GetExportPath()
    {
        SaveFileDialog saveFileDialog = new SaveFileDialog();

        //デフォルトのファイル名
        DateTime dateTime = DateTime.Now;
        string time = dateTime.ToString("MMddHHmmss");
        saveFileDialog.FileName = _exportVci.name + time + ".vci";

        //ウインドウのメッセージ
        saveFileDialog.Title = "VCIの保存先を指定してください。";

        //デフォルトのディレクトリ
        saveFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);

        //ファイルの種類指定
        saveFileDialog.Filter = "vci|*.vci";
        saveFileDialog.CheckFileExists = false;

        //ダイアログを開く
        //OKを選択した場合、ファイルパスを返す。
        if (saveFileDialog.ShowDialog() == DialogResult.OK)
        {
            string path = saveFileDialog.FileName;
            saveFileDialog.Dispose();
            return path;
        }

        //OK以外を選択した場合、"" を返す。
        saveFileDialog.Dispose();
        return "";
    }
}

使い方

ファイルのパスを取得するのに System.Windows.FormsSaveFileDialog を使用するので、DLLのインポートが必要です。
そのままサンプル使おうとするとコンパイルした時に「Formsなんて名前空間無いぞ!」と言われます。

System.Windows.Forms.dll (DLL) のインポート
1.C:\Program Files\Unity\Editor\Data\Mono\lib\mono\2.0 から System.Windows.Forms.dll をコピーする。
※UnityHubを使用してる場合は微妙にディレクトリが違うかもしれません。
2./Asset/PluginsPlugins フォルダにdllを張り付ける。

あとはサンプルのスクリプトをコピペして、gameobjectにアタッチして、実行中に E キーを押すとエクスポートを実行します。

解説

Exporter部分

エクスポートの部分はVCIのパッケージから落とせる VCIObjectExporterMenu.cs// export 部分に書いてあります。

VCIObjectExporterMenu.cs
            // export
            var gltf = new glTF();
            using (var exporter = new VCI.VCIExporter(gltf))
            {
                //rootはVCIのrootオブジェクト
                exporter.Prepare(root);
                exporter.Export();
            }

            var bytes = gltf.ToGlbBytes();
            //pathは保存先
            File.WriteAllBytes(path, bytes);

VCIをエクスポートするのに必要な事は「保存先を指定して、byteになったVCIを、File.WriteAllBytes()するとエクスポートできる」という感じです。
なので root のgameobjectをhierarchyから指定して PathSaveFileDialog から指定すればビルド後のクライアントでエクスポートできます。

DLLを使いたくない場合…

Application.dataPath でVCIを保存するパスを決め打ちして Directory.Exists() でパスの有無を確認してから Directory.CreateDirectory() で動的に作成するなど…でしょうか。
(自分がやるとしたらひとまずそれで作ります)

SaveFileDialog

メッセージボックスのタイトル、開くファイルの種類、開くディレクトリを指定した上で ShowDialog() を実行してファイルパスを取得します。
これらのオプションは適宜、追加変更するのがよいと思います。
リファレンス:SaveFileDialog Class
フィルターのプロパティ:FileDialog.Filter Property

saveFileDialog.InitialDirectory で指定してる Environment は環境変数など様々なOSの情報を返してくれるヤツです。
これを使ってデスクトップのパスを取得します。
Environment.GetFolderPath Method

最終的に saveFileDialog.FileName の中にファイルまでのディレクトリが格納されるので、その内容を return するのがよいと思います。

その他注意点

ビルド後のクライアントだと、VCIのShaderに standardVcolor が含まれてるとエラーになります。
ビルド後にエラーがでるようでしたら BuildingSetting から DevelopmentBuil のオプションにチェックを入れてビルドするとエラーのログが簡単に見れるようになります。
ArtFrameGenerator作ってて苦戦したので覚えた。

公式からちゃんとしたExporterのサンプルが出るのを待つべき…

まとめ

あとは、動的にTexture2Dを差し替えたり、Subitemの子になってるオブジェクトを変更すればVCIのGeneratorのようなものが作れます。
VRChatで「Unityをインストールしてください」に慣れてる層はいるとはいえ、やはりハードルがあるかないかで言えばハードルはあるのでジェネレーターから興味を持ってもらえると嬉しいですね…。

ちなみにArtFrameGeneratorのニコニコの紹介動画が1万再生、紹介ツイート350RTでDL数が50にも満たなかったです。
ツールを広めたり定着させるのは大変なんだな…と思いました。ツールをちゃんとリーチさせたい層に届けるテクニック?なんかも、学ばないといけないんですね。
バーチャルモーションキャプチャーとかコンスタントに動画があがってくるのですごい。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away