- 投稿日:2019-08-28T20:47:17+09:00
Unity ML-Agentsで強化学習をやってみる。(環境構築〜サンプル実行編)
サンプル的にブロック崩しの設定厳しめゲームを作った。機械学習の勉強も兼ねて強化学習を学んでみようと、このゲームをML-Agentsを使って学習させてみようと試みた記事。。。
始まりはこの本。
https://www.borndigital.co.jp/book/6702.html
しかし・・・ml-Agents色々変わってるし。。結局GitHubのドキュメントみながら一つ一つやっていくことになる。。
(強化学習の概要を、マップ探索で説明してあって個人的にはわかりやすかった。)環境構築
OS:macOS Mojave(10.14.5)
Unity
まずは、Unityのインストール。既に2018.03が入っていたがバージョンアップしてみる。。(バージョンアップで非対応コードが出ないか心配。。)=>Unity-Hubを導入。
https://unity3d.com/get-unity/update
今まで使ってなかったけど、各種バージョンを一元管理できるので便利ですね。
(前バージョンからのマイグレーションも自動。。)
※大したことしていなかったので、マイグレーションが問題なく完了。ML-Agents
UnitySDKのサンプルを動かしてみる
次は、ML-Agents Toolkitをclone。適当にディレクトリを作成して。
git clone https://github.com/Unity-Technologies/ml-agents.git落としてきたら、Unityでサンプルを動かしてみる。
- UnityHubで「リスト追加」→ml-agents/UnitySDKを開く
- Unityバージョンを最新に設定
開いたあとは(とりあえず3DBallでも・・・)
- Assets/ML-Agents/Examples/3DBall/Scenesの3DBallを開いてシーンをロード
- Ball 3D AcademyにセットされいてるBrains Assets/ML-Agents/Examples/3DBall/Brains/3DBallLearningのModelにAgents/Examples/3DBall/TFModels/3DBallLearningをセッ
- Playを実行
プレートがバランスを取りながらボールを落とさないように動くサンプルが確認できる!!
PythonとmlagentsPackageを入れる
Pythonは既にpyenvを入れていたので、ml-agentsを入れたディレクトリ配下のpython環境を3.6.6に設定
$ pyenv local 3.6.6 $ python --version Python 3.6.6$ pip3 install mlagents※ここで”You are using pip version 10.0.1, however version 19.1.1 is available.”アップグレードしろと。
$ pip3 install --upgrade pip setuptools※依存関係の諸々で”pip3 install --upgrade pip ”だとうまく更新できず。。色々ググったらこれでいけた。(upgradeの方にもpip"3"付けたくなりますよね。。。)
あとはそれぞれのフォルダ配下でインストール$ cd ml-agents-envs $ pip3 install -e ./ $ cd .. $ cd ml-agents $ pip3 install -e ./ml-agentsを起動して学習の確認
Unity側
AcademyのControlチェックボックスのチェックを入れる(入れないと学習したBrainsをロードして動く)
ml-agent側:
$ mlagents-learn ../config/trainer_config.yaml --run-id=sample[これはなんでもOK] --trainこんな具合に実行されてれ以下メッセージが出るのでUnity側を実行すると動く!!
INFO:mlagents.envs:Start training by pressing the Play button in the Unity Editor.INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 1000. Time Elapsed: 11.224 s Mean Reward: 1.207. Std of Reward: 0.728. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 2000. Time Elapsed: 22.257 s Mean Reward: 1.296. Std of Reward: 0.759. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 3000. Time Elapsed: 33.052 s Mean Reward: 1.523. Std of Reward: 0.838. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 4000. Time Elapsed: 43.845 s Mean Reward: 1.806. Std of Reward: 1.128. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 5000. Time Elapsed: 54.400 s Mean Reward: 2.777. Std of Reward: 1.975. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 6000. Time Elapsed: 65.174 s Mean Reward: 3.922. Std of Reward: 3.448. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 7000. Time Elapsed: 75.998 s Mean Reward: 7.204. Std of Reward: 6.900. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 8000. Time Elapsed: 86.734 s Mean Reward: 9.579. Std of Reward: 10.298. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 9000. Time Elapsed: 97.146 s Mean Reward: 17.802. Std of Reward: 18.521. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 10000. Time Elapsed: 108.207 s Mean Reward: 22.553. Std of Reward: 23.049. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 11000. Time Elapsed: 119.127 s Mean Reward: 54.259. Std of Reward: 35.250. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 12000. Time Elapsed: 129.911 s Mean Reward: 58.905. Std of Reward: 35.462. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 13000. Time Elapsed: 140.319 s Mean Reward: 64.107. Std of Reward: 38.436. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 14000. Time Elapsed: 151.080 s Mean Reward: 73.244. Std of Reward: 33.077. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 15000. Time Elapsed: 161.689 s Mean Reward: 74.694. Std of Reward: 35.027. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 16000. Time Elapsed: 172.868 s Mean Reward: 94.123. Std of Reward: 20.358. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 17000. Time Elapsed: 183.989 s Mean Reward: 95.277. Std of Reward: 16.361. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 18000. Time Elapsed: 193.713 s Mean Reward: 94.962. Std of Reward: 17.454. Training. INFO:mlagents.trainers: ponponRun-0: 3DBallLearning: Step: 19000. Time Elapsed: 204.282 s Mean Reward: 86.887. Std of Reward: 22.600. Training.
初めは落とす落とす。。。
からの
プレートがバランスを取るようになるのがわかる。。。学習したファイルのロード
学習ファイルは以下フォルダに格納される。
./models/sample[起動時に指定したrun-id]/3DBallLearning.nnファイルをUnity側プロジェクトのAssetsにコピーして以下で読み込む。
・AcademyのControlチェックを外す
・Brainsをダブルクリックする3DBallLearningがInspectorに表示されるので、ここのModelにコピーしたXXXX.nnファイルをドラック&ドロップする。
最後に実行すれば学習したデータが読み込めます。
参考
環境構築および動作確認
https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Installation.md
- 投稿日:2019-08-28T20:45:41+09:00
UnityのVR SamplesのMazeでキャラクターを変更する
はじめに
前回記事 UnityのVR SamplesのMazeでカメラの位置をキャラクターに追従する の続きです。
今回は、キャラクターを変更してみたいと思います。
変更するキャラクターとしては、VRM形式の「ニコニ立体ちゃん」を使用します。開発環境
Unity 2019.2.0f1
VR Samples - 1.4
Oculus Integration for Unity - 1.39
UniVRM v0.53.0
「ニコニ立体ちゃん」(VRM版)ビルド手順
前回記事で作成したプロジェクトで引き続き作業します。
- 今回もThe Mazeを改造するので、サンプルのシーンMazeを開きます。
- まずはVRM形式のモデルのファイルを読み込むためのアセットUniVRMをvrm-c/UniVRM からダウンロードします。自分がダウンロードしたファイルは"UniVRM-0.53.0_6b07.unitypackage"ですが、数字の部分はバージョンにより変わります。
- Assets -> Import Package -> Custom Packageからファイル"UniVRM-0.53.0_6b07.unitypackage"を指定してインポートします。
- 次に、キャラクターのファイルをニコニ立体ちゃん (VRM) からダウンロードします。
- Projectウインドウで、Assetsフォルダの下にキャラクターを配置するフォルダ(今回はModelsとします)を作成し、ダウンロードしたファイル"AliciaSolid.vrm"エクスプローラーからドラッグアンドドロップすると、プレファブができます。
- できたプレファブをHierarchyウインドウのCharactersの下にドラッグアンドドロップします。
![]()
- CharactersのMazeCharacterからAliciaSolidに赤枠で囲んだコンポーネントをコピーします。Inspectorウインドウ上で1個ずつ右クリックで"Copy Component"して"Paste Component As New"でコピーします。TransformとAnimatorの設定も合わせます。
![]()
- Third Person Charactorコンポーネントの設定も元の値と変わっていた場合、元の値にあわせておきます(AI Charactor ControlでRequireComponentされているため、コピーする順番によりデフォルトの値になるようです)。Ground Check Distanceの値が0.1になっていると、キャラクターの動作が不自然になります。
- MazeCharacter側のチェックを外して無効にします。
![]()
- あとは、あちこちで設定されているMazeCharacterへの参照をAliciaSolidに直していきます。
![]()
- すべて変更したら、ビルド設定からビルドします。
さいごに
今回キャラクターを変更することができたのですが、変更する箇所が多く、結構手間がかかりました。
VRMのファイルを配置するだけで簡単に変更できるようにする方法がないか、引き続き検討してみたいと思います。
- 投稿日:2019-08-28T15:42:43+09:00
Unityでオンラインマルチプレイなゲームを作りたい その6 ルーム入室
前回の記事でルームの作成が行えるようになりました。
今回は作成されたルームへ入室を行う処理を作っていきます。今回の目標
イメージ図
※ルーム名は気にしないでください。
ルームリストから選んで入室を行うものと、プライベート設定になっている部屋を指定して入室を行うものの2パターンを実装しています。ルームリストから入室
http://www.monobitengine.com/doc/mun/contents/FeatureClient/JoinRoom.htm
MonobitEngine.MonobitNetwork.JoinRoom("roomName");引数に入室したいルーム名を入力して使用するだけで任意のルームへ入室できます。
http://www.monobitengine.com/doc/mun/contents/FeatureClient/GetRoomData.htm
MonobitEngine.MonobitNetwork.GetRoomData()入室可能なルーム群をRoomData[]型で取得できます。
MonobitEngine.MonobitNetwork.GetRoomDataで取得したものをリストとして表示し、選択したルームからルーム名取得しMonobitEngine.MonobitNetwork.JoinRoomの引数に入れてあげるという形で実装していきます。まず、ルームリストに並べるボタンを作ります。
ScrollViewButton.cs/// <summary></summary> public class ScrollViewButton : UnityEngine.MonoBehaviour { /// <summary></summary> private RoomData m_Data; /// <summary>ボタンにルーム情報を適用する</summary> /// <param name="roomData">ボタンに適用させたいルームの情報</param> public void Set(RoomData roomData) { m_Data = roomData; gameObject.GetComponent<Button>().onClick.AddListener(OnClick); gameObject.GetComponentInChildren<Text>().text = m_Data.name + ", " + m_Data.playerCount + "/4"; } /// <summary>ボタンクリック時に適用されたルーム情報を元に任ルームへ入室する</summary> private void OnClick() { MonobitNetwork.JoinRoom(m_Data.name); } }uGUIのボタンをプレハブ化したものにアタッチして使ってください。
ルームリストに表示する際にルームデータを貰い、クリックされたときに貰ったルームデータを元に入室を行うようにしています。次にボタンを並べるリストを作ります。
※リスト内に表示するボタンの位置調整などの処理は省いています。ScrollView.cs/// <summary>ルームリスト及びボタンを表示するスクロールビュー</summary> public class ScrollView : UnityEngine.MonoBehaviour { /// <summary>ルームリストに表示するボタンのプレハブ</summary> private GameObject m_ButtonPrefub; /// <summary>ルームリストに表示するボタンのオブジェクトのリスト</summary> private List<GameObject> m_ButtonsList; // Start is called before the first frame update void Start() { m_ButtonPrefub = Resources.Load("UI_ScrollViewButton") as GameObject; m_ButtonsList = new List<GameObject>(); UpdateRoomData(); } /// <summary>ルームリストを更新する</summary> public void UpdateRoomData() { int roomCount = MonobitNetwork.GetRoomData().Length; for (int i = 0; i < roomCount; i++) { RoomData roomData = MonobitNetwork.GetRoomData()[i]; // パスワードが設定されている = 非公開ルームとして扱うのでリスト表示から除外 if (!roomData.customParameters["password"].Equals("empty")) { continue; } // リスト上に並べるボタンを生成 GameObject button = (GameObject)Instantiate(m_ButtonPrefub); // ボタンにルーム情報を適用する button.GetComponent<ScrollViewButton>().Set(MonobitNetwork.GetRoomData()[i]); m_ButtonsList.Add(button); } } /// <summary>ルームリストを削除する</summary> public void DestroyAll() { int length = m_ButtonsList.Count; for (int i = 0; i < length; ++i) { if(m_ButtonsList[i] == null) { continue; } Destroy(m_ButtonsList[i]); } m_ButtonsList.Clear(); m_ButtonsList.TrimExcess(); } }
public void UpdateRoomData()がメインの処理になります。
これでルームリストにルーム情報が設定されたボタンが並べられます。
ボタンを押すと適用されたルームへ入室してくれます。/// <summary></summary> private void OnClickUpdateRoomList() { ScrollView scrollView = m_ScrollViewObj.GetComponent<ScrollView>(); scrollView.DestroyAll(); scrollView.UpdateRoomData(); }更新ボタンを作り、このようにしてルームリストの情報を任意で更新するようにするといいですね。
プライベート設定にしている部屋への入室
ルームリストを作ったときと同様で、
MonobitEngine.MonobitNetwork.JoinRoomとMonobitEngine.MonobitNetwork.GetRoomDataを使い実装していきます。SceneJoinRoom.cspublic class SceneJoinRoom : MonobitEngine.MonoBehaviour { /// <summary>ルーム名</summary> private string m_RoomName = string.Empty; /// <summary>パスワード</summary> private string m_Password = string.Empty; // Start is called before the first frame update void Start() { if (!MonobitNetwork.inLobby) { Debug.Log("Not in lobby"); } /******** 表示するuGUIの初期化/配置等の処理 ******/ } /// <summary>ルーム名入力時に呼ばれる</summary> /// <param name="roomName">入力された文字列</param> private void OnInputRoomName(string roomName) { m_RoomName = roomName; } /// <summary>パスワード入力痔に呼ばれる</summary> /// <param name="password">入力された文字列</param> private void OnInputPassword(string password) { m_Password = password; } /// <summary>作成ボタンが押された際に呼ばれる</summary> private void OnClickJoin() { RoomData[] roomData = MonobitNetwork.GetRoomData(); int length = roomData.Length; for (int i = 0; i < length; ++i) { // 入りたいルームの名前に該当するルームを探し出す if (!roomData[i].name.Equals(m_RoomName)) { continue; } // 入りたいルームに設定されているパスワードを入力したパスワードと一致するかを確認する if (!roomData[i].customParameters["password"].Equals(m_Password)) { Debug.Log("Incorrect Password "); return; } MonobitNetwork.JoinRoom(m_RoomName); break; } } }プライベート設定になっている入りたいルームの名前を
MonobitEngine.MonobitNetwork.GetRoomDataで取得したルーム一覧から探し出し、見つかったルームに設定されているパスワードを、入力されたパスワードが一致するかを確認しています。
これで、パスワード設定がされた非公開ルームへの入室が実装できました。仕上げにルーム入室時/失敗時/サーバー切断時のコールバックを実装していきます。
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnJoinedRoom%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89public void OnJoinedRoom()ルーム入室に成功した際に呼ばれます。
public void OnJoinRoomFailed(object[] codeAndMsg)ルーム入室に失敗した際に呼ばれます。
コードにコールバックを追加します。
SceneJoinRoom.cspublic class SceneJoinRoom : MonobitEngine.MonoBehaviour { /// <summary>ルーム名</summary> private string m_RoomName = string.Empty; /// <summary>パスワード</summary> private string m_Password = string.Empty; // Start is called before the first frame update void Start() { if (!MonobitNetwork.inLobby) { Debug.Log("Not in lobby"); } /******** 表示するuGUIの初期化/配置等の処理 ******/ } /// <summary>ルーム入室成功時に呼ばれる</summary> private void OnJoinedRoom() { SceneManager.LoadScene("InGame"); } /// <summary>ルーム入室失敗時に呼ばれる</summary> private void OnJoinRoomFailed(object[] codeAndMsg) { Debug.Log("Join Room Failed : errorCode = " + codeAndMsg[0] + ", message = " + codeAndMsg[1]); } /// <summary>MUNサーバーとの接続を切った際に呼ばれるコールバック</summary> private void OnDisconnectedFromServer() { SceneManager.LoadScene("Title"); } /// <summary>ルーム名入力時に呼ばれる</summary> /// <param name="roomName">入力された文字列</param> private void OnInputRoomName(string roomName) { m_RoomName = roomName; } /// <summary>パスワード入力痔に呼ばれる</summary> /// <param name="password">入力された文字列</param> private void OnInputPassword(string password) { m_Password = password; } /// <summary>作成ボタンが押された際に呼ばれる</summary> private void OnClickJoin() { RoomData[] roomData = MonobitNetwork.GetRoomData(); int length = roomData.Length; for (int i = 0; i < length; ++i) { // 入りたいルームの名前に該当するルームを探し出す if (!roomData[i].name.Equals(m_RoomName)) { continue; } // 入りたいルームに設定されているパスワードを入力したパスワードと一致するかを確認する if (!roomData[i].customParameters["password"].Equals(m_Password)) { Debug.Log("Incorrect Password "); return; } MonobitNetwork.JoinRoom(m_RoomName); break; } } }ルーム入室成功時に次の画面へ遷移するようにしています。
失敗時は失敗内容をログに出力しています。
サーバーが切断された際の処理もついでに追加しています。これでルーム入室ができるようになりました。
次回はゲーム開始前の、ルーム待機室のようなものを作っていきます。参考
http://www.monobitengine.com/doc/mun/contents/FeatureClient/JoinRoom.htm
http://www.monobitengine.com/doc/mun/contents/FeatureClient/GetRoomData.htm
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnJoinedRoom%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnJoinRoomFailed%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89
- 投稿日:2019-08-28T15:20:36+09:00
【Unity】子の要素をFindを使わずにFor文で取得する。
はじめに(追記)
追記 : コメントでさらにスマートな処理を書いていただいたので、そちらも併せて紹介しておきます。
GameObjectをFindで取得するには、当然Findするオブジェクトを指定しなければいけないため、
取得したいオブジェクトの数だけ分が増えます。これが数が増えてくると面倒くさい...。これをなんとか解消したいと思い、色々試した結果For文を使って短く、それでいて管理が楽にできたので
記事としてまとめようと思った次第です。Script(追記)
追記 :追記のスクリプト
GetChildNext.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class GetChild : MonoBehaviour { public GameObject[] Parents; List<GameObject> Paneles; void Start() { Paneles = new List<GameObject>(); foreach(GameObject p in Parents) { foreach(Transform child in p.transform) { Paneles.Add(child.gameObject); } } for(int i = 0; i < Paneles.Count; i++) { Debug.Log(Paneles[i]); } } }この処理では親を1、子を2、子の子を3としたとき、Parentsにセットしている子を取得するので、
1をセットしている場合、2のオブジェクトは取得されるが、3は取得されません。
(下のGetChild.csでも同じです)3を取得したい場合は2をセットすれば取得できます。
GetChild.csusing System.Collections; using System.Collections.Generic; using UnityEngine; public class GetChild : MonoBehaviour { public GameObject[] Parents; GameObject Panel; List<GameObject> Paneles; void Start() { Paneles = new List<GameObject>(); for(int v = 0; v < Parents.Length; v++) { for(int i = 0; i < Parents[v].transform.childCount; i++ ) { Panel = Parents[v].transform.GetChild(i).gameObject; Paneles.Add(Panel); } } for(int i = 0; i < Paneles.Count; i++) //確認用の表記 { Debug.Log(Paneles[i]); } } }たったこれだけです。
使い方は以下の通りです。
1、何らかのオブジェクトにアタッチする
2、子を取得したい親オブジェクトをParentsインスペクターにセット
(Sizeを変更すればセットできる数も増えます)
さいごに
この方法を考え付いたのは、インターフェースの記事を見ていて、こういう感じにうまく
まとめられないかなぁ~という漠然とした思い付きで試してみた結果、
「なんかできたわ」という感じでした。自分の知識量が以前に比べて増えているんだなぁという実感を得られたので良かったです。
この方法が誰かの役に立ちますように。
- 投稿日:2019-08-28T09:00:15+09:00
DockerでUnity ML-Agentsを動作させる(v0.9.1対応)
Unity ML-Agents(v0.9.1)をDocker上で動作させてみました。
UnityやUnity ML-Agentsの環境構築などは下記をご参考ください。
Macでhomebrewを使ってUnityをインストールする(Unity Hub、日本語化対応)
https://qiita.com/kai_kou/items/445e614fb71f2204e033MacでUnity ML-Agentsの環境を構築する(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/268ccf6f961f8ca8cba8手順
基本的には公式のドキュメントに沿えばよい感じです。
Using Docker For ML-Agents
https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Using-Docker.mdDockerのインストール
Dockerがインストールされていない場合、インストールします。
> brew cask install docker (略) > docker --version Docker version 19.03.1, build 74b1e89※Dockerを初回起動すると初期設定のためにパスワード入力が求められます。
UnityにLinuxビルドサポートコンポーネントを追加する
Unity Hubを利用してUnityにLinuxビルドサポートコンポーネントを追加します。
Unityのバージョンは2019.1.13f1を利用しています。
- Unity Hubアプリを起動する
- [Installs] > [On my machine]からUnityリスト右側にある[...]をクリックして[Add Component]を選択する
![]()
- [Add components your install]ダイアログの[Platforms]にある[Linux Build Support]にチェックを入れて[Done]ボタンをクリックする
![]()
学習用のappをダウンロードしてビルドする
ML-Agentsリポジトリをダウンロード
適当なディレクトリにリポジトリをダウンロードする。
> mkdir 適当なディレクトリ > cd 適当なディレクトリ > git clone https://github.com/Unity-Technologies/ml-agents.gitUnityアプリからサンプルプロジェクトを開く
Unity Hubでアプリを立ち上げます。Unity Hubがインストールされていない場合は下記をご参考ください。
Macでhomebrewを使ってUnityをインストールする(Unity Hub、日本語化対応)
https://qiita.com/kai_kou/items/445e614fb71f2204e033ML-Agentsを利用するにはUnityのバージョン
2017.4以上が必要となります。今回は2019.1.13f1を利用しました。アプリが立ち上がったら「開く」ボタンから
任意のディレクトリ/ml-agents/UnitySDKフォルダを選択します。Unityエディタのバージョンによっては、アップグレードするかの確認ダイアログが立ち上がります。
「Upgrade」ボタンをクリックして進めます。
アップグレード処理に少し時間がかかります。
起動しました。
今回は、サンプルとして含まれている[3DBall]Scenesを利用します。
- Unityアプリの下パネルにある[Project]タブから以下のフォルダまで開く
- [Assets] > [ML-Agents] > [Examples] > [3DBall] > [Scenes]
- 開いたら、[3DBall]ファイルがあるので、ダブルクリックして開く
Scenes(シーン)の設定
ML-Agentsで学習させるための設定です。
[Inspector]パネルで以下の設定を確認する
Prefabs(プレハブ)の設定
- Unityアプリの下にある[Project]タブから以下のフォルダまで開く
- [Assets] > [ML-Agents] > [Examples] > [3DBall] > [Prefabs]
- Prefabsフォルダ内の[Game]をダブルクリックする
- Unityアプリの左側にある[Hierarchy]パネルから[Game] > [Pratform]を選択する
![]()
- Unityアプリの右側にある[Inspector]パネルのBall 3D Agent(Script)にあるBrainが[3DBallLearning(LearningBrain)]であることを確認する
- [3DBallLearning(LearningBrain)]ではない場合、
- [Ctrl] + [s]キーでシーンを保存する
- ※設定変更後、しっかりと保存しないと、ビルド時に設定が反映されなくてハマります。
Brainsの設定
- Unityアプリの下パネルにある[Project]タブから以下のフォルダまで開く
- [Assets] > [ML-Agents] > [Examples] > [3DBall] > [Scenes]
- Unityアプリの左側にある[Hierarchy]パネルから[Ball3DAcademy]を選択する
- Unityアプリの右側にある[Inspector]パネルの[Broadcast Hub] > [Brains]に「3DBallLearning (LearningBrain)」が指定されていることを確認する
[Ctrl] + [s]キーでシーンを保存する
※設定変更後、しっかりと保存しないとビルド時に設定が反映されなくてハマります。
ビルド設定
- Unityアプリの[File]メニューから[Build Settings]を選択する
- [Build Settings]ダイアログで[Add Opne Scenes]をクリックする
- [Scenes In Build]で[ML-Agents/Examples/3DBall/Scenes/3DBall]にチェックを入れる
- [Platform]で
PC, Mac & Linux Standaloneが選択されていることを確認する- [Target Platform]を
Linuxに変更する- [Architecture]を
x86_64に変更する- [Server Build]にチェックを入れる
- 以前は[Headless Mode]でした。
- [Build Settings]ダイアログで[Build]ボタンをクリックする
- ファイル保存ダイアログで以下を指定してビルドを開始する
- ファイル名: 3DBall
- フォルダ名: 任意のディレクトリ/ml-agents/unity-volume
すると、
unity-volumeに以下フォルダ・ファイルが出力されます。> ls 任意のディレクトリ/ml-agents/unity-volume 3DBall.x86_64 3DBall_Dataハイパーパラメーターファイルの用意
ハイパーパラメーターファイルを
unity-volumeフォルダにコピーしておきます。> cd 任意のディレクトリ/ml-agents > cp config/trainer_config.yaml unity-volumeDockerコンテナを構築する
Dockerが起動していることを確認してから、ml-agentsリポジトリ直下でdockerコンテナを構築します。すでに
Dockerfileが用意されているので、docker buildするだけ。楽々ですね。> cd 任意のディレクトリ/ml-agents > docker build -t 3dball-ml-docker . (略) Step 20/20 : ENTRYPOINT ["mlagents-learn"] ---> Running in 56532e0d2127 Removing intermediate container 56532e0d2127 ---> 073d8b1040e9 Successfully built 073d8b1040e9 Successfully tagged 3dball-ml-docker:latestDockerコンテナの実行
Dockerコンテナが構築できたら実行してみます。
bashの場合# unity-ml-docker-3dball: コンテナ名(任意) # 3DBall: Unityでbuild時に付けたアプリの名前(拡張子なし) # 3dball-ml-docker: Dockerでbuild時に付けた名前 # docker-first-run: 機械学習結果を保存する際の名称(任意) > docker run -it \ --name unity-ml-docker-3dball \ --mount type=bind,source="$(pwd)"/unity-volume,target=/unity-volume \ -p 5005:5005 \ -p 6006:6006 \ 3dball-ml-docker:latest \ trainer_config.yaml \ --docker-target-name=unity-volume \ --env=3DBall \ --train \ --run-id=docker-first-runfishシェルで実行する場合は、
"$(pwd)"を"$PWD"に置き換えます。fishの場合> docker run -it \ --name unity-ml-docker-3dball \ --mount type=bind,source="$PWD"/unity-volume,target=/unity-volume \ -p 5005:5005 \ -p 6006:6006 \ 3dball-ml-docker:latest \ trainer_config.yaml \ --docker-target-name=unity-volume \ --env=3DBall \ --train \ --run-id=docker-first-run注意点
Unity ML-Agents公式にある
docker runコマンドのサンプルがおそらく動作検証していなくて、パラメータ指定エラーとなります。(2019/08/20時点)以下は
mlagents-learnコマンドのヘルプです。
Dockerで実行する場合には、Dockerイメージの指定(3dball-ml-docker:latest)の後からが、mlagents-learnコマンドのパラメータ指定となるため、--docker-target-nameはtrainer_config.yaml (<trainer-config-path>)の後ろに指定します。mlagents-learn helpUsage: mlagents-learn <trainer-config-path> [options] mlagents-learn --help Options: --env=<file> Name of the Unity executable [default: None]. --curriculum=<directory> Curriculum json directory for environment [default: None]. --sampler=<file> Reset parameter yaml file for environment [default: None]. --keep-checkpoints=<n> How many model checkpoints to keep [default: 5]. --lesson=<n> Start learning from this lesson [default: 0]. --load Whether to load the model or randomly initialize [default: False]. --run-id=<path> The directory name for model and summary statistics [default: ppo]. --num-runs=<n> Number of concurrent training sessions [default: 1]. --save-freq=<n> Frequency at which to save model [default: 50000]. --seed=<n> Random seed used for training [default: -1]. --slow Whether to run the game at training speed [default: False]. --train Whether to train model, or only run inference [default: False]. --base-port=<n> Base port for environment communication [default: 5005]. --num-envs=<n> Number of parallel environments to use for training [default: 1] --docker-target-name=<dt> Docker volume to store training-specific files [default: None]. --no-graphics Whether to run the environment in no-graphics mode [default: False]. --debug Whether to run ML-Agents in debug mode with detailed logging [default: False].実行すると、学習が始まります。
trainer_config.yamlのmax_stepsで指定されているステップ数が完了するか、ctrl+cキーで学習が終了します。> docker run (略) INFO:mlagents.trainers:{'--base-port': '5005', '--curriculum': 'None', '--debug': False, '--docker-target-name': 'unity-volume', '--env': '3DBall', '--help': False, '--keep-checkpoints': '5', '--lesson': '0', '--load': False, '--no-graphics': False, '--num-envs': '1', '--num-runs': '1', '--run-id': 'docker-first-run', '--sampler': 'None', '--save-freq': '50000', '--seed': '-1', '--slow': False, '--train': True, '<trainer-config-path>': 'trainer_config.yaml'} ▄▄▄▓▓▓▓ ╓▓▓▓▓▓▓█▓▓▓▓▓ ,▄▄▄m▀▀▀' ,▓▓▓▀▓▓▄ ▓▓▓ ▓▓▌ ▄▓▓▓▀' ▄▓▓▀ ▓▓▓ ▄▄ ▄▄ ,▄▄ ▄▄▄▄ ,▄▄ ▄▓▓▌▄ ▄▄▄ ,▄▄ ▄▓▓▓▀ ▄▓▓▀ ▐▓▓▌ ▓▓▌ ▐▓▓ ▐▓▓▓▀▀▀▓▓▌ ▓▓▓ ▀▓▓▌▀ ^▓▓▌ ╒▓▓▌ ▄▓▓▓▓▓▄▄▄▄▄▄▄▄▓▓▓ ▓▀ ▓▓▌ ▐▓▓ ▐▓▓ ▓▓▓ ▓▓▓ ▓▓▌ ▐▓▓▄ ▓▓▌ ▀▓▓▓▓▀▀▀▀▀▀▀▀▀▀▓▓▄ ▓▓ ▓▓▌ ▐▓▓ ▐▓▓ ▓▓▓ ▓▓▓ ▓▓▌ ▐▓▓▐▓▓ ^█▓▓▓ ▀▓▓▄ ▐▓▓▌ ▓▓▓▓▄▓▓▓▓ ▐▓▓ ▓▓▓ ▓▓▓ ▓▓▓▄ ▓▓▓▓` '▀▓▓▓▄ ^▓▓▓ ▓▓▓ └▀▀▀▀ ▀▀ ^▀▀ `▀▀ `▀▀ '▀▀ ▐▓▓▌ ▀▀▀▀▓▄▄▄ ▓▓▓▓▓▓, ▓▓▓▓▀ `▀█▓▓▓▓▓▓▓▓▓▌ ¬`▀▀▀█▓ INFO:mlagents.envs: 'Ball3DAcademy' started successfully! Unity Academy name: Ball3DAcademy Number of Brains: 1 Number of Training Brains : 1 Reset Parameters : scale -> 1.0 mass -> 1.0 gravity -> 9.8100004196167 Unity brain name: 3DBallLearning Number of Visual Observations (per agent): 0 Vector Observation space size (per agent): 8 Number of stacked Vector Observation: 1 Vector Action space type: continuous Vector Action space size (per agent): [2] Vector Action descriptions: , 2019-08-19 09:26:41.207263: I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA INFO:mlagents.envs:Hyperparameters for the PPOTrainer of brain 3DBallLearning: trainer: ppo batch_size: 64 beta: 0.001 buffer_size: 12000 epsilon: 0.2 hidden_units: 128 lambd: 0.99 learning_rate: 0.0003 max_steps: 5.0e4 memory_size: 256 normalize: True num_epoch: 3 num_layers: 2 time_horizon: 1000 sequence_length: 64 summary_freq: 1000 use_recurrent: False vis_encode_type: simple reward_signals: extrinsic: strength: 1.0 gamma: 0.99 summary_path: /unity-volume/summaries/docker-first-run-0_3DBallLearning model_path: /unity-volume/models/docker-first-run-0/3DBallLearning keep_checkpoints: 5 INFO:mlagents.trainers: docker-first-run-0: 3DBallLearning: Step: 1000. Time Elapsed: 33.147 s Mean Reward: 1.107. Std of Reward: 0.588. Training. INFO:mlagents.trainers: docker-first-run-0: 3DBallLearning: Step: 2000. Time Elapsed: 61.621 s Mean Reward: 1.227. Std of Reward: 0.680. Training. INFO:mlagents.trainers: docker-first-run-0: 3DBallLearning: Step: 3000. Time Elapsed: 94.060 s Mean Reward: 1.511. Std of Reward: 0.973. Training. INFO:mlagents.trainers: docker-first-run-0: 3DBallLearning: Step: 4000. Time Elapsed: 143.072 s Mean Reward: 1.959. Std of Reward: 1.307. Training. INFO:mlagents.trainers: docker-first-run-0: 3DBallLearning: Step: 5000. Time Elapsed: 201.150 s Mean Reward: 2.899. Std of Reward: 2.278. Training. (略) INFO:mlagents.trainers: docker-first-run-0: 3DBallLearning: Step: 50000. Time Elapsed: 2084.527 s Mean Reward: 100.000. Std of Reward: 0.000. Training. INFO:mlagents.envs:Saved Model INFO:mlagents.trainers:List of nodes to export for brain :3DBallLearning INFO:mlagents.trainers: is_continuous_control INFO:mlagents.trainers: version_number INFO:mlagents.trainers: memory_size INFO:mlagents.trainers: action_output_shape INFO:mlagents.trainers: action INFO:mlagents.trainers: action_probs INFO:tensorflow:Restoring parameters from /unity-volume/models/docker-first-run-0/3DBallLearning/model-50001.cptk INFO:tensorflow:Froze 14 variables. INFO:mlagents.trainers:Exported /unity-volume/models/docker-first-run-0/3DBallLearning.nn file Converted 14 variables to const ops. Converting /unity-volume/models/docker-first-run-0/3DBallLearning/frozen_graph_def.pb to /unity-volume/models/docker-first-run-0/3DBallLearning.nn IGNORED: Cast unknown layer IGNORED: StopGradient unknown layer GLOBALS: 'is_continuous_control', 'version_number', 'memory_size', 'action_output_shape' IN: 'vector_observation': [-1, 1, 1, 8] => 'sub_3' IN: 'epsilon': [-1, 1, 1, 2] => 'mul_1' OUT: 'action', 'action_probs' DONE: wrote /unity-volume/models/docker-first-run-0/3DBallLearning.nn file.
TensorBoardを利用して学習の進捗状況を視覚的に確認することもできます。> docker exec \ -it unity-ml-docker-3dball \ tensorboard \ --logdir=/unity-volume/summaries \ --host=0.0.0.0 2019-08-19 09:43:12.596652: I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA TensorBoard 1.7.0 at http://0.0.0.0:6006 (Press CTRL+C to quit)学習結果をアプリに組み込む
学習結果は、
ml-agents/unity-volumeフォルダ内に保存されます。> ls unity-volume/ 3DBall.x86_64 3DBall_Data models summaries trainer_config.yamlUnityアプリの設定
Playerの設定を行います。
- Unityアプリの[Edit]メニューから[Project Settings]を選択する
- [Inspector]ビューの[Other Settings]欄で以下を確認・設定する
学習結果ファイルの取り込み
ターミナルかFinderで学習結果を以下フォルダにコピーします。
- 学習結果ファイル:
unity-volume/models/docker-first-run-0/3DBallLearnig.nn- 保存先:
UnitySDK/Assets/ML-Agents/Examples/3DBall/TFModels/※すでに保存先に
3DBallLearnig.nnファイルが存在していますので、リネームしてください。> cp unity-volume/models/docker-first-run-0/3DBallLearnig.nn UnitySDK/Assets/ML-Agents/Examples/3DBall/TFModels/
- Unityアプリの[Project]パネルで以下ファイルを選択する
- [Assets] > [ML-Agents] > [Examples] > [3DBall] > [Brains] > [3DBallLearning]
- Unityアプリの[Project]パネルで以下フォルダを選択する
- [Assets] > [ML-Agents] > [Examples] > [3DBall] > [TFModels]
- Unityアプリの[Inspector]パネルにある[Model]という項目に[TFModels]フォルダ内の
3DBallLearning.nnファイルをドラッグ&ドロップする![]()
- Unityアプリの[Hierarchy]パネルから以下を選択する
- [3DBall] > [Ball3DAcademy]
- Unityアプリの[Inspector]パネルにある[Broadcast Hub] > [Brains] > [3DBallLearning(LearningBrain)]横の[Control]のチェックを外す
![]()
- Unity上部にある[▶]ボタンをクリックする
これで、学習結果が組み込まれた状態でアプリが起動します。
参考
Using Docker For ML-Agents
https://github.com/Unity-Technologies/ml-agents/blob/master/docs/Using-Docker.mdMacでUnity ML-Agentsの環境を構築する(v0.9.1対応) - Qiita
https://qiita.com/kai_kou/items/268ccf6f961f8ca8cba8Docker for Macをインストールしてみた
https://qiita.com/scrummasudar/items/750aa52f4e0e747eed68Macでhomebrewを使ってUnityをインストールする(Unity Hub、日本語化対応)
https://qiita.com/kai_kou/items/445e614fb71f2204e033
- 投稿日:2019-08-28T08:56:46+09:00
ドット絵のキャラクターに逆光を当ててみる
前回の記事 の続きです。
ドット絵のキャラクターを自然に3D空間に馴染ませるテクニック https://t.co/juxESYa1sE #Qiita pic.twitter.com/WnlyWOtoRn
— 徳川バガ寅 (@flankids) August 26, 2019こういうことをやりました
逆光の表現 pic.twitter.com/DTrxBnRllD
— 徳川バガ寅 (@flankids) August 27, 2019
- 前回記事で用意したSpriteRendererに 裏から光を当てて影を落とす
- ボリュームライト を適用
SpriteRendererに裏から光を当てて影を落とす
普通にライトの角度変えればいいんじゃないの?と思ってましたが
こっちは影が出るのに
こっちは出ない。。。
これはどういうことかというと、カメラをSpriteRendererの後ろに回るとわかるのですが、裏面に表示用の面が無いため です。その結果、光を遮って影を作ることができることができていません。
Standardシェーダーに細工をする
前回、もろもろの表現をするために、SpriteRendererにStandardシェーダーを適用しました。このStandardシェーダーは ポリゴンの面が無い方向には何も表示しない(カリング) という処理しているのですが、裏面にポリゴンの面がないSpriteRendererにおいてはそれだと都合が悪いです。
1. Starndardシェーダーをダウンロード
そこで、カリング処理をしないStandardシェーダー を用意しましょう。StandardシェーダーはUnity組み込みのシェーダーで、普通は編集することはできません。Unity ダウンロード アーカイブ へアクセスし、今使っているUnityのバージョンから、ビルトインシェーダーをダウンロードしましょう。
ダウンロードしたzipを展開し、フォルダ内から Standard.shader を見つけましょう。それをプロジェクト内にインポートしてください。
2. シェーダー名を変える
インポートしたままだと、Unity内組み込みのStandardシェーダーと名前がバッティングしてややこしいです。ファイル名と、シェーダー名定義を「StandardCullOff」とでも変えておきましょう。
StandardCullOff.shader// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt) Shader "StandardCullOff" // ← ここがシェーダー名定義! { Properties { ...3.カリング処理をOFFにする
Shaderファイル内の54行目くらいの以下の場所に Cull Offと記述しましょう。
StandardCullOff.shaderSubShader { Tags { "RenderType"="Opaque" "PerformanceChecks"="False" } LOD 300 Cull Off // ← これを追加! // ------------------------------------------------------------------4.シェーダーをマテリアルに設定
これで背面からの光でも影が出るようになります!
ボリュームライトを適用
初回の記事でも触れましたが、ボリュームライトとは、本来見えない光の形が、霧やホコリによって見えることです。(参照)空間に奥行きを感じてリアリティが出て、リッチな印象を与えます。
ボリュームライトの実現にはいくつか手段がありますが、今回はGitHubで公開されているVolumetric Lightsというアセットを利用します。
1. Volumetric Lightsをダウンロード
GitHubのプロジェクトページ にアクセスし、Download ZIPをクリック。
2. 必要なファイルをプロジェクトにインポート
フォルダごとプロジェクトにインポートしてもいいですが、最低限必要なのは以下のファイルと思われます。
- Resources
- Scripts
- Shaders
- Textures
適宜プロジェクト内に取り込みましょう。
3.コンポーネントを設定
- Cameraに Volumetric Light Renderer
- Lightに Volumetric Light
をアタッチして、シーンを実行するだけでOKです!ただし、Volumetric Lightのほうは、Directional か Spot のタイプのライトでしか有効にならないとのことなので、ご注意を。
光のシルエットが見えやすいように、光の通り道を部分的に作ったり、影のシルエットを意識したりするとその真価が発揮できる・・・かもしれません。
このあたりコツを掴むのがやや難しいように感じたので、コンポーネントのパラメータの内容や、ライト・オブジェクトの配置の仕方について調べてみようと思います。所感
後ろから光が当たったときは、リムライト的な考え方でSpriteRendererのフチが明るくなるといいのですが、あくまで平面にノーマルマップを持たせたにすぎないので、今のところまだできていません。いよいよシェーダーをちゃんと触らないといけないかも…!
今後はそのあたりと、Depth of Fieldを組み合わせて「オクトパストラベラー」や「The Last Night」っぽいジオラマシーンを作ってみようかなと思っています。
- 投稿日:2019-08-28T07:12:27+09:00
(もうちょっと) 楽に Unity ネイティブプラグイン を実装したい
はじめに
この記事は Unityゆるふわサマーアドベントカレンダー2019 の 28 日目の記事です。
昨年末のアドカレでネイティブプラグインの実装記事を書きました。
この記事では Unity Editor 上でネイティブプラグインを動的にロード/アンロードできるようにすることことで、いちいち Unity Editor を再起動することなくネイティブプラグインのリビルド~入れ替えをできるようになったわけですが、ぶっちゃけそれでもまだ面倒くさいわけです。
結局のところ Unity で動作確認しているから面倒くさくなるので Unity に持ってくる前に手軽に動作確認を・・・もっといえば単体アプリのように IDE 上で即 "デバッグ実行~停止" を高速に行えればよいわけです。
C++ で単体テストアプリを作成 ( Unity HDR Display Output plugin ではそうしてます) すればよいのですが、 C++ だと C# とのつなぎ込み部分が確認できず、 C# 部分は Unity にもっていってからという事になります。できればそこもテストアプリで確認したい。
ということで C# (.NET Framework) でテストアプリを作ってテストできるようにしたいと思います。対象は Windows です。
実例プロジェクトは後日上げます (すいません) 。
環境
- Windows 10
- Visual Studio 2019 (16.2.1)
- Unity 2019.1.11f1
プロジェクトを作る
Unity プロジェクトとテストアプリとはテスト対象の C# コードを共有するようにしておきたいので、配置には配慮したいところです。ソースコードのコピーはしたくないので考えられるやり方としては
- シンボリックリンクをはる
- テストアプリプロジェクトのフォルダー内に Unity プロジェクトを配置する
今回は 2. でやっています。逆にする (Unity プロジェクト内にテストアプリプロジェクト) と Assets の下にテストアプリの .csproj などが配置されてしまうのであまりよろしくないのではないかと思います。
.csproj の直接編集をしているので注意してください。
- Visual Studio で Windows Forms プロジェクトを作成する
- Unity プロジェクトを 1. で作成した .csproj の位置を保存先にして作成する
- Assets の下にネイティブプラグイン用の C# コード (テストアプリと Unity プロジェクトで共有するもの) を配置するフォルダーを作成する
- テストアプリのプロジェクトを VS 上で "プロジェクトのアンロード" を行う
![]()
- "編集" を選択して .csproj を開く
![]()
- <ItemGroup> タグに 3. で作成したフォルダーを <Folder> タグで追加する。(下記は例)
<Folder Include="UnityNativePlugin\Assets\NativePluginTest\Scripts\" />![]()
- "プロジェクトの再読み込み" をする
以上でテストアプリと Unity プロジェクトで必要な C# コードの共有化準備ができました。 "Assets" までは Unity 側でフォルダーを作成してしまうので、 .csproj 直接編集しないとうまくいかないと思います。これ以後はテストアプリの .csproj で 6. のフォルダーにコードを追加していけば Unity 側からも参照できるようになります。
ネイティブプラグインとつなぎ込みコードの実装
ネイティブプラグイン自体は通常の手順で実装します。
つなぎ込みに関しては DllImport などの DLL 読み込み定義と C# で使いやすくするためのラッパー実装など Unity に非依存なコードまでを実装します。
この辺りに関しては私の過去記事もご一読ください。
テストアプリを作成する
テストアプリのプロジェクトにテストコードを実装していきます。
Unity で扱うのであれば GPU を利用したプラグインである事が多いはずです (もしそうでなければ本記事はここまで) 。よってテストアプリでもグラフィックス API を扱っていきます。
.NET で DirectX を扱う場合は SharpDX というライブラリを利用します。
- sharpdx/SharpDX (GitHub)
- NuGet
SharpDX は機能別に細かくパッケージに分かれています。テストアプリの作成には上記くらいのパッケージは必要になります (SharpDX.Desktop は必須ではないですが使った方が楽) 。
SharpDX はプロジェクトが終了していますが、十分安定していると思うので Direct3D 11 レベルまでだったら SharpDX でよいと思います。 Direct3D 12 の最新を使用するのであれば別のライブラリを検討してください。
UnityPluginLoad / UnityPluginUnload に対応する
Unity のネイティブプラグインはロード時に UnityPluginLoad 、アンロード時に UnityPluginUnload が呼ばれます。UnityPluginLoad で渡される IUnittyInterfaces を使う (GPU の Device を必要とする) ネイティブプラグインをテストアプリで確認するには UnityPluginLoad に対応しなくてはならないわけです。
そこで問題になってくるのが "ネイティブプラグインから呼ばれる IUnityInterfaces をどうやって実装するか" です。 ネイティブから呼ばれるので C++ で書けば簡単確実ですが管理するものが増えてしまって最終的には面倒になるので C# で実装してみました。
using System; using System.Runtime.InteropServices; using D3D11 = SharpDX.Direct3D11; namespace NativePluginTest { class UnityInterface : IDisposable { private delegate IntPtr FnGetInterface(Guid guid); private delegate IntPtr FnGetDevice(); private FnGetInterface _fnGetInterface; private FnGetDevice _fnGetDevice; private GCHandle _handleUnityInterfaces; private GCHandle _handleUnityGraphicsD3D11; private IntPtr[] _unityInterfaces; private IntPtr[] _unityGraphicsD3D11; private D3D11.Device _device; public UnityInterface(D3D11.Device device) { _device = device.QueryInterface<D3D11.Device>(); _fnGetInterface = UnityInterfaceGetInteface; _fnGetDevice = UnityGraphicsD3D11GetDevice; _unityInterfaces = new IntPtr[] { Marshal.GetFunctionPointerForDelegate(_fnGetInterface) }; _unityGraphicsD3D11 = new IntPtr[] { Marshal.GetFunctionPointerForDelegate(_fnGetDevice) }; _handleUnityInterfaces = GCHandle.Alloc(_unityInterfaces, GCHandleType.Pinned); _handleUnityGraphicsD3D11 = GCHandle.Alloc(_unityGraphicsD3D11, GCHandleType.Pinned); } public void Dispose() { _device?.Dispose(); _device = null; _handleUnityInterfaces.Free(); _handleUnityGraphicsD3D11.Free(); } public IntPtr GetUnityInterfaces() { return _handleUnityInterfaces.AddrOfPinnedObject(); } private IntPtr UnityInterfaceGetInteface(Guid guid) { return _handleUnityGraphicsD3D11.AddrOfPinnedObject(); } private IntPtr UnityGraphicsD3D11GetDevice() { return (_device?.NativePointer).GetValueOrDefault(); } } }要点としては
- IUnityInterfaces の実態は構造体定義の関数ポインターテーブルなので IntPtr 配列に Marshal.GetFunctionPointerForDelegate で取得したポインターを設定する
- IntPtr 配列は GCHandle.Alloc でピン止めしてポインターを動かないようにしておく
とりあえず Direct3D Device を渡せればよいのでその関連のメソッドだけ実装しました。
本来、 IUntyInterfaces::GetInterface に対応する実装では渡される UnityInterfaceGUID に応じて適切なインターフェースポインターを返さなくてはいけないですが、ここも決め打ち実装にしています。
ピン止めですが、 .NET の Object は参照が有効でも実体の場所は保証されていないので、ピン止めすることでその Object の実体位置が固定化されます (そもそもピン止めしないとポインターは取得できないですが) 。ピン止めはその仕組み上、長時間ピン止めするのは望ましくないですが、今回の場合はアプリ終了まで維持する必要があります。
Marshal.GetFunctionPointerForDelegate で取得したポインターは個人的には結構謎なのですが、参照元のデリゲート自身を GCHandle.Alloc することができないようなので別の仕組みでポインターの有効性を保証しているのでしょう (多分) 。
使う時は次のようにします (抜粋) 。
using System; using DXGI = SharpDX.DXGI; using D3D11 = SharpDX.Direct3D11; [DllImport("NativePlugin")] static extern void UnityPluginLoad(IntPtr unityInterfaces); [DllImport("NativePlugin")] static extern void UnityPluginUnload(); static void Main(string[] args) { DXGI.SwapChain swapchain = null; D3D11.Device device = null; UnityInterface unityInterface = null; try { var desc = new DXGI.SwapChainDescription() { /* 略 */ }; D3D11.Device.CreateWithSwapChain(D3D.DriverType.Hardware, D3D11.DeviceCreationFlags.None, desc, out device, out swapchain); unityInterface = new UnityInterface(device); UnityPluginLoad(unityInterface.GetUnityInterfaces()); // テストコードをここに書く UnityPluginUnload(); } finally { device?.Dispose(); swapchain?.Dispose(); factory?.Dispose(); unityInterface?.Dispose(); } }SharpDX で Direct3D Device を生成し、それを元に UnityInterface クラスのインスタンスを生成します。 UnityInterface からは IUnityInterfaces の実装コードのポインターが取得できるのでこれを引数にネイティブプラグインの UnityPluginLoad を実行します。
これにより初期化からテストアプリで動作確認ができるようになり、 Unity に持っていく前に行えるテスト範囲が大分広くなりました。
おわりに
ネイティブプラグインの実装は必要でないケースの方が多いとは思いますが、いざやろうとするとどうしても面倒と感じてしまう場合が多いように思います。
前回記事でも書きましたが事前準備が多いのでそこがすでに面倒くさい感じもするかもですが、一度準備すれば後は早いと思いますのである程度以上の規模になるなら見合った手間になるのではないかと思います。テストアプリ部分はテンプレート化しておきたいですね。













































