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

## 概要
先日、画像を差し替えてVCIを吐き出してくれる <a target=”_blank” href="https://neko28.booth.pm/items/1282955">ArtFrameGenerator 0.01</a> を作りました。
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.Forms` の `SaveFileDialog` を使用するので、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/Plugins` の `Plugins` フォルダに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から指定して `Path` を `SaveFileDialog` から指定すればビルド後のクライアントでエクスポートできます。

### DLLを使いたくない場合…

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

### SaveFileDialog

メッセージボックスのタイトル、開くファイルの種類、開くディレクトリを指定した上で `ShowDialog()` を実行してファイルパスを取得します。
これらのオプションは適宜、追加変更するのがよいと思います。
リファレンス：<a target=”_blank” href="https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.savefiledialog?view=netframework-4.7.2">SaveFileDialog Class</a>
フィルターのプロパティ：<a target=”_blank” href="https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.filedialog.filter?view=netframework-4.7.2#System_Windows_Forms_FileDialog_Filter">FileDialog.Filter Property</a>

`saveFileDialog.InitialDirectory` で指定してる `Environment` は環境変数など様々なOSの情報を返してくれるヤツです。
これを使ってデスクトップのパスを取得します。
<a target=”_blank” href="https://docs.microsoft.com/ja-jp/dotnet/api/system.environment.getfolderpath?view=netframework-4.7.2">Environment.GetFolderPath Method</a>

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

## その他注意点

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

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

## まとめ

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

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