便利なライブラリが沢山ある NuGet ですが Unity で開発する HoloLens や Windows MR では簡単には使え無さそう? UWP のプロジェクトをビルドで吐いたところに手動で NuGet 追加すれば使えるけど、リポジトリにはビルドで出したプロジェクトは入れないからクローンするたびとかに手動で追加しないといけないよね?(認識違ってたら教えてください)
めんどくさいですね。
Unity から出力されるプロジェクトの形
とりあえずシーンを追加して、適当なスクリプトを作成してシーンのカメラあたりにアタッチしました。 この状態で UWP のプロジェクトを出力すると以下のような形のものが出てきます。
project.json を使った少し前までの形のプロジェクトみたいですね。この形のプロジェクトの場合は NuGet で参照を追加すると project.json に定義が追加されます。試しに Json.NET の 9.0.4 を追加してみました。(最新のだとエラーになったので)
Assembly-CSharp プロジェクトの project.json
{ "dependencies": { "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0", "Newtonsoft.Json": "9.0.1" }, "frameworks": { "uap10.0.10240": {} }, "runtimes": { "win10-arm": {}, "win10-arm-aot": {}, "win10-x86": {}, "win10-x86-aot": {}, "win10-x64": {}, "win10-x64-aot": {} } }
メインのプロジェクト(今回は NuGetApp という名前で作ったので NuGetApp プロジェクト)の project.json
{ "dependencies": { "Microsoft.ApplicationInsights": "1.0.0", "Microsoft.ApplicationInsights.PersistenceChannel": "1.0.0", "Microsoft.ApplicationInsights.WindowsApps": "1.0.0", "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0", "Newtonsoft.Json": "9.0.1" }, "frameworks": { "uap10.0.10240": {} }, "runtimes": { "win10-arm": {}, "win10-arm-aot": {}, "win10-x86": {}, "win10-x86-aot": {}, "win10-x64": {}, "win10-x64-aot": {} } }
これで Json.NET が使えます。こんな感じで。
using Newtonsoft.Json; using UnityEngine; public class Sample : MonoBehaviour { // Use this for initialization void Start() { var jsonText = JsonConvert.SerializeObject(new Person { Name = "tanaka", }); } // Update is called once per frame void Update() { } } public class Person { public string Name { get; set; } }
問題点 1 ビルドエラーになる
この状態で、Unityから再ビルドするとエラーになります。何個かエラーが出ますが、恐らくこれが原因でしょう。
Assets/Scripts/Sample.cs(1,7): error CS0246: The type or namespace name `Newtonsoft' could not be found. Are you missing an assembly reference?
まぁ当然ですよね。Unity 側からしたらプラットフォーム固有のビルド用に出力した先で追加されたライブラリのことなんて知ったこっちゃないので。
#if ディレクティブで回避
エラー自体は #if で回避できます。UNITY_UWP で括れば OK です。
先日教えてもらったのですが、WINDOWS_UWPとか何個か定義される定数があるのですが、ものによって定義されるタイミングが違うらしいのですよね。WINDOWS_UWP だと Unity から UWP のプロジェクト出力するタイミングのビルドで評価されるらしいので UWP プロジェクトの出力段階でエラーになります。
UNITY_UWP だと大丈夫みたいです。 ということで以下のようなコードにしておけば OK です。
#if UNITY_UWP using Newtonsoft.Json; #endif using UnityEngine; public class Sample : MonoBehaviour { // Use this for initialization void Start() { #if UNITY_UWP var jsonText = JsonConvert.SerializeObject(new Person { Name = "tanaka", }); #endif } // Update is called once per frame void Update() { } } public class Person { public string Name { get; set; } }
問題点 2 再ビルド時に手動で NuGet 追加
これでなんとか開発出来そうなのですが、出力されたプロジェクトを削除して Unity から再ビルドすると当然ながら project.json が新規に作られて NuGet の定義が足りなくなるので、手動で追加しないといけません。
1 つ 2 つならいいのですが数が増えてくるとやってられませんよね。
ということで自動で追加してもらいましょう。Unity にはビルド後に任意のスクリプトを動かす方法があります。
Unity - Scripting API: PostProcessBuildAttribute
いけそうですね。ということで Editor/AddNuGetPackagePostProcess.cs を作って以下のように書きます。
using UnityEditor; using UnityEditor.Callbacks; using UnityEngine; public class AddNuGetPackagePostProcessor { [PostProcessBuild] public static void OnPostProcessBuild(BuildTarget target, string pathToBuildProject) { Debug.Log(pathToBuildProject); } }
ビルドすると以下のようなログが出ます。
C:/Projects/NuGetApp/UWP UnityEngine.Debug:Log(Object) AddNuGetPackagePostProcessor:OnPostProcessBuild(BuildTarget, String) (at Assets/Editor/AddNuGetPackagePostProcessor.cs:11) UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
いけそうですね。あとはゴリゴリ書くだけ。
project.json では Dictionary<string, string>
を扱いたかったのですが JsonUtility ではサポートされてないので泣く泣く MiniJSON を使うことにしました。
ということで Editor フォルダに MiniJSON.cs ファイルを作って上記コードをコピペします。 そして、AddNuGetPackagePostProcessor.cs を以下のようにします。単純に指定した定義を project.json に追加してるだけです。
using MiniJSON; using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.Callbacks; using UnityEngine; public class AddNuGetPackagePostProcessor { [PostProcessBuild] public static void OnPostProcessBuild(BuildTarget target, string pathToBuildProject) { Debug.Log(pathToBuildProject); var targetProjectJsonFiles = new[] { Path.Combine(pathToBuildProject, "NuGetApp", "project.json"), Path.Combine(pathToBuildProject, "GeneratedProjects", "UWP", "Assembly-CSharp", "project.json"), }; var libraries = new Dictionary<string, string> { { "Newtonsoft.Json", "9.0.1" }, }; foreach (var projectJsonPath in targetProjectJsonFiles) { AddNuGetReference(projectJsonPath, libraries); } } private static void AddNuGetReference(string projectJsonPath, Dictionary<string, string> libraries) { var projectJson = Json.Deserialize(File.ReadAllText(projectJsonPath)) as Dictionary<string, object>; var dependencies = (Dictionary<string, object>)projectJson["dependencies"]; foreach (var library in libraries.Where(x => !IsAlreadyDefined(dependencies, x))) { dependencies[library.Key] = library.Value; } File.WriteAllText(projectJsonPath, Json.Serialize(projectJson)); } private static bool IsAlreadyDefined(Dictionary<string, object> dependencies, KeyValuePair<string, string> library) { if (!dependencies.ContainsKey(library.Key)) { return false; } return ((string)dependencies[library.Key]) == library.Value; } }
UWP のプロジェクトを一旦消して Unity から再出力すると以下のように project.json のインデントが死んだ状態ですが Json.NET の定義が追加されてることがわかります。
AddNuGetApp プロジェクト
{"dependencies":{"Microsoft.ApplicationInsights":"1.0.0","Microsoft.ApplicationInsights.PersistenceChannel":"1.0.0","Microsoft.ApplicationInsights.WindowsApps":"1.0.0","Microsoft.NETCore.UniversalWindowsPlatform":"5.0.0","Newtonsoft.Json":"9.0.1"},"frameworks":{"uap10.0":{}},"runtimes":{"win10-arm":{},"win10-arm-aot":{},"win10-x86":{},"win10-x86-aot":{},"win10-x64":{},"win10-x64-aot":{}}}
Assebmly-CSharp プロジェクト
{"dependencies":{"Microsoft.NETCore.UniversalWindowsPlatform":"5.0.0","Newtonsoft.Json":"9.0.1"},"frameworks":{"uap10.0":{}},"runtimes":{"win10-arm":{},"win10-arm-aot":{},"win10-x86":{},"win10-x86-aot":{},"win10-x64":{},"win10-x64-aot":{}}}
確認
生成されたソリューションを Visual Studio で開いてビルドすると参照のところに、ちゃんと Json.NET が追加されていることが確認できます。
まとめ
ということでまとめです。
- NuGet から入手したライブラリに関する処理は #if UNITY_UWP ~ #endif で括ろう
- NuGet パッケージの手動追加がメンドクサイ場合は PostProcessBuild で project.json を書き換えよう
ちなみに、多分ですが近い将来きっと Unity が出力するプロジェクトは project.json の存在しない形になると思うので、そのタイミングでこの PostBuildProcess は使えなくなります。 その時は、csproj ファイルに NuGet のリファレンスが書き足される形になると思うので、同じ要領で PostProcessBuild で csproj を書き換えてしまいましょう。