UE4でPythonを使ってみる

UE4でちょっとした機械学習関係の実験環境を作る際に、ブループリント以外にもPythonを使いたくなり色々調べていたのでそのメモです。


# 使うもの

- UE4の4.21.2のバージョンを使っています。
- [UnrealEnginePython](https://github.com/20tab/UnrealEnginePython)というプラグインを使っていきます。
- Pythonは3.6を使っていきます。
- OSはWindows10環境です。（UbuntuでもUE4動くらしいけど試していない・・）

# リポジトリに書かれている特徴の抜粋

- 他のUE4のプラグインを使うのにも使えるらしい
- タスクの自動化やテストスクリプトなどにも便利らしい
- 最新の対応しているUE4バージョンは4.21（2019-04-24時点）
- Anacondaなども加味されているらしい

Anaconda関係が使えるということはNumPyが使えたりとか、もしくは機械学習関係のライブラリ使って制御したりとかさくっと色々ごにょごにょとできる・・のでしょうか？（NPCのAIにディープラーニングのライブラリや学習済みのモデルデータ扱ったりとか）

# インストール

以下のページからインストール用のzipを落としてきます。
[20181128 Binary Releases](https://github.com/20tab/UnrealEnginePython/releases/tag/20181128)

今回は「20181128 Binary Releases」で且つPython3.6を使いたかった点、UE4のバージョンが4.21.2だったので、「UnrealEnginePython_20181128_4_21_python36_embedded_win64.zip」を選択しました。embeddedとついていると、Pythonも含まれているらしいので、Pythonのパスを気にしたりしなくて済むようです。（逆に特定のAnaconda環境のPythonなどを指定する場合は恐らくパス関係で対応が必要そうな印象）

embed版のダウンロードしたzipを解凍してみると、pipやらvenvらしきフォルダが見受けられます・・

![20190424_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/f4bd6040-e30b-8366-78c4-9f2fd55ca761.png)

zipを解凍したときに出てくるUnrealEnginePythonフォルダを、UE4のプロジェクト直下にPluginsというフォルダを作ってそこに設置しました。

![20190424_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/7bf7d8d4-5105-addb-e820-3ae666fb826d.png)

![20190424_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/982f3eff-e4d4-b418-e7d5-04dee3e77825.png)

UE4のプロジェクトを起動してみます。embed版ではない場合に、パス設定などが抜けているとここでプロジェクトがエラーで起動しないようです。

![20190424_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/94e68166-9ac8-cbfe-2e5f-9f82f7aea771.png)

無事起動し、Windowメニューの下の方にPython Editorなどが追加されているようです。

エディタを開くとこんな感じでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/cf228a9b-cbb2-7016-729a-ce839d651520.png)

参考にしていた記事では、このエディタだとちょっと辛いので別のエディタ使ったほうがいいかも、と書かれていたのと、プラグインのリポジトリでもお気に入りのエディターを使ってね、と書かれていたので、VS Codeを使っていきます。

# 少し動かしてみる

プラグインのリポジトリの説明やコードを参考に、Blueprintクラスを追加して、そちらで少しコードを書いていってみます。

追加するクラスの選択メニューでは、PyActorを選択します。

![20190427_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/30bcdfa0-dc64-596d-f626-edf83f30106b.png)

また、<UE4のプロジェクトパス>/Content/Scripts/ 以下のディレクトリに、お試しでpython_test.pyというファイルを設置しました。

python_testモジュールをVS Code側で、ごくシンプルな記述を追加しておきます。

```py
import unreal_engine as ue

ue.log('Hello UnrealEnginePython!')

```

※Hello World的な記述だけです。

次に、追加したPyActorのブループリントクラスをブループリントエディタで開きます。

Detailsタブに、対象のPythonモジュールを指定する個所（Python Module）があるので、今回追加したpython_testモジュールを指定してコンパイルします。

![20190427_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ce48014f-3249-66dd-f1e8-6b16e2b94508.png)

このブループリントクラスを、レベル（マップ）に追加して、ゲームをスタートさせてみます。

Output Logタブに、正常にPythonコードが実行されてメッセージが出力されていることが分かります。

![20190427_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/da20eda6-9454-ebfd-77ff-c3b253283cc8.png)

## クラスを設定する

コードに以下のようにクラスの記述を追加してみます。

```py
class SampleClass:

    def begin_play(self):
        # レベル上でのゲームのプレイが開始されたタイミングで
        # 実行されます。
        ue.log('SampleClass begin play')

    def tick(self, delta_time):
        # location = self.uobject.get_actor_location()
        ue.log('delta_time: %s' % delta_time)

```

ブループリントで、BeginPlayイベントがよくでてきますが、クラスの記述にあるbegin_playメソッドも同様の挙動をするもので、ゲーム開始時にこの名前の関数が実行されるようです。

また、tickという名前のメソッドでは、フレームレートの設定に応じた間隔で実行されるようです。delta_timeの引数は、前回からの経過した時間が渡されます。

UE4のエディターで、対象のブループリントクラスのDetailsタブで今回追加したクラスを追加することで、このクラスが使われるようになります。

![20190427_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/693df040-75e2-a842-8f37-0324206deb20.png)

ゲームをスタートさせてみます。
最初の方に以下のメッセージがOutputLogタブに表示されていることが分かります。

```
...
LogPython: SampleClass begin play
...
```

また、その後にフレームごとにtickメソッドが呼ばれていることが分かります。

```
LogPython: delta_time: 0.016666997224092484
LogPython: delta_time: 0.016666993498802185
LogPython: delta_time: 0.016667723655700684
LogPython: delta_time: 0.016666997224092484
```

60FPS相当の設定による、経過時間の出力がされています。

そのほかにも、衝突判定など、各イベントは決められたメソッド名でクラスに設定できるようです。

```py
def on_actor_begin_overlap(self, me, other_actor):
    pass

def on_actor_end_overlap(self, me, other_actor):
    pass
```

他にも、多くのブループリント側で用意されているイベントなんかは、利用可能な場合には用意されているようです。

## Actorの座標を変える

クラスのuobject属性などを参照することで、座標を取得したり座標を設定したりできるようです。
ブループリントクラス内に四角のコンポーネントを追加して、tickメソッドでアニメーションさせてみます。

![20190427_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/546e244f-8b60-5a48-e3c4-3c42c85d413c.png)

空中に浮かんでいる四角がブループリントクラス内に設置したコンポーネントです。Python側のtickメソッドを調整していきます。

```py
import unreal_engine as ue


class SampleClass:

    def tick(self, delta_time):
        location_vector = self.uobject.get_actor_location()
        location_vector.z += 100 * delta_time
        self.uobject.set_actor_location(location_vector)
```

![20190427_7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/f631fe5f-d8c7-2780-b47b-b6a2d662aefd.gif)

Python側からActorにアクセスできて、座標が変わっていっていることが分かります。

uobject内の各メソッドの入力補完とかはVS Code側からは効かない（もしかしたら効くようにできるかもですが）ので、少し覚えるまでは大変かもしれませんが、ちょこっとPythonで操作を書いたりする分にはお手軽で良さそうです。

## ブループリントと色々連携してみる

たとえば画面内のテキスト表示やら各オブジェクトの値やら、ブループリントでよく操作するものをPython側で取得するケースを考えてみます。

今回はブループリント側の関数をPythonから呼び出すのを試してみます。
ブループリントクラスでgetSampleMessageという関数を追加しました。

![20190427_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/239b5ef1-e2b3-a4f1-6620-56e376b0142c.png)

以下のように、関数呼び出した際に固定で「This message is blueprint's variable!」という文字列が返却されるようにノードを設定しました。

![20190427_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/059f7a17-a03d-97e3-f2ef-ef17976dce6f.png)

関数自体は外部からアクセスできるように、publicにしておきます。

![20190427_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/45732620-e3fe-6f65-38cb-a0ad8bf1e8e7.png)

Pythonコード側では、uobjectにこの関数（getSampleMessage）が設定されてアクセスできようになります。

```py
import unreal_engine as ue


class SampleClass:

    def begin_play(self):
        sample_message = self.uobject.getSampleMessage()
        ue.log('sample_message: %s' % sample_message)

```

ゲームをスタートしてみます。Output Logタブに、ブループリント上の関数をPythonから呼び出して、ログ出力がされていることが確認できます。

![20190427_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/86e4db51-7bcf-509d-8d6f-79f3ea97824e.png)

## pipを使ってみる

今のところはブループリントだとやりづらいところなどをPythonで少し補助するか・・ぐらいの想定ではいるのですが、もしかしたら今後Pythonの数値計算系のライブラリだったり、もしくは機械学習関係のライブラリを使ったりしたくなるかもしれません。
そのため、pip関係を少し触っておきます。

※今回はembeddedのPythonプラグインのバージョンを使っていたのですが、embeddedではないバージョンの場合自分でAnacondaなどのパスを指定する必要があるのでそういったケースはAnacondaで直接設定できると思われます。

今回はNumPyをインストールして、UE4環境でNumPyを使ってみましょう。

コマンドラインで、プラグインが設置してあるパスに移動します。

```
$ cd <プロジェクトのパス>\Plugins\UnrealEnginePython\Binaries\Win64
```

※Windows以外の環境であればプラグイン以下のパスが変わってくると思います。（Win64の個所など）

その中にpython.exeやpip関係のフォルダが設置してあると思うので、以下のようにすることでpipを実行することができます。

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

```
Successfully installed numpy-1.16.3
```

※--target . などの記述がないと、このフォルダ直下にNumPyなどがインストールされず、UE4からも認識できないようです。あまりtargetを普段使わないケースが多いと思うので注意します。
正常にインストールできていない場合には、UE4でゲームスタート時にOutput Logのタブに以下のようにPythonで見慣れたスタックトレースのエラーが出てきます。

![20190427_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/6021e9fb-904c-9b89-dbc1-4d954748b432.png)

python.exeをダブルクリックするとインタラクティブシェルが起動するので、一応NumPyがimportできることを確認します。

![20190427_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/98d8c8be-71bf-27a5-7efe-b25b55b06cbb.png)

大丈夫そうです。実際にUE4のOutput LogにNumPyの値が表示されることを確認してみます。

以下のようなコードにしました。

```py
import unreal_engine as ue
import numpy as np


class SampleClass:

    def begin_play(self):
        sample_arr = np.array([100, 200, 300])
        ue.log('NumPy arr: %s' % sample_arr)

```

ゲームをスタートしてOutput Logを確認してみます。

![20190427_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/2382ffb7-4a70-7bf7-a269-b22a38beaa5c.png)

問題なくNumPyが動いたようです！

# 雑感

- インストールなどは特に小難しいことはなく、ブループリントとの連携もシンプルだった。
- uobject関係で入力補完が効かないものの、まあちょっと遊んでいく分にも問題はない（そもそも、ブループリント側で頻繁に関数の追加やら変数名やら更新したりするので、補完の利かせようがない・・という印象）
- pip使えるので色々できそう。慣れたPythonライブラリで機械学習関係を動かしたり、そのままブループリント連携でPythonで色々やったりと、学習コスト少なく遊べそう。ちょっと遊んだり、実験環境として使ったりする分には十分そう。

# 参考文献

- [UnrealEnginePython](https://github.com/20tab/UnrealEnginePython)
- [UnrealEnginePython プラグインを使ってみた (導入編)](https://qiita.com/negimochi/items/4eac2a6f6725d03f0cf1)
- [Plotting Graphs with Matplotlib and Unreal Engine Python](https://github.com/20tab/UnrealEnginePython/blob/master/tutorials/PlottingGraphsWithMatplotlibAndUnrealEnginePython.md)
    - ※pip周りの記述を参考にさせていただきました。