初心者がUE4でルービックキューブ風なものを作って強化学習用のライブラリにしたい #3

前回に引き続き初心者がUE4を絡めたPythonライブラリを作れないか色々進めていく記事です（ほぼ自分のための備忘録としての面が強い・・）。

最初 : [#1](https://qiita.com/simonritchie/items/e8d84c2941b5bd2862f5)
前回 : [#2](https://qiita.com/simonritchie/items/c72e24368909f4fb196e)

# 回転処理の関数のブラッシュアップ

前回の記事で作った回転の関数が、少々煩雑な気がしてきており、且つ何個も作る形になる（なるべくシンプルにしたい）ので、ブループリントがシンプルになるように抽象化された関数を挟んだり等、変更していきます（DRY原則的に）。

assert用の処理は加えてあるので、躊躇せずがっつり手を加えていきます。

## 移動のアニメーションの関数の抽象化

applyMoveAnimValueという関数を追加して、各軸の回転とアニメーションの関数から共通で利用し、アニメーションに必要なパラメーターを渡してアニメーションさせるようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/0d7e0eda-bef3-ea74-b500-9ef1c1c93a89.png)

引数に対象のキューブ位置の組み合わせの配列や、特定の軸方向に応じた位置の値、そして1回のアニメーション量の配列の引数を設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ab38efb9-a4fa-0ba5-ef6c-e34a151a3055.png)

最初に呼び出している関数では、対象の回転方向の軸に対する回転制御用の真偽値がいずれかがTrueになっていることをチェックするように設けてあります。
ForEachLoopWithBreakというノードを今回はじめて使いましたが、どうやらLoopBody内でBreakさせる条件に該当したら、BreakのExecピンに流すとループが終了してCompletedのピンに処理が流れるようです。そのため、ループの箇所で上のスクショのように循環するようなノードの繋ぎ方にしてあります。

参考 : [UE4 繰り返し処理を行っている途中で終了する（For Loop With Break、For Each Loop With Break）](http://kagring.blog.fc2.com/blog-entry-412.html)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/d7dea764-6077-f486-62e8-9f4f2285e358.png)

続いて各キューブ位置の配列分ループさせていきますが、ループ最後のインデックスを指定された配列に応じて取得する関数を挟んでいます。回転する方向の関数によって、3つある配列のうち2つだけを利用するので、長い配列の方を参照して算出します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/f72635fd-f9f0-16ba-34d5-5bd01c0a3c12.png)

MAXとかのノードは用意されているようですね。
なお、ForLoopなどで指定するのは最後のループインデックスであり、jsやPythonみたく配列件数とかではないので-1しておきます（0スタートなので）。
（明示的にLast Indexと引数に表示されるのはミスを減らせて親切だなと思いました）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/6144308e-66e9-072d-ce18-2ea0e35b0435.png)

続いて、現在のループのインデックスに応じた各位置の種別値を取得する関数を挟んでいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/f4ed9f88-a72c-633f-fa0e-388d295480b2.png)

内容としては、各配列で件数が長ければ配列内の値を返却値に、そうでなければtargetCubePositionの値を設定という感じにしています（正面から見て水平方向に回転させる場合には水平方向と奥行き方向の各位置の種別は配列から取り、縦軸方向の値は固定でtargetCubePositionから取るといった具合に）。

配列の値が不要な場合でもMakeArrayを挟んで対応している都合、他の言語みたく配列件数が0かどうかでの判定ができず、最低1件になってしまうのですが、これはもっといい使い方がある・・のでしょうか・・？
（今回は特にこれでも困りませんが・・）
引数に指定する値でMakeArray自体を省くと、スカラー値を引数で省略したりするときとは異なりコンパイルエラーになってしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/5a29978f-617b-4eea-1d12-1aa9ef35d4ba.png)

続いて、取得された位置の種別が対象のCubeの現在の値と一致しているかどうかをBranchノードで判定しています。一致している時だけアニメーションさせます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/4c88e086-7051-7c51-fe30-c55d437e22e4.png)

アニメーション量の値が1を超過している配列かどうかをチェックし・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/f7d387a8-ebee-f73d-3f76-2da3d9477b54.png)

超過している場合は1回のアニメーションにおける変化量（Animation○Offsetというにしました。）を除算など使いつつ設定し、超過していない場合は0とします（OffsetよりもDeltaとかの方が名前として向いているのだろうか・・？）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/8153df33-d3a9-5356-a3ea-b824f66bd2d2.png)

算出が終わったらベクトルにしてAddActorWorldOffsetノードに反映して位置をアニメーションさせます。

## 回転のアニメーションの関数の抽象化

回転のアニメーションも同様の対応をしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/25f65b3b-bfae-1cb2-37ba-ab529f0cacdc.png)

ほぼ移動の時と引数は同じなのですが、途中でswitch文的な分岐を挟んでいるので、回転方向の種別（別途定数で定義）を指定するようにしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/c1de15d3-7697-6363-a3d7-04de32e4cf62.png)

回転の方向の種別値に応じて回転量を分岐させます。
なお、今Qiitaにまとめていて気づきましたが、普通にSwitch on Intというノードがあったのでそちらの方が良さそうな気配がしますね・・（長いことPythonばっか触っていたので、switch文が頭から消えていた感が・・・）
今後こういったケースが出てきたら、switchノードも使ってみようと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b2be1ad8-6cc6-eecf-c615-c75a3a974e5f.png)

一度の回転で90度回転したら終わりなので、その辺りの判定用の更新処理や分岐を入れておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/0c5ba36c-54a4-c69f-f496-8d2738b83d11.png)

90度分の回転が完了したら、各回転方向に応じた各フラグの組み合わせを一通りFalseにしておきます。

その他、位置の種別値の更新などもこのノードの流れで対応しておきます（長いのでスクショは省略・・）。

## 抽象化した関数を参照する形に各回転の関数を差し替える

用意した抽象化された関数を挟むことで、個別の回転の関数は以下の処理だけになります。

- 回転方向に準じた真偽値の配列の準備
- 回転のリストとなる位置の種別値の配列の準備
- アニメーションの値の配列を準備
- アニメーション後の位置の種別の配列の準備
- 初期値設定用の関数の呼び出し
- 移動と回転のアニメーションの関数の呼び出し

順に見ていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/4d445917-3a85-e994-aabc-4656840cba43.png)

まずは真偽値の配列と位置の種別の配列の定義を上記のようにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b61fb201-92d9-e0ae-8328-03d84c1c9248.png)

続いてアニメーション用の配列なども用意しつつ、それらを位置のアニメーション用に抽象化した関数に渡して実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/67e5d82b-3980-f6c0-f694-d3b9b9ba2a5d.png)

続いて回転用の準備した関数に、回転終了後の位置の種別の配列なども準備しつつ設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/d8c5c494-f46a-9c38-2de0-38e3be8e4696.png)

後は、アニメーション開始時の際のみ、初期値を変数に保持しておくための関数をびだしておいて終わりです。

俯瞰してみるとこんな感じになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/60b2652f-fc11-7a9d-ff3b-441165221b0c.png)

大分ノードの数が減りました。これを各方向に対して6個作ります。
用意している配列も、最初は頭が混乱していましたが、作っていると可視化されて私の頭でも大分パターンが把握できたので、その辺も一括でできたら尚良い（というか時間かければできそう）・・のですが、これでも対したノード数ではないのでこのまま進めます。

assert用の関数も、同様に似たような対応をしておきます（割愛）。
修正後、とりあえずassertの処理を通ることを確認して一旦回転アニメーション回りは一段落です（やることはまだまだ色々ありますが・・）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/8586ab85-dcb3-5c02-b58c-7186a55548d0.png)

*とりあえずassert通って少し試しに回転させてみた図*

# カメラの初期位置を調整する

そろそろゲームスタート時のカメラの初期位置を設定しておきます。
Player Startのオブジェクトを画面に配置することで設定できるので、斜め上から俯瞰気味に見れる適当な位置に配置します。多少左右均等な位置ではない気がしますが、まあデザインをこだわるものでもないので、あまり拘らずに進めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/46868c4d-b3ea-8f8e-59a4-ecbbd355092c.png)

以下のような位置とカメラの向きに設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/2bc77530-c0f8-a577-7c4a-eea0f19c484a.png)

画面上ではコントローラーと旗のようなアイコンで表示されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/94e84cd1-fa2e-4fd5-766b-f3c4b69887d9.png)

プレビューしてみるとこんな感じに上から3面見える形でカメラが配置されているのが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/16d984d1-3354-6828-578f-a0ebc82f3072.png)

# 即時の回転の処理を組み込む

アニメーション付きの回転の処理は前述までの対応で組めましたが、強化学習で使っていく際にはアニメーションしていると学習で時間が膨大にかかってしまいます。
（スクエニの方もCEDEC2019とかで、学習時間の大部分がシミュレーションだとおっしゃっていたのも踏まえ）

参考セッション : [［CEDEC 2019］ゲームの敵AIはAIに作らせる。「『強い』を作るだけが能じゃない！ディープラーニングで3Dアクションゲームの敵AIを作ってみた」聴講レポート](https://www.4gamer.net/games/999/G999905/20190906094/)

ただし、すべて即時の回転だけだと、最終的に人に見せたりする際の見栄えが良くありません（アニメーションした方がどんな感じに回転させているのか分かりやすい）。

そこで、アニメーション付きの回転処理とは別に、即時で回転してくれる関数を用意していきます。

ところどころ、アニメーションするタイプの関数と処理が被る箇所が出てくるので、各所のノードを関数分割や定数化などを進めて、ノードがなるべく重複しないようにしつつ対応していきます。

## 抽象的な関数を用意する

アニメーションするタイプの関数同様、即時の回転用の関数でも各回転方向共通で利用する抽象的な関数をまずは用意します。
引数は多めですが、ある種ブループリントってキーワード引数的な指定の仕方になるので、まあ多くなってもいいか・・・ということで進めます（キーワード引数が無い言語とかだと、引数の数が多いとつらみがありますが、Pythonとかだと引数多いライブラリとかも多く気になっていないというのもあり・・・）。

関数はrotateImmediatelyという名前にしました。
assertの処理などは共通で使いまわすので、こちらでも回転前の初期値を保持させておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/fe77c3c1-a71d-899e-a988-462c18ea8238.png)

各キューブに対してループを回していきます。
また、ループ中に一度更新されたらもう更新しないようにしておきます（Updatedノード部分）。
Break付きのForLoopノードにしようか迷いましたが、特に重い処理でもないのと、結構横に長い関数なので、BreakのExecピンに戻す記述も長くなりそうで、読みづらいかな・・・という印象をもったので普通にForLoopで進めてあります。
（ただ、後で考えると、ForLoopの直後にUpdatedの判定の箇所でBreakのピンに繋いでしまえば良かったな・・・とか思いつつ）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/51fff1f7-48d2-e27d-7a88-7f13792ab927.png)

ループにおける対象の各キューブ位置の種別値やインデックスをローカル変数に設定し、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/808e4024-6b3d-c1c1-14d5-146da6092c4b.png)

現在のループの対象が、キューブの現在位置の種別にマッチする場合のみ処理を進めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/280a4ca9-14f3-77fa-8d3d-355aa4f597c0.png)

対象のループのインデックスのアニメーション量を取得します。この部分はアニメーションする方の関数とも共通で使えるように、関数分割してあります（アニメーションする方の関数ではDivision Valueの値を90とかにして、関数で返却される値を1回のアニメーションに合わせて小さい値にしています）。
XYZそれぞれで対応します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/5ddad7e6-3af7-f5f1-c6fa-0ee0cc6c277c.png)

算出された位置のアニメーション量をキューブのアクターに反映します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/6ed1c2a8-5a27-428f-ee74-0cf9ca6f59a7.png)

回転も反映します。ここも関数分割してアニメーションする方と共通化してあります。
なお、assertの処理は事前に挟んであるので、結構既に組んだ関数にもどんどん手を加えていますが最終的にデグレせずに進められています（ミスがあると気づける形に）。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">個人的に触っている程度でも、ブループリントでもassert的なチェックは大事だなぁと感じます。<br><br>後でしつかりUE4のテスト周り調べよう…</p>&mdash; simonritchie (@simonritchie_sd) <a href="https://twitter.com/simonritchie_sd/status/1188387594549092352?ref_src=twsrc%5Etfw">2019年10月27日</a></blockquote>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/27341919-ba89-a6c9-5646-18e838f2117e.png)

最後にキューブの位置の種別値を移動後の値で更新し、更新済みとしてもうループ内で更新しないように真偽値を更新します。

## 各方向への即時の回転の関数を追加していく

先ほど用意した共通で参照する関数を呼び出す形で、ラッパー的に各方向の即時の回転の関数を用意していきます。

各キューブの位置の種別のリスト、移動量、移動後の位置の種別値のリストは、アニメーションする方の関数と共通なので定数化などをしてあります。関数呼び出し以外ほぼノードが無いシンプルな関数になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/0586debb-7ecb-3b68-bf13-76812d62aaeb.png)

これを、各方向で6つの関数を作りました。
レベルのブループリントで動かしてみて、動作確認をしてみます。assert用のノードはアニメーションする関数と共通です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/86b4ba3f-5a83-f285-bcfb-73ea9478f6f9.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/81c4a3e9-d4cf-4f65-ed93-f392bc5bce6d.png)

即時で回転することが確認でき、大丈夫そうなので次の対応に進みます。

# そろそろPythonを入れておこうと思ったものの・・・

だんだんとGymライブラリ的なインターフェイスを作っていこうと思いますが、都度Python側でも動作確認していこうと思うため、プロジェクトにPythonを入れておきます。

Pythonの入れ方は以前既に試してあるので、そちらを参考に進めていきます。
参考 : [UE4でPythonを使ってみる](https://qiita.com/simonritchie/items/58fe6d4d3c411c8c062f)

が、そういえば↑の記事はWindows環境で進めていましたが、現在Ubuntuで進めています・・・
どうやら結構必要な対応が異なる模様。

一応Linuxでのインストール方法も書かれていますが、以下のような点が段々気になってきており、このままLinuxで進めるべきなのか・・・Windowsに切り替えべきなのか・・・？といった具合に悩ましい感じになってきました。

- なぜかよく分からないものの、ブループリント上で日本語のコメントなどが入力できない
- デスクトップ向けだとパッケージングがLinux向けにしかできない気配がある（まあ当たり前といえばそうかもですが・・・）
- Windowsの方がシェアが高いしWindows上で開発してWindows向けに出した方がいいのではという気がしてきている（理想は各プラットフォームですが、手間を考えると・・）
- Linux環境のパッケージングなどの情報が少なくて苦労しがち
- 今回のPythonのものも、Linux向けの対応で進めた場合に後でWindowsに移そうとした際にこけるのではないかという懸念がある（未検証）
- PyTorchとかを動かして遊ぶくらいなら、Windowsでも十分動いてくれるのではないかという印象がある。
- 環境も、ビルドの手間とかコマンドラインでの対応とかがぼちぼちあって気になる（WindowsみたくGUIとかが欲しい）
- Linux環境だとちょっと古めのPythonしか対応していない気配がある（3.5とか）
- 自動保存で強制的にUE4 Editorが前面に来るのが微妙に感じる（しかし自動保存は有効にしていたい。どうやらLinux環境のみの挙動な模様・・）
- そもそもCreativeCloudや他のツールなど、Windows環境なら使えるツールが使えなかったりしてそちらを使いたいときに辛い（Ubuntu上でGifアニメ作るのも結構手間そうで対応できていない）

ということで、しばらく考えたのですが、Windowsに環境を移すことに決めました。Linuxが必要になったらWindows上でLinux動かして開発してみる・・とかもアリかもしれませんし・・。
ブループリントなどはGitで管理していたので、そのままWindowsに移行できるのか・・などもよく分かっていないので、試していってみます。

同バージョンの4.21のUE4をWindowsにインストールし、起動後に新規プロジェクトの作成ではなくプロジェクトファイル（`.uproject`）ファイルをブラウズしてプロジェクトを読み込みました。

その際、「このプロジェクトは別のバージョンのUE4で作成されています。編集データが崩れるかもしれないのでバックアップ取ってね」というなんだか不穏な警告が出ました。
どうやらLinux版とWindows版で同じバージョンのUE4を使っていても、バージョンが異なるケースと同様の扱いになって完全にそのまま持ってくることができない模様・・・

致し方無いのでそのまま読み込みます。
読み込んでみた結果、スターターコンテンツの中身ががらっと上書きされました（レベルも初期化などされているようです）。

スターターコンテンツで作成を始めて、初期表示のマップ（レベルのファイル）でそのまま作業していた（レベルのブループリント含み）ので、そちらがどうやら消えています。
そのあたりは、gitの編集が入ったファイルを確認しつつ、元に戻したいものは一旦UE4を停止してからrevertするなどして、再度UE4を起動してみたところ元のレベルは復元できました。加えて、なんだかそれでもレベル上に謎のアクターが配置されていたので、一部削除しています。
プレビューしてみて、Ubuntu環境と同じような表示となり、各ブループリントもとりあえずは問題なく動いていそうな気配があります。一安心ですね・・・（他のゲームエンジンとは異なり、UE4ではこの辺りの対応が初めてなので挙動が読めずに最初は結構怖いところです・・）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b8bf1599-d6b0-722b-38e3-e74a7ad2d69c.png)

スターターコンテンツで一部消したりしていたものの、再度諸々が追加されているので、パッケージング前までにいろいろ整理して余分なものは消しておくようにします。

# コメントとかを追加しておく

Ubuntu環境のUE4では日本語入力のなぜかコメントとかができなかったのが、Windows環境に移ったことによって入力できるようになったので、各関数などに日本語でコメントを設定しておきます（きっとLinux且つ日本語圏で作業している方が少なくてissueなどに上がらないのかな・・と考えています）。

コメントを書いていて思いましたが、改行ってどうやるんだ・・？と思い調べてみました。どうやらShift + Enterで改行できる模様。

> 改行は"Shift + Enter"で行えます。
> [【UE4】コメント機能 ①](http://shuntaendo.hatenablog.com/entry/2017/04/28/171534)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/a5d82013-fb49-97d1-27aa-3ca9fd22e6ac.png)

引数がある場合、皆さんどうされているのでしょう？docstringとかjsdoc的なものを関数の先頭のノードとかに追加したりしているのだろうか・・？
この点に関しては検索してもいまいちヒットせず。
まあヒットしないということは明確な決まりは特にないということだと思うのと、プライベートでの利用なので厳密なルールとか無くてもいいと思うため、Pythonのdocstring風に書いていこうと思います（Pythonを並行して書いたりすることきにスイッチングコストが低く済むように）。

余談ですが、検索とかしていて気づきましたが、関数のノードにコメントを付けておくと検索結果の一覧で対象の関数のコメントが表示されるようです。便利。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/0211924c-c5b7-6812-2801-1af383c83836.png)


# 開発中の環境かパッケージングされた環境かの判定で、assertのところの配布版で無駄な処理を止めておく

Print Stringノードとかだと、Development Onlyみたいな表示があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/4d46c8ff-686e-44df-5d14-2f977fcb1f4e.png)

このような、開発中だけ実行される真偽値とかどうやってUE4で取るのだろう・・と調べました。
ブループリントエディタ上でdevelopmentと検索していると、Is Packaged for Distributionというそれらしきノードが見つかりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/287c8cb4-10c1-4d98-7002-3558a1c8efc4.png)

ネットで検索してみても、どうやらこのノードで問題なさそうな気配があります。

> 「Is Packaged for Distribution」というノードを使えばできるみたいですね。
> ...
> 「Returns whether this is a build that is packaged for distribution」とあるので
パッケージ化されている場合のみ true を返すノードのようですね。
> [UE4 パッケージ化時には処理されないデバッグ処理を実装する（Is Packaged for Distribution）](http://kagring.blog.fc2.com/blog-entry-337.html)

Pythonコードを書く際にも、ある程度チェックしてくれるテストを書いたりしたいので、その際にもこれらを使っていこうと思います（本格的な機能テストとかまでは仕事じゃないのでスキップしようと思いますが、コードでミスしていないかを開発中に起動時にチェックしてくれたりくらいはしてほしいので）。

以下のように各assertの処理の最初にBranchノードで繋いでおきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/cca2347c-9f51-d209-37d2-53412703f980.png)


# 今度こそPythonのプラグインを入れる

これで「[UE4でPythonを使ってみる](https://qiita.com/simonritchie/items/58fe6d4d3c411c8c062f)」の記事と同じWindows環境・UE4バージョンとなったので、さくっと入れれるはず・・ということで記事の通りに入れていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/3d6298c6-e7c4-f29b-9151-10bd7c86000e.png)

UE4上のPythonエディタなども問題なく起動しました。

## hdf5用のPythonライブラリ（h5py）を入れておく（※駄目だった）

最終的には、別途Jupyter上などからpipでインストールされたライブラリを使って、UE4でパッケージされたものとやりとりをする必要があります。
どうしようか・・・という感じですが、単純にローカルファイルを経由してやりとりをしようと思います。

ファイルのうちで部分的に更新して扱っていこうと思いますので、HDF5を使っていこうと思います（頻繁にファイル全体的に保存したりなどするとそれなりに負荷になりそうなので）。

HDF5に関しては以前記事にしているので、そちらも参照しつつ進めます : [意外と奥が深い、HDFの世界（Python・h5py入門）](https://qiita.com/simonritchie/items/23db8b4cb5c590924d95)

また、UE4のPythonプラグインで、pipでPythonライブラリを追加するという点も、以前別の記事で試してあります : [UE4でPythonを使ってみる](https://qiita.com/simonritchie/items/58fe6d4d3c411c8c062f)

インストールは、分析作業などであればAnacondaで最初から入っているのですが、今回はUE4のPythonプラグインを使うため、公式ドキュメントの[Installation — h5py 2.10.0 documentation](http://docs.h5py.org/en/stable/build.html)を参考に進めていきます。

`<プロジェクトパス>\Plugins\UnrealEnginePython\Binaries\Win64\`のパスでコマンドラインなどを起動し、以下のコマンドでh5pyをインストールします。

```
$ ./python.exe -m pip install --target . h5py
```

```
Successfully installed h5py-2.10.0 numpy-1.17.3 six-1.12.0
```

無事インストールされ、依存モジュールのNumPyやsixなども入ったようです。
Anacondaほど依存関係でよしなにやってくれる・・・というものでもない（以前古い環境にpipだけでインストールしたらぼちぼち躓いたり・・・）ので、実際に動くのか試してみます。

まずはプラグインフォルダにあるpython.exeをダブルクリックして起動して、インタラクティブシェルでimportなどを試してみます。

```py
import h5py
f = h5py.File('./test.hdf5', 'w')
```

特にエラーなど無いようですので、実際にUE4上で試していきます。
簡単な読み書きだけ試していきます。

`Content/Scripts/`以下に配置していきます。python_and_h5py_test.pyという名前でとりあえず配置します。編集はプラグインのエディタだと辛いため、VS Codeを使っていきます。
一旦Hellow World的なもののみ設定します。

```py:python_and_h5py_test.py
import unreal_engine as ue

ue.log('Hello UE4!')
```

また、PyActorクラスを継承したブループリントのクラスを追加します。
後で消すのでファイル名はBP_PythonTestとしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/1139934b-896a-76e5-845a-c883b194ee66.png)

ブループリントの`Python Module`の箇所に追加したモジュール名の`python_and_h5py_test`を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/03df499e-2215-b02b-bfa8-bbcce3e6ea46.png)

ブループリントをコンパイルして、現在のレベルに配置し、プレビューします。

OutputLogウインドウでPythonとフィルタリングしてみると、出力が確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/adfefbe4-7f63-924a-51c5-7c49095efe10.png)

Hello World的なところは問題なさそうなので、h5pyの方も挙動を確認してみます。

UE4上でimportしてみるとエラーが発生しました

```py:python_and_h5py_test.py
import h5py
```

```
LogPython: Error: AttributeError: type object 'h5py.h5.H5PYConfig' has no attribute '__reduce_cython__'
```

Cython・・？
調べてみると、どうやらh5pyとCythonのバージョンの依存関係によるもののようです。でもCython使っていないような・・？

参考 : [AttributeError: type object 'h5py.h5r.Reference' has no attribute '\_\_reduce_cython\_\_'](https://qiita.com/ankomotch/items/52baffedf43ffb2385d8)

`pip freeze`コマンドでCythonバージョンを調べてみます。

```
$ ./python.exe -m pip freeze
```

```
h5py==2.10.0
numpy==1.17.3
six==1.12.0
```

やはり入っていません。Cythonがh5pyで必要だけど、pipでは入らない・・・という感じなのでしょうか？
リンク先の記事を参考に、Cython==0.27, h5py==2.8.0のバージョンをインストールしてみます。

```
$ ./python.exe -m pip install --target . h5py==2.8.0
```

```
Successfully installed h5py-2.8.0 numpy-1.17.3 six-1.12.0
```

```
$ ./python.exe -m pip install --target . cython==0.27
```

```
Successfully installed cython-0.27
```

インストールは無事通ったようです。しかしこれでもエラーが消えない。
UE4を再起動してみると、別のエラーも出ていることが分かりました。

```
LogPython: Error:   File "h5py\h5.pyx", line 1, in init h5py.h5
LogPython: Error: ImportError: DLL load failed: 指定されたプロシージャが見つかりません。
```

Tracebackで出ていた、version.pyというh5pyのコードを見てみると、なにやら相対importになっています。
これでこけている模様・・・（UE4用にパス指定などしているので引っかかるのだろうか・・・）

```py:version.py
from . import h5 as _h5
```

本当はあまりライブラリのコードを直接編集するのは良くないのですが、しょうがないので少し触って以下のように編集してみます。

```py:version.py
from h5py import h5 as _h5
```

先ほどのエラーは消えたものの、以下のCythonのエラーはまだ出ているようです。

```
\Plugins\UnrealEnginePython\Binaries\Win64\h5py\version.py", line 18, in <module>
    from h5py import h5 as _h5
LogPython: Error:   File "h5py\h5.pyx", line 41, in init h5py.h5
LogPython: Error: AttributeError: type object 'h5py.h5.H5PYConfig' has no attribute '__reduce_cython__'
```

以下を見ると、何やらCythonとNumPyバージョンが絡んでいるらしいとの記述があるので試してみます。

[Attribute error: no attribute '\_\_reduce_cython\_\_' ](https://github.com/pynbody/pynbody/issues/503)

リンク先で書かれているように、1.16.1にNumPyバージョンを調整してみます。

```
$ ./python.exe -m pip install --target . numpy==1.16.1
```

```
Successfully installed numpy-1.16.1
```

※UE4起動したままだと以前のバージョンのまま更新されなかったため、一度UE4を落としてライブラリフォルダを削除・その後にh5pyをインストール → numpy==1.16.1をインストール、と進めてみました。

```
...
LogPython: Error:   File "h5py\h5r.pxd", line 21, in init h5py._conv
LogPython: Error:   File "h5py\_objects.pxd", line 12, in init h5py.h5r
LogPython: Error:   File "h5py\_objects.pyx", line 1, in init h5py._objects
LogPython: Error: ImportError: DLL load failed: 指定されたプロシージャが見つかりません。
```

うーんダメっぽいですね・・・
少しh5pyのコードを眺めていたのですが、結構色々な箇所で相対importがあって、それが絡んでいるのかもしれません。
これ以上は結構トリッキーなことをしないとなので、別の方法を検討してみます（Anaconda経由とかでのインストールなら特に動作不良に悩むことは少ないのですが・・・）。

一旦h5pyやNumPy関係など追加されたものはアンインストールしておきます。

# SQLiteで何とかならないか試してみる

要するにデータを頻繁にそこまで負荷にならずに色々やりとりできればなんでもいいので、次はSQLiteを試してみます。

PythonにおけるSQLiteに関しても以前調べてあるので、そちらを参考にしつつ進めます。

[PythonでSQLiteを使ってみる（DB4S・SQLAlchemy）](https://qiita.com/simonritchie/items/705148154f810ee308e2)

まず、SQLiteファイルの保存先にも絡んでくるので、プレビュー中のPythonの作業ディレクトリがどこになるのか調べてみます。

```py
import os

import unreal_engine as ue

ue.log('current dir: %s' % os.getcwd())
```

```
LogPython: current dir: C:\Program Files\Epic Games\UE_4.21\Engine\Binaries\Win64
```

Program Filesの中なのか・・・ここにSQLiteのファイルが設置されるのはなんだか微妙なので、デスクトップにフォルダが作られて、その中にSQLiteのファイルが配置されていく、みたいな感じにしていきます。
Pythonでのデスクトップパスの取り方は以下を参考にしました。

参考 : [How to get Desktop location?](https://stackoverflow.com/questions/34275782/how-to-get-desktop-location)

```py
ue.log('Desktop path: %s' % os.path.join(os.environ['HOMEPATH'], 'Desktop'))
```

```
LogPython: Desktop path: \Users\****\Desktop
```

実際にアクセスしてみましたが問題なさそうで、Linuxなどではまた話が変わってくるとは思いますがとりあえずWindowsならこれで良さそうです。

続いてSQLAlchemyのインストールを進めていきます。

```
$ ./python.exe -m pip install --target . SQLAlchemy
```

```
error: [Errno 2] No such file or directory: '*****\\Plugins\\UnrealEnginePython\\Binaries\\Win64\\python36.zip\\lib2to3\\Grammar.txt'
```

なんだか昔見かけた覚えのあるエラーが・・・
たしか、なぜかzipファイルの中が参照されてしまう、というものだった気がします。
ライブラリ側の問題なので、あまりいい感じの対応ではないですが、python36.zipを別の名前にリネームして、zipをpython36.zipというフォルダ名で展開します。

その後もう一度pipコマンドでSQLAlchemyのインストールを実行してみます。

```
Successfully installed SQLAlchemy-1.3.10
```

今度はエラーにならずに通ったようです。
そのままだとプレビューしてみると以下のエラーが出てきてしまいます。

```
LogPython: Error: [Errno 13] Permission denied: '*****Plugins\\UnrealEnginePython\\Binaries\\Win64\\python36.zip'
```

そのため、先ほど解凍して用意したフォルダを削除し、zipファイルを元のpython36.zipという名前にリネームします（zip解凍したフォルダだと、Windowsでプロパティで読み取り専用を外しても反映されません・・・）。

> 読み取り専用の■を外しても、またプロパティを開くと出てきてしまう。
> [Permission denied：pythonでフォルダ読み書きができない時に確認する事](http://ajec.hatenablog.com/entry/2017/06/26/165424)

これで、とりあえずUE4上でimportとかバージョン確認だけしてみます。

```py
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import sessionmaker

ue.log('sqlalchemy version: %s' % sqlalchemy.__version__)
```

```
LogPython: sqlalchemy version: 1.3.10
```

とりあえずh5pyとは異なり、こちらはUE4上でも問題なくimportできたようです（これで駄目だったらPythonビルトインのSQLiteの機能でやるか・・・という感じですが）。

読み書きなどまでできることを確認するため、次回の記事で最低限の実装を進めていってみます。


# 参考ページまとめ

- [UE4 繰り返し処理を行っている途中で終了する（For Loop With Break、For Each Loop With Break）](http://kagring.blog.fc2.com/blog-entry-412.html)
- [［CEDEC 2019］ゲームの敵AIはAIに作らせる。「『強い』を作るだけが能じゃない！ディープラーニングで3Dアクションゲームの敵AIを作ってみた」聴講レポート](https://www.4gamer.net/games/999/G999905/20190906094/)
- [UE4でPythonを使ってみる](https://qiita.com/simonritchie/items/58fe6d4d3c411c8c062f)
- [UE4 パッケージ化時には処理されないデバッグ処理を実装する（Is Packaged for Distribution）](http://kagring.blog.fc2.com/blog-entry-337.html)
- [【UE4】コメント機能 ①](http://shuntaendo.hatenablog.com/entry/2017/04/28/171534)
- [意外と奥が深い、HDFの世界（Python・h5py入門）](https://qiita.com/simonritchie/items/23db8b4cb5c590924d95)
- [UE4でPythonを使ってみる](https://qiita.com/simonritchie/items/58fe6d4d3c411c8c062f)
- [Installation — h5py 2.10.0 documentation](http://docs.h5py.org/en/stable/build.html)
- [AttributeError: type object 'h5py.h5r.Reference' has no attribute '\_\_reduce_cython\_\_'](https://github.com/pynbody/pynbody/issues/503)
- [PythonでSQLiteを使ってみる（DB4S・SQLAlchemy）](https://qiita.com/simonritchie/items/705148154f810ee308e2)
- [How to get Desktop location?](https://stackoverflow.com/questions/34275782/how-to-get-desktop-location)
- [Permission denied：pythonでフォルダ読み書きができない時に確認する事](http://ajec.hatenablog.com/entry/2017/06/26/165424)