[UE4] PythonScriptPluginでアセットを整理しよう

大量のアセットを機械的に処理したい！ということで、Pythonに手を出すことにしました

# この記事は
- [UnrealEnginePython](https://github.com/20tab/UnrealEnginePython)ではなく、[公式でサポートされたPythonPlugin](https://docs.unrealengine.com/ja/Engine/Editor/ScriptingAndAutomation/Python/index.html)を扱います
- UE4.22で確認します
- python触るのは今回がはじめて
- ~~実際に作っていくとあれこれ欲しくなるので、Plugin拡張&ビルドしてライブラリを足したりしてます~~
- 思ったより公開されてました、なので今回は拡張なしで出来る範囲で紹介します
- ただ必要な関数を探すのが大変なので、余裕がない場合は自作するのもいいかも

# 開発環境の構築
[Python を使用したエディタのスクリプティング](https://docs.unrealengine.com/ja/Engine/Editor/ScriptingAndAutomation/Python/index.html)
まずは公式ドキュメントを読むことをお勧めします
なかなか大事なことがさらりと短く書いてあるので、見落としがないように（自戒）

「[Python を使用するようプロジェクトを設定](https://api.unrealengine.com/JPN/Engine/Editor/ScriptingAndAutomation/Python/index.html#python%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%82%88%E3%81%86%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E8%A8%AD%E5%AE%9A)」の通りに、PythonScriptPluginを有効化
UE4のコンソールでPythonが実行できるようになりました
![fa3950a4332cbfd20bd2739e84677de6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/97f0fb85-b2f5-4b68-ed72-483ed2607304.png)

## Pythonインストール
PythonScriptPluginに合わせて2.7.14をインストールします
![562d4bd19c7ab7a44e751267f85b78bc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/e562ec08-1b0c-ed05-25db-07c7fe2f83ad.png)
UE4エディタでの実行であれば内包されているものを使えばよいのですが、後述するソースエディタでどちらにせよ必要になります

[Python Release Python 2.7.14 | Python.org](https://www.python.org/downloads/release/python-2714/)
Python公式からダウンロードしてインストール
ちょっと調べると仮想環境作ったほうがいいとか書かれてますが、UE4でしか使わないのでこれでいいんですｚｚ
エンジンのビルドができるのであれば、環境変数「UE_PYTHON_DIR」を書き換えることで別のバージョンを指定することも可能！とのことですが、そこまで不自由を感じなかったので保留

## ソースコードエディタ設定
いつも愛用しているVSCodeを使います
Linterやオートフォーマットが結構良さげでした

### Python拡張機能
![python1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/ec8cf2d8-f63d-7d8e-f0ec-8db1083bb549.png)
拡張機能から検索してインストールします
![a758d508d379065b6d9a6af140e0dcec.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/7af3ae72-006a-baec-4eef-6f02308ebdcc.png)
インストール後、Pythonがないと怒られる図

Pythonをインストール後、右下に出るサジェスト通りに進めてLinter,Formatterをインストールします

### Pythonのエディタ設定
[はじめに &mdash; pep8-ja 1.0 ドキュメント](https://pep8-ja.readthedocs.io/ja/latest/)
[Pythonで学ぶ 基礎からのプログラミング入門(36) Pythonのコーディング規約「PEP8」](https://news.mynavi.jp/article/python-36/)
コード規約通りに書かないとダメ（そもそも動かない）な点が多いので、エディタである程度固めておいた方がよいです

VSCodeは言語毎の設定ができるようになっています
1. Ctrl+Shift+Pを押す
2. [Preferences: Configure Language Specific Settings...]を検索して実行
3. [言語の選択]と出るので、Pythonを検索して実行
4. settings.jsonが開くので、以下を設定

```json:settings.json
{
	"[python]": {
		"editor.formatOnSave": true,
		"editor.formatOnPaste": true,
		"editor.insertSpaces": true,
		"editor.tabSize": 4,
	}
}
```

# Pythonでプログラミング
の前に、[APIリファレンス](https://docs.unrealengine.com/en-US/PythonAPI/index.html)にざっと目を通しておきます

EditorScriptingUtilitiesのModule内にあるものが、PythonのHelper的なクラスで実装してあると思うので、こちらもチェック

- [unreal.EditorAssetLibrary](https://api.unrealengine.com/INT/PythonAPI/class/EditorAssetLibrary.html) -> Assetへのアクセス
- [unreal.EditorFilterLibrary](https://api.unrealengine.com/INT/PythonAPI/class/EditorFilterLibrary.html) -> 配列のフィルタリング
- [unreal.EditorLevelLibrary](https://api.unrealengine.com/INT/PythonAPI/class/EditorLevelLibrary.html) -> レベルを開いた状態での操作ヘルパ
- [unreal.EditorSkeletalMeshLibrary](https://api.unrealengine.com/INT/PythonAPI/class/EditorSkeletalMeshLibrary.html) -> SkeletalMeshのプロパティ操作
- [unreal.EditorStaticMeshLibrary](https://api.unrealengine.com/INT/PythonAPI/class/EditorStaticMeshLibrary.html) -> StaticMeshのプロパティ操作

[公式サンプル](https://epicgames.ent.box.com/v/Python-Examples/folder/46970070593)は申し訳程度にしか置いていないので、[こちら](https://github.com/mamoniem/UnrealEditorPythonScripts)を参考にしました

## サンプル
今回書いたソースコードは[こちら](https://github.com/koorinonaka/UE4PythonScriptSample)
Pluginsに入れて使います

### 未参照（使用されてない）のアセットを一覧で出力する
[Asset/ReportUnusedAssets]
(https://github.com/koorinonaka/UE4PythonScriptSample/blob/master/Content/Python/asset/report_unused_assets.py)
（画像はさらにクラス名でフィルタ）
![d200a0b540ac8e46f68f09b97637043c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/ee8f8ffa-5fcb-51d7-7073-96229b4a95a9.png)
[サンプル](https://github.com/mamoniem/UnrealEditorPythonScripts/blob/master/Assets/ReportUnusedAssets.py)を参考に実装したものの、[unreal.EditorAssetLibrary](https://docs.unrealengine.com/en-US/PythonAPI/class/EditorAssetLibrary.html) が実はMapの参照に対応してないじゃん・・・
と、諦めかけたその時、ありました！
[unreal.AssetRegistryHelpers](https://docs.unrealengine.com/en-US/PythonAPI/class/AssetRegistryHelpers.html) と [unreal.AssetRegistry](https://docs.unrealengine.com/en-US/PythonAPI/class/AssetRegistry.html) 、こっちのほうが遥かに充実してない？

### ファイル名の重複を検出
[Asset/ReportDuplicatedAssetNames](https://github.com/koorinonaka/UE4PythonScriptSample/blob/master/Content/Python/asset/report_duplicated_asset_names.py)
![45f8b9ecfb42fdd65eb3e9f6a3d697cb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/9d013874-289a-0609-c6b0-c6f27d460664.png)
コピーしたアセットの消し忘れのために、unusedと組み合わせるとなお良し
![ba5c3a87ce114f3a022305a7278c9dac.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/24ea59e3-9b19-1db0-c59d-dbe65d3f1496.png)
アセット検索で同名が表示されたりするので、ユニークな命名にするようにしましょう

### ファイル名のUnicodeを検出
[Asset/ReportUnicodedAssetNames](https://github.com/koorinonaka/UE4PythonScriptSample/blob/master/Content/Python/asset/report_unicoded_asset_names.py)
![907214e89bbf097cf4dd25fa092df735.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/5739c022-9c3d-5ac0-07ee-d188541cd9f7.png)
気を付けていても＿とか全角アルファベットつけちゃうことあるよね
`if isinstance(package_name, unicode):`
unicodeとの比較で判定します

### 中身が空のディレクトリを削除
[Asset/DeleteUnusedLocalDirectories](https://github.com/koorinonaka/UE4PythonScriptSample/blob/master/Content/Python/asset/delete_unused_local_directories.py)
Perforceでは他人が消したディレクトリを消してくれないので（実は設定で出来るらしいと、聞いたけど）
init_unreal.pyとかに設定するとよいかもしれない
コンテンツブラウザ上だと中身が空でも、見えないだけで実際はファイルが存在している場合（Pythonとか）はディレクトリ削除に失敗するので、FileManagerからファイルを取得する関数を作成するほうがよい
FPackageName::FindPackagesInDirectoryとかを使うのがよさげ？

### テクスチャのプロパティを変更
[Texture/ValidateProperties](https://github.com/koorinonaka/UE4PythonScriptSample/blob/master/Content/Python/texture/validate_properties.py)
![fa9b239794d7f06b924acce38c33d63f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/517377d6-b25b-0f41-44dc-85df1f776796.png)
サンプルではUIテクスチャの設定として、MipGenSettingsとTextureGroupを修正している

### テクスチャ圧縮可否のサイズ判定
[Texture/ReportAssets](https://github.com/koorinonaka/UE4PythonScriptSample/blob/master/Content/Python/texture/report_assets.py)
![ce8cdbeade965553d24b93173c2d02d1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41105/003bc975-8e90-b664-d4d5-2679bad00319.png)
[圧縮テクスチャ メモリ要件](https://api.unrealengine.com/JPN/Engine/Content/Types/Textures/SupportAndSettings/index.html#%E5%9C%A7%E7%B8%AE%E3%83%86%E3%82%AF%E3%82%B9%E3%83%81%E3%83%A3%E3%83%A1%E3%83%A2%E3%83%AA%E8%A6%81%E4%BB%B6)
>DXT は、パレットカラーおよび補間カラーを持つ 4x4 ブロックにピクセルをパッキングすることに基づいた不可逆圧縮を使用します

テクスチャを圧縮するためには4の累乗である必要があるので、不適切なサイズのアセットを検出しています

# コマンドラインからの起動
深夜に自動でスクリプトを実行したいじゃないですか？
[コマンドライン](https://api.unrealengine.com/JPN/Engine/Editor/ScriptingAndAutomation/Python/index.html#%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3)の項に沿って起動します

- エディタを通常通り起動する場合（たぶんMapとかを直接開いて操作するとかはこっち）
ex. `"[EnginePath]\Engine\Binaries\Win64\UE4Editor-Cmd.exe" "[ProjectPath]\[ProjectName].uproject" -ExecutePythonScript="[ProjectPath]\Plugins\UE4PythonScriptSample\Content\Python\[PythonFile].py" -LogCategoryFiles="[ProjectPath]\Saved\Logs\[LogName].log=LogPython"`

- 最小限の構成でエディタを起動（アセットのチェックだけとかはこっち）
ex. `"[EnginePath]\Engine\Binaries\Win64\UE4Editor-Cmd.exe" "[ProjectPath]\[ProjectName].uproject" -run=pythonscript -script="[ProjectPath]\Plugins\UE4PythonScriptSample\Content\Python\[PythonFile].py" -LogCategoryFiles="[ProjectPath]\Saved\Logs\[LogName].log=LogPython"`

	- パスはフルパスで書く必要がある（UE4Editor-Cmd.exeからの相対でパス解決される）
	- pythonscriptの指定は、*"hoge.py arg1 arg2"*のようにコマンド引数を与えることもできる
	- Cmdの標準出力にはunreal.logは出力されない（log_warning,log_errorは出力される）
	- LogCategoryFilesで、カテゴリ毎の出力ファイルを指定できる（こっちはlogも出力される）

# 使ってみての雑感
- python2は言語的に終焉に近いようだけど、基本的なことをやる分には問題ないのかなと思う
- 今が触り始めなのと、パッケージ関係を入れ始めたら困り始めるかも
- ただし\_\_init\_\_.py（自作moduleをimportするとき必要）は許しません、あとreloadをいちいち書かないとimport先がリロードされないのは、開発する上で結構めんどくさい
- ググったときのドキュメントは大抵3.x系の内容で、互換性がないものもあるし、python2とかで検索してもヒットしないのはそこそこ困るｗ
- Pythonで出来ること≒Blueprintで出来ることなので、どちらを使うかは好みになるかも
- 正直、開発初期から導入しておきたかったくらい便利で楽しい(´・ω・`)

## Python関係ないけど
- エディタ周りでいろいろ触っていくには、エンジン内部処理を追うためにソースを見ていく必要がある
- [公式のAPIリファレンス](https://docs.unrealengine.com/en-US/PythonAPI/index.html)は、[Native Types]の項を除いてBP公開クラスを羅列してるだけなので、探すのが大変ｗ
- まずは~Library、~Helperとかでリファレンスをさらうとよいかも

## TODO的な、あったらいいな的な？
- ライトビルド（ライトシナリオの切り替えとか）
- アセット名の修正（Prefix,Suffix,ルール付け）
- レベル内のアウトライン整理
- submit履歴を参照して、最後の変更者に通知