---
title: テクニカルアーティスト向け Unreal Python スタートガイド【UNREAL FEST 2024 TOKYO】
tags:  #ue5 #unreal fest #unreal fest 2024 tokyo  
author: [エピック ゲームズ ジャパン](https://www.docswell.com/user/EpicGamesJapan)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/V7N85RN6E8.jpg?width=480
description: 本講演へのアンケートにご協力をお願いします： https://docs.google.com/forms/d/e/1FAIpQLSeeq3O53ausrSoHXHluKskiabOlucYgXBajAhVQ2IbL__Km7Q/viewform  Youtube URL：   講演内容：  普段DCCツールをサポートしているテクニカルアーティストの方向けにUEでPythonを活用し、ツール作成からリリースまでの一連の流れをご紹介します。 ツール作成には、アセットをUEへシームレスにインポートするアセットエクスポーターを例にUEにおける基本的な全アセットのインポート設定や手法を説明し、リリースに必要なGUIやメニュー追加、テスト方法などについても解説します。  講演者： 山城 拓巳(デザイナー部テクニカルアーティストチーム シニア(株式会社Cygames))  Cygamesについてはこちら： https://www.cygames.co.jp/  UNREAL FEST 2024 TOKYO 公式サイト： https://unrealengine.jp/unrealfest/2024/
published: November 14, 24
canonical: https://www.docswell.com/s/EpicGamesJapan/Z82MN7-UE_UF24T_CYGAMES
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/V7N85RN6E8.jpg)

テクニカルアーティスト向け
Unreal Python スタート ガイド
株式会社 Cygames デザイナー部 テクニカルアーティストチーム シニア
山城 拓巳


# Page. 2

![Page Image](https://bcdn.docswell.com/page/YJ9KYMRRJ3.jpg)

自己紹介
株式会社 Cygames
テクニカルアーティストチーム/シニア
山城 拓巳
● アーティスト向けのDCCツールやゲームエンジ
ン向けのツール開発を担当。
CEDEDC 2022
ツール保守コスト大幅削減！テクニカルアーティストによ
るツールログサービスの開発と運用事例
2/96


# Page. 3

![Page Image](https://bcdn.docswell.com/page/GJ8R6LWG7D.jpg)

このセッションの環境
● Windows 11
● Unreal Engine 5.4
● Pythonバージョンは 3.11
※ 内容的にはUnreal Engine5系であれば内容に細かいバージョン縛りはなし
● DCC
● Maya 2023
● Substance 3DPainter 10.1
3/96


# Page. 4

![Page Image](https://bcdn.docswell.com/page/LJLK9LNX7R.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
4/96


# Page. 5

![Page Image](https://bcdn.docswell.com/page/47M2ZMX37W.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
5/96


# Page. 6

![Page Image](https://bcdn.docswell.com/page/P7RM6VNRJ9.jpg)

Unreal Pythonの導入方法
● プラグインを有効化するだけ
● 最近はデフォルトでOn
6/96


# Page. 7

![Page Image](https://bcdn.docswell.com/page/PJXVMZNYEX.jpg)

Unreal Python 導入後追加されるもの
1. 出力ログにPythonのコード実行とREPL(Read-Eval-Print Loop)が追加
REPL(Read-Eval-Print Loop)
7/96


# Page. 8

![Page Image](https://bcdn.docswell.com/page/3JKQG8N4JD.jpg)

Unreal Python 導入後追加されるもの
2. コマンドレット実行にPython実行用のコマンドレットが追加
(コマンドレット・・Unreal Engineをコマンドライン実行するもの)
エディター起動あり
UnrealEditor-Cmd.exe “uprojectパス” –ExecutePythonScript=“Pythonパス”
エディター起動なし
UnrealEditor-Cmd.exe &quot;uprojectパス&quot; -run=pythonscript -script=&quot;Pythonパス”
8/96


# Page. 9

![Page Image](https://bcdn.docswell.com/page/LE3PN2VDJ5.jpg)

Unreal Pythonの Hello World
● UE内で実行可能な「unreal」モジュールが存在
import unreal # Mayaでいうcmdsみたいなもの
unreal.log(&quot;Hello World!&quot;)
9/96


# Page. 10

![Page Image](https://bcdn.docswell.com/page/8EDLWZ82JG.jpg)

Unreal Pythonの設定(型情報)
● エディター環境設定のDeveloper モード
この機能がOnになってるとそのプロジェクト内で利用可能な関数の型情報を生成してくれる
プロジェクトフォルダ/Intermediate/PythonStub/unreal.py
10/96


# Page. 11

![Page Image](https://bcdn.docswell.com/page/V7P29D8L78.jpg)

Unreal Pythonの仕組み
Blueprint向けに公開された関数がPythonにも「ほぼ」公開される
共通
⇐ 共通
前述したデベロッパーモードをOnにして生成された型情報の中でも関数を確認できる。
この仕組みのおかげで、C++経由で足りない機能なども追加が可能。
11/96


# Page. 12

![Page Image](https://bcdn.docswell.com/page/2JVDM1N6JQ.jpg)

Unreal Pythonの設定(予約パス)
● プロジェクトフォルダ/Content/Python
● Unreal Engineフォルダ/Content/Python
● 有効にした各プラグインフォルダ/Content/Python
● Documents/UnrealEngine/Pythonフォルダ
● 例) C:/Users/Username/Documents/UnrealEngine/Python
12/96


# Page. 13

![Page Image](https://bcdn.docswell.com/page/5EGYG2K2EL.jpg)

Unreal Pythonの設定(追加パス)
● プロジェクト設定のPython Plugin設定画面から追加可能。
任意のフォルダにpip install先を作ってそのパスを通せば、pipライブラリも読み込める。
上記はContentと同階層にsite-packagesフォルダを作った例。
13/96


# Page. 14

![Page Image](https://bcdn.docswell.com/page/4JQMXPN97P.jpg)

Unreal Pythonの設定(スタートアップ)
● init_unreal.pyという名前で予約パス、追加パスに配置すると起動時実行可能。
14/96


# Page. 15

![Page Image](https://bcdn.docswell.com/page/K74V6Y3RJ1.jpg)

Unreal Pythonの設定(スタートアップ)
● 任意のスタートアップファイル指定も可能。
15/96


# Page. 16

![Page Image](https://bcdn.docswell.com/page/LJ13V61V7G.jpg)

Unreal Pythonの設定(ブラウザ統合)
● この講演では便宜上、「コンテンツブラウザ統合を有効化」をOnにする。
この設定を行うとPythonファイルをコンテンツブラウザで表示して、実行できる。
16/96


# Page. 17

![Page Image](https://bcdn.docswell.com/page/GJWPLW8372.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
17/96


# Page. 18

![Page Image](https://bcdn.docswell.com/page/4EZKM58GE3.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
2.1. スタティックメッシュ
2.2. スケルタルメッシュ
2.3. アニメーションシーケンス
2.4. テクスチャ
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
18/96


# Page. 19

![Page Image](https://bcdn.docswell.com/page/Y76QN9PQ7V.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
2.1. スタティックメッシュ ← 一番簡単なものから
2.2. スケルタルメッシュ
2.3. アニメーションシーケンス
2.4. テクスチャ
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
19/96


# Page. 20

![Page Image](https://bcdn.docswell.com/page/G75R5NK2E4.jpg)

スタティックメッシュの手動手順整理
● DCCからFBXを書き出す
===== UE =====
● FBX をドラッグする
● インポート設定を編集する
● 「インポート」を押す
20/96


# Page. 21

![Page Image](https://bcdn.docswell.com/page/9J2DN5W1JR.jpg)

スタティックメッシュの手動手順を整理
● DCCからFBXを書き出す
● FBX をドラッグする
● インポート設定を編集する
3つの工程をまとめて自動化
● 「インポート」を押す
※ DCCからFbxを書き出す部分はDCC側なので、割愛
21/96


# Page. 22

![Page Image](https://bcdn.docswell.com/page/DEY2PKL4EM.jpg)

ここまでのデモ


# Page. 23

![Page Image](https://bcdn.docswell.com/page/VJN85R4678.jpg)

3工程をまとめて自動化する
import unreal
task = unreal.AssetImportTask()
source_file_path = &quot;インポートしたいFBX パス&quot;
ue_file_dir = &quot;インポートしたいUEファイルルート&quot;
# ダイアログ非表示
task.set_editor_property(&quot;automated&quot;, True)
task.set_editor_property(&quot;filename&quot;, source_file_path)
task.set_editor_property(&quot;destination_path&quot;, ue_file_dir)
# 上書き許可
task.set_editor_property(&quot;replace_existing&quot;, True)
# 完了後、保存する
task.set_editor_property(&quot;save&quot;, True)
⇐ FBXをドラッグする
# FBX設定 次ページで説明
import_ui = unreal.FbxImportUI()
task.options = import_ui
⇐インポート設定
# 実行する
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
⇐「インポート」を押す
23/96


# Page. 24

![Page Image](https://bcdn.docswell.com/page/YE9KYMQRE3.jpg)

FBX Import UIを設定する
import_ui = unreal.FbxImportUI()
Import_ui.reset_to_default()
import_ui.import_mesh = True
# UEの中にどのタイプでインポートするか
Import_ui.mesh_type_to_import = unreal.FBXImportType.FBXIT_STATIC_MESH
# FBXのオリジナルタイプ。スタティックメッシュタイプを強制的にスケルタルメッシュにできたりできる
Import_ui.original_import_type = unreal.FBXImportType.FBXIT_STATIC_MESH # FBXIT_STATIC_MESH
# アニメーションのインポート有無
Import_ui.import_animations = False
# スケルトンのインポート有無
Import_ui.import_as_skeletal = False
# マテリアルのインポート有無
import_ui.import_materials = False
# テクスチャのインポート有無
import_ui.import_textures = False
# メッシュのインポート有無
import_ui.import_mesh = True
24/96


# Page. 25

![Page Image](https://bcdn.docswell.com/page/GE8R6LGGJD.jpg)

より詳細な設定については・・
● unreal.pyのFbxImportUIを参照
スタティックメッシュの詳細設定はFbxStaticMeshImportDataを指定したり、
スケルタルメッシュではFbxSkeletalMeshImportDataを指定したりする。
各アセットごとの詳細設定ごとに設定クラスが分かれて指定する形式
25/96


# Page. 26

![Page Image](https://bcdn.docswell.com/page/LELK9LGXJR.jpg)

現時点での気になる点
1. Asset Import Taskで取得する置き場所が決まっている
import unreal
task = unreal.AssetImportTask()
source_file_path = “ここがDCCの実行と分離されてるので、取り扱いしづらい&quot;
ue_file_dir = &quot;インポートしたいUEファイルルート&quot;
2. 実行が2段階になっている
1. DCCでFbx書き出す
2. UEでAssetImportTaskを呼び出し、インポートする
26/96


# Page. 27

![Page Image](https://bcdn.docswell.com/page/4JM2ZMQ3JW.jpg)

DCCからリモートで実行可能


# Page. 28

![Page Image](https://bcdn.docswell.com/page/PJRM6V8RE9.jpg)

Remote 設定
● Python Pluginにはリモート実行可能な設定が存在
28/96


# Page. 29

![Page Image](https://bcdn.docswell.com/page/PEXVMZ8Y7X.jpg)

Remote 接続実装
● Python Pluginのフォルダ内にRemote実行のサンプルファイルが存在
PythonScriptPlugin¥Content¥Python¥remote_execution.py
29/96


# Page. 30

![Page Image](https://bcdn.docswell.com/page/3EKQG8K4ED.jpg)

remote_execution.pyを利用する
● Unreal Engine外から以下を送信する
import time
from remote_execution import RemoteExecution
remote_execute = RemoteExecution()
remote_execute.start()
⇐ サンプルファイルのクラス
リモート実行セッションを開始する
# 即確認すると接続される前に完了してしまうため、1秒待機する。
time.sleep(1)
# UEが存在したら、一番最初に見つけたUEで実行する
if remote_execute.remote_nodes:
remote_execute.open_command_connection(remote_execute.remote_nodes[0])
remote_execute.run_command(“sample_call.py unreal_fest 2024”)
remote_execute.stop()
sample_call.pyをunreal_fest, 2024という引数で実行する
30/96


# Page. 31

![Page Image](https://bcdn.docswell.com/page/L73PN2ZDE5.jpg)

実行先のsample_call.pyと実行結果
Unreal側で実行するsample_call.py
import sys
import unreal
unreal.log(f&quot;Hello, {sys.argv}&quot;)
リモートで実行された結果
31/96


# Page. 32

![Page Image](https://bcdn.docswell.com/page/87DLWZR2EG.jpg)

これらを踏まえ、
スタティックメッシュ書き出しへ


# Page. 33

![Page Image](https://bcdn.docswell.com/page/VJP29DWLJ8.jpg)

DCC側から呼び出すコード
import time
from remote_execution import RemoteExecution
使いやすい様にクラス化する
class UERemoteExecution:
@classmethod
def execute_file(cls, file_path: str, *args) -&gt; None | dict:
remote_execute = RemoteExecution()
remote_execute.start()
time.sleep(1)
if remote_execute.remote_nodes:
remote_execute.open_command_connection(remote_execute.remote_nodes[0])
remote_execute.run_command(f&quot;{file_path} {&quot; &quot;.join(args)}&quot;)
remote_execute.stop()
UERemoteExecution.execute_file(“import_static_mesh.py”, “FBXパス“, “UEディレクトリ”)
インポート実装コードを指定
書きだしたFbxパスとUEディ
レクトリを引数にする
33/96


# Page. 34

![Page Image](https://bcdn.docswell.com/page/2EVDM186EQ.jpg)

UEで実行するimport_static_mesh.py
import unreal
import sys
fbx_path = sys.argv[1]
ue_dir = sys.argv[2]
⇐ 変更点。定数から引数で来る想定に変更。
task = unreal.AssetImportTask()
task.set_editor_property(&#039;automated&#039;, True)
task.set_editor_property(&#039;filename&#039;, f&#039;{fbx_path}&#039;)
task.set_editor_property(&#039;destination_path&#039;, f&#039;{ue_dir}&#039;)
task.set_editor_property(&#039;save&#039;, True)
import_ui = unreal.FbxImportUI()
# スタティックメッシュに施したい設定を追加する
task.option = import_ui
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
34/96


# Page. 35

![Page Image](https://bcdn.docswell.com/page/57GYG2527L.jpg)

改めてデモ


# Page. 36

![Page Image](https://bcdn.docswell.com/page/4EQMXPZ9JP.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
2.1. スタティックメッシュ
2.2. スケルタルメッシュ
2.3. アニメーションシーケンス
2.4. テクスチャ
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
36/96


# Page. 37

![Page Image](https://bcdn.docswell.com/page/KJ4V6YDRE1.jpg)

スタティックメッシュとの相違点
● スケルトンという概念がある
新規で作成したり、既存のスケルトンで共有したり
逆にエクスポーターで気を付ける点はほぼこれだけ
37/96


# Page. 38

![Page Image](https://bcdn.docswell.com/page/LE13V6ZVJG.jpg)

デモ


# Page. 39

![Page Image](https://bcdn.docswell.com/page/GEWPLW93J2.jpg)

DCC側から呼び出すコード
● 先ほど紹介した UERemoteExecution クラスをそのまま利用する
● 実行ファイルをImport_skeletal_mesh.pyに変更
UERemoteExecution.execute_file(“import_skeletal_mesh.py”, ⇐ スケルタルメッシュ
“FBXパス”,
インポートコードに変更
“UEディレクトリ”,
“スケルトンパス”)
スケルトンパスを追加
39/96


# Page. 40

![Page Image](https://bcdn.docswell.com/page/47ZKM59G73.jpg)

UEで実行する「import_skeletal_mesh.py」
import unreal
import sys
fbx_path = sys.argv[1]
ue_dir = sys.argv[2]
skeleton_path = sys.argv[3]
task = unreal.AssetImportTask()
# タスクの設定は前述してるので省略
import_ui = unreal.FbxImportUI()
# FbxImport設定は前述してるので省略
⇐ 追加点。スケルトンパスを指定する。
例) /Game/Meshes/sample_skeletal_mesh_Skeleton
スケルトン設定
loaded_skeleton = unreal.load_asset(skeleton_path) # Game/**の形式
if loaded_skeleton: # 存在してるならそれを利用する
import_ui.import_as_skeletal = False
import_ui.skeleton = loaded_skeleton
else: # ない場合は新規作成
import_ui.import_as_skeletal = True
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
40/96


# Page. 41

![Page Image](https://bcdn.docswell.com/page/YJ6QN9KQJV.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
2.1. スタティックメッシュ
2.2. スケルタルメッシュ
2.3. アニメーションシーケンス
2.4. テクスチャ
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
41/96


# Page. 42

![Page Image](https://bcdn.docswell.com/page/GJ5R5NP274.jpg)

スケルタルメッシュとの相違点
● スケルトン指定が必須
アニメーションシーケンスには骨がないとエラーになる。
42/96


# Page. 43

![Page Image](https://bcdn.docswell.com/page/LE3PN231J5.jpg)

デモ
● TODO: モーションビルダーからUEへのデモ


# Page. 44

![Page Image](https://bcdn.docswell.com/page/8EDLWZ4KJG.jpg)

UERemoteExecution の変更内容
● 骨がない場合は、明示的に失敗させたいので、リザルトを返す
class UERemoteExecution:
@classmethod
def execute_file(cls, file_path: Path, *args) -&gt; dict:
remote_execute = RemoteExecution()
remote_execute.start()
time.sleep(1)
戻り値を追加
res = None # 追加
if remote_execute.remote_nodes:
remote_execute.open_command_connection(remote_execute.remote_nodes[0])
res = remote_execute.run_command(f”{file_path.as_posix()} {“ “.join(args)}”)
remote_execute.stop()
return res # 追加
44/96


# Page. 45

![Page Image](https://bcdn.docswell.com/page/V7P29DM378.jpg)

DCC側からの実行コード
● 骨が必須という関係で、見つからない場合は、DCC側で通知したい
● execute_fileに結果情報を返す機能を追記する
from remote import UERemoteExecution
res = UERemoteExecution.execute_file(
“import_animation.py”, “FBXパス”, “UEディレクトリ”, “スケルトンパス”
)
if res[&quot;success&quot;] != True:
pass
⇐ ここでDCC側でダイアログを出し、気づかせる
45/96


# Page. 46

![Page Image](https://bcdn.docswell.com/page/2JVDM19NJQ.jpg)

UEで実行するimport_animation.py
import unreal
import sys
fbx_path = sys.argv[1]
ue_dir = sys.argv[2]
skeleton_path = sys.argv[3]
# Task設定は前述してるので省略
task = unreal.AssetImportTask()
# FbxImport設定は前述してるので省略
import_ui = unreal.FbxImportUI()
loaded_skeleton = unreal.load_asset(skeleton_path) # Game/**の形式
if loaded_skeleton:
import_ui.import_as_skeletal = False
import_ui.skeleton = loaded_skeleton
else:
raise ValueError(&quot;Skeleton not found.&quot;) ⇐ 変更点。ない場合はエラーにする
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
46/96


# Page. 47

![Page Image](https://bcdn.docswell.com/page/5EGYG2Z5EL.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
2.1. スタティックメッシュ
2.2. スケルタルメッシュ
2.3. アニメーションシーケンス
2.4. テクスチャ
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
47/96


# Page. 48

![Page Image](https://bcdn.docswell.com/page/4JQMXPLL7P.jpg)

今までのアセットとの違い
● インポート時にダイアログが表示されない
48/96


# Page. 49

![Page Image](https://bcdn.docswell.com/page/K74V6YD5J1.jpg)

今までのアセットとの違い
● テクスチャの種類によって設定が変わる
Albedo, Normalで圧縮法設定が変わったりする
49/96


# Page. 50

![Page Image](https://bcdn.docswell.com/page/LJ13V6Z27G.jpg)

相違点に対する対応方針
● インポート時にダイアログが表示されない
● テクスチャをインポート後に後から設定を変更する
● テクスチャの種類によって設定が変わる
● テクスチャによって設定がサフィックスで設定を分岐する
● アルベドなら_D、ノーマルなら_Nなど
50/96


# Page. 51

![Page Image](https://bcdn.docswell.com/page/GJWPLW9272.jpg)

デモ
● TODO: Substance Painterからリモート実行するデモ


# Page. 52

![Page Image](https://bcdn.docswell.com/page/4EZKM594E3.jpg)

DCC側からの実行コード
● 今まで利用していたUERemoteExecutionを利用する
● 実行ファイルをimport_texture.pyに変更
from remote import UERemoteExecution
UERemoteExecution.execute_file(“import_texture.py”, “テクスチャパス”, “UEディレクトリ”)
変更
変更
52/96


# Page. 53

![Page Image](https://bcdn.docswell.com/page/Y76QN9KG7V.jpg)

UEで実行するimport_texture.py
● FbxImportUIの設定を削除
import unreal
Import sys
fbx_path = sys.argv[1]
Ue_dir = sys.argv[2]
task = unreal.AssetImportTask()
Task.set_editor_property(‘automated’, True)
Task.set_editor_property(‘filename’, f’{fbx_path}’)
Task.set_editor_property(‘destination_path’, f’{ue_dir}’)
Task.set_editor_property(‘save&#039;, True)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
次のページでテクスチャの設定変更コードに続く
53/96


# Page. 54

![Page Image](https://bcdn.docswell.com/page/G75R5NPXE4.jpg)

UEで実行するimport_texture.py
# unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
インポートしたテクスチャを取得する
texture_path = task.imported_object_paths[0]
asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()
texture = asset_registry.get_asset_by_object_path(texture_path).get_asset()
# テクスチャグループを設定
texture.lod_group = unreal.TextureGroup.TEXTUREGROUP_CHARACTER
texture_name = texture.get_name()
サフィックスで設定変更する
if texture_name.endswith(&quot;_D&quot;):
texture.srgb = True
elif texture_name.endswith(&quot;_N&quot;):
texture.srgb = False
texture.compression_settings = unreal.TextureCompressionSettings.TC_NORMALMAP
# 変更点あれば保存
unreal.EditorAssetLibrary.save_loaded_asset(texture, only_if_is_dirty=True)
54/96


# Page. 55

![Page Image](https://bcdn.docswell.com/page/9J2DN56QJR.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
55/96


# Page. 56

![Page Image](https://bcdn.docswell.com/page/DEY2PK9YEM.jpg)

オススメのGUIは2つ
● PySide6
● Mayaなどでよく使われている
● QtをPythonで使用できる様にしたもの。
● Qt自体はC++製
● Editor Utility Widget (EUW)
● UE標準のEditor用GUI機能
● 普段UEでUI作る時と同じエディターを利
用できる
56/96


# Page. 57

![Page Image](https://bcdn.docswell.com/page/VJN85RLR78.jpg)

オススメのGUIは2つ
● PySide6
● Mayaなどでよく使われている
● QtをPythonで使用できる様にしたもの。
● Qt自体はC++製
● Editor Utility Widget (EUW)
● UE標準のEditor用GUI機能
● 普段UEでUI作る時と同じエディターを利
用できる
57/96


# Page. 58

![Page Image](https://bcdn.docswell.com/page/YE9KYM4ZE3.jpg)

PySide6をUEで利用するまでの流れ
● site-packagesでPySide6を追加する
● python –m pip install PySide6 –t “UEが確認できるsite-packagesフォルダ”
● Python Plugin設定の追加パスにsite-packagesを追加する
● 導入完了
58/96


# Page. 59

![Page Image](https://bcdn.docswell.com/page/GE8R6LVYJD.jpg)

基本的なQMainWindowのサンプル
import sys
from PySide6 import QtWidgets
class SampleWindow(QtWidgets.QMainWindow):
def __init__(self):
super(SampleWindow, self).__init__()
self.setWindowTitle(&quot;Sample Window&quot;)
self.resize(640, 480)
layout = QtWidgets.QVBoxLayout()
# ボタンを作成
self.button = QtWidgets.QPushButton(&quot;Print Hello&quot;)
layout.addWidget(self.button)
central_widget = QtWidgets.QWidget()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
self.button.clicked.connect(self.print_hello)
def print_hello(self):
print(&quot;Hello&quot;)
サンプルで表示されるWindow
59/96


# Page. 60

![Page Image](https://bcdn.docswell.com/page/LELK9L59JR.jpg)

UIを表示する
import unreal
import sys
from sample import SampleWindow
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
window = SampleWindow()
window.show()
裏側対策
unreal.parent_external_window_to_slate(
window.winId(), unreal.SlateParentWindowSearchMethod.ACTIVE_WINDOW
)
60/96


# Page. 61

![Page Image](https://bcdn.docswell.com/page/4JM2ZM3VJW.jpg)

PySide6デモ


# Page. 62

![Page Image](https://bcdn.docswell.com/page/PJRM6VQWE9.jpg)

オススメのGUIは2つ
● PySide6 (PySide2)
● Mayaなどでよく使われている
● QtをPythonで使用できる様にしたもの。
● Qt自体はC++製
● Editor Utility Widget (EUW)
● UE標準のEditor用GUI機能
● 普段UEでUI作る時と同じエディターを利
用できる
62/96


# Page. 63

![Page Image](https://bcdn.docswell.com/page/PEXVMZ5V7X.jpg)

Editor Utility Widget (EUW)
● 通常はUIにBP側のイベントをcallbackとして登録して利用する。
● 今回はPython経由で全てバインドする方式でご紹介する。
● EUWをViewとして分離でき、色々と都合がよい。
63/96


# Page. 64

![Page Image](https://bcdn.docswell.com/page/3EKQG865ED.jpg)

EUWデモ


# Page. 65

![Page Image](https://bcdn.docswell.com/page/L73PN261E5.jpg)

EUW表示のコード紹介
import unreal
EUW_PATH = “EUWまでのパス&quot;
class EUWSample:
@classmethod
def launch(cls, *args, **kwargs):
euw = unreal.load_asset(EUW_PATH)
eus = unreal.get_editor_subsystem(unreal.EditorUtilitySubsystem)
euw_instance = eus.spawn_and_register_tab(euw)
EUWを生成・表示
ボタンを取得し、関数をバインドする
button: unreal.Button = euw_instance.get_editor_property(&quot;sample_call&quot;)
button.on_clicked.add_callable_unique(cls.clicked_sample)
@classmethod
def clicked_sample(cls):
unreal.log(&quot;Hello&quot;)
EUWSample.launch()
65/96


# Page. 66

![Page Image](https://bcdn.docswell.com/page/87DLWZVKEG.jpg)

EUWの注意点
● Level再読み込み時にバインド先が消える
● EUWが再生成される仕様のため
● Level開いた時に再度接続しなおすコードが必要
66/96


# Page. 67

![Page Image](https://bcdn.docswell.com/page/VJP29DV3J8.jpg)

GUI比較
● PySide6 (PySide2)
● メリット
● DCCで培ったGUI資源を利用できる
● 全部Pythonで完結できる
● デメリット
● テーマをUEと揃えるのは難しい
● 非公式で存在
● ドッキングできない
● Editor Utility Widget (EUW)
● メリット
● テーマが標準機能と揃う
● ドッキング可能で、UE ライクな挙動に
なる
● デメリット
● 既存のWidget資源が使えない
● Level を開きなおすとPythonとの連携
が切れる
67/96


# Page. 68

![Page Image](https://bcdn.docswell.com/page/2EVDM1ZNEQ.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
68/96


# Page. 69

![Page Image](https://bcdn.docswell.com/page/57GYG2457L.jpg)

メニュー周りの用語
Tool Menus
メニュー全体を管理するクラス
Tool Menu
各メニューの場所ごとの管理クラス
Menu
各メニュー。Sectionを持つ
Section
各Menu Entryごとのグループ。Menu Entryをもつ
Menu Entry
メニュー項目。ここにPythonの実行コマンドとかEUWの表示を追加する
69/96


# Page. 70

![Page Image](https://bcdn.docswell.com/page/4EQMXPGLJP.jpg)

メニュー周りの仕組みについて
厳密ではないが、既存のメニューに当てはめてみる
Tool Menus
Tool Menu
Menu
Section
Menu Entry
Menu Entry
70/96


# Page. 71

![Page Image](https://bcdn.docswell.com/page/KJ4V6Y55E1.jpg)

デモ
● TODO: 実際の動作デモ


# Page. 72

![Page Image](https://bcdn.docswell.com/page/LE13V6W2JG.jpg)

メニュー追加の実装
● メニュー定義用JSONファイルと実装コードに分ける
● JSONに分けておくと、簡単にメニューを追加しやすい
● 実装コード側だけ各プロジェクトで共通化しやすい
メニュー定義(JSON)
メニュー実装(python)
72/96


# Page. 73

![Page Image](https://bcdn.docswell.com/page/GEWPLW62J2.jpg)

メニュー定義用JSONファイルを用意する
{
}
“name”: “カスタムメニュー”, ⇐ メニュー名
“menus”: [
{
Menu
&quot;label&quot;: &quot;サンプルメニュー1&quot;,⇐ サブメニュー名
&quot;type&quot;: &quot;submenu&quot;,
&quot;children&quot;: [
{
&quot;label&quot;: &quot;サンプルセクション1&quot;,
Section
&quot;type&quot;: &quot;section&quot;,
&quot;children&quot;: [
{
Menu
Entry
&quot;label&quot;: &quot;ツール1&quot;,
&quot;type&quot;: &quot;python&quot;,
&quot;annotation&quot;: &quot;アノテーション1&quot;,
&quot;command&quot;: &quot;print(&#039;call tool1&#039;)&quot;
},
{
&quot;label&quot;: &quot;ツール2&quot;,
&quot;type&quot;: &quot;widget&quot;,
&quot;annotation&quot;: &quot;アノテーション2&quot;,
&quot;command&quot;: &quot;print(&#039;call tool2&#039;)&quot;
}
]
}
]
}
]
Jsonを読み込むと上記ができる
73/96


# Page. 74

![Page Image](https://bcdn.docswell.com/page/47ZKM5Y473.jpg)

メニュー実装(メニューを作成する)
def launch_menu():
MENU_PATH = os.path.join(SCRIPT_DIR, &quot;menu.json&quot;) JSONをロードする
json_data = None
with open(MENU_PATH, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
json_data = json.load(f)
# メニューの管理クラス取得
tool_menus: unreal.ToolMenus = unreal.ToolMenus.get() ⇐ メニュー全体を管理するクラスを取得する
# メインメニューの上部を取得
main_menu = tool_menus.find_menu(&quot;LevelEditor.MainMenu&quot;) ⇐ UEのトップメニューを取得する
menu_name = json_data[&quot;name&quot;]
カスタムメニューを作成する
custom_menu = main_menu.add_sub_menu(
main_menu.get_name(), menu_name, menu_name, menu_name)
以下サブメニュー作成に続く
現状の状態イメージ
74/96


# Page. 75

![Page Image](https://bcdn.docswell.com/page/YJ6QN9DGJV.jpg)

メニュー実装(メニューを作成する)
サンプルメニュー１を作成する
for sub_menu_data in json_data[&quot;menus&quot;]:
sub_menu_name = sub_menu_data[&quot;label&quot;]
custom_sub_menu = custom_menu.add_sub_menu(
custom_menu.get_name(), sub_menu_name, sub_menu_name, sub_menu_name)
サンプルセクション1を作成する
for section_data in sub_menu_data[&quot;children&quot;]:
custom_sub_menu.add_section(section_data[&quot;label&quot;], section_data[&quot;label&quot;])
Menu Entryを作成する
for menu_entry_data in section_data[&quot;children&quot;]:
add_entry(custom_sub_menu, menu_entry_data, section_data[&quot;label&quot;])
# メニューリフレッシュ
tool_menus.refresh_all_widgets()
現状の状態イメージ ⇒
75/96


# Page. 76

![Page Image](https://bcdn.docswell.com/page/GJ5R5N3X74.jpg)

メニュー実装(SectionとMenu Entry)
def add_entry(menu: unreal.ToolMenu, data: dict, include_session):
name = data.get(&quot;label&quot;, &quot;&quot;)
menu_type = data.get(&quot;type&quot;)
annotation = data.get(&quot;annotation&quot;, &quot;&quot;)
command = data.get(&quot;command&quot;, &quot;&quot;)
if menu_type == &quot;python&quot;:
entry = unreal.ToolMenuEntry(
name=name,
type=unreal.MultiBlockType.MENU_ENTRY,
)
entry.set_label(name)
entry.set_tool_tip(annotation)
entry.set_string_command(
type=unreal.ToolMenuStringCommandType.PYTHON,
custom_type=&quot;&quot;,
string=f&quot;{command}&quot;,
)
menu.add_menu_entry(include_session, entry)
現状の状態イメージ ⇒
Menu Entryの情報取得
Python 用Menu Entryを作成する
76/96


# Page. 77

![Page Image](https://bcdn.docswell.com/page/9E2DN5ZQER.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
77/96


# Page. 78

![Page Image](https://bcdn.docswell.com/page/D7Y2PKRY7M.jpg)

テストとは
● テストとは？
● 実装コードが正しいかどうか確認するための手法
● 色々なテストあるが、今回はユニットテストのみを対象。
● 機能テストとかホワイトボックステストとか色々ある。
78/96


# Page. 79

![Page Image](https://bcdn.docswell.com/page/VEN85RDRJ8.jpg)

どんな事する？
● 実装コードに対応した、テストコードを書く
実装コード
テストコード
class Sample:
def increment(self, number: int) -&gt; int:
return number + 1
from sample import Sample
class TestSample:
def test_increment(self):
result = Sample().increment(4)
assert result == 5
このコードをテストライブラリで実行
コードが正しいかを確認する
79/96


# Page. 80

![Page Image](https://bcdn.docswell.com/page/Y79KYMGZ73.jpg)

テストを行う理由は？
● Unreal Engineのアップデート時に非常に恩恵がある。
● エンジンアップデート時にUnreal Pythonの関数が結構変わってしまう。
● ツールを実行するまでエラーがわからない状態になる
● 全ツールを動作確認すれば問題ないが現実的ではない
テストコードを用意し、自動実行しておけば問題範囲をすぐ確認できる
80/96


# Page. 81

![Page Image](https://bcdn.docswell.com/page/G78R6L8YED.jpg)

やらなくてよいときもある？
● Pythonでツールを作る量が少なければ
● テストコードを書くコスト &gt; 確認するコストになる
● 一般的にテストは色々なメリットがあると言われているが・・
● 当然コストもかかる為、Unreal Pythonで開発 = 絶対やろうというわけではない
本講演では
Unreal Engineアップデートにツール保守を迅速に対応させるという気持ち
81/96


# Page. 82

![Page Image](https://bcdn.docswell.com/page/L7LK9LR9ER.jpg)

利用するライブラリ
● Pythonのテストフレームワークとして有名なpytestを例に紹介
82/96


# Page. 83

![Page Image](https://bcdn.docswell.com/page/4EM2ZMRVEW.jpg)

pytest導入方法
● site-packagesでpytestを追加する
● python –m pip install pytest –t “UEが確認できるsite-packagesフォルダ”
● Python Plugin設定の追加パスにsite-packagesを追加する
83/96


# Page. 84

![Page Image](https://bcdn.docswell.com/page/PERM6VRW79.jpg)

pytestの構成
● test-sample
● src
● __init__.py
● sample
・・・ モジュール
● __init__.py
● main.py ・・・ 実装
● tests
● __init__.py
● test_main.py ・・・ テストコード
srcフォルダへのパスを通しておく
84/96


# Page. 85

![Page Image](https://bcdn.docswell.com/page/P7XVMZRVJX.jpg)

実装とテストコード
● 先ほどのサンプルを利用する
実装コード(main.py)
テストコード (test_main.py)
class Sample:
def increment(self, number: int) -&gt; int:
return number + 1
from sample import Sample
class TestSample:
def test_increment(self):
result = Sample().increment(4)
assert result == 5
85/96


# Page. 86

![Page Image](https://bcdn.docswell.com/page/37KQG8R57D.jpg)

テスト実行するexecute_test.py
● Unreal Engine上で実行できるようにコードを実装する
import pytest
from pathlib import Path
import sys
SCRIPT_DIR = Path(__file__).parent ⇐ srcフォルダパス
TEST_DIR = SCRIPT_DIR / “tests&quot; ⇐ testsフォルダパス
hit_modules = []
for module_name in sys.modules.keys():
if module_name.startswith(“sample&quot;):
hit_modules.append(module_name)
for module_name in hit_modules:
del sys.modules[module_name]
リロード対策
pytest.main([TEST_DIR.as_posix(), &quot;-v&quot;, &quot;-s&quot;, f&quot;--rootdir={SCRIPT_DIR.as_posix()}&quot;])
86/96


# Page. 87

![Page Image](https://bcdn.docswell.com/page/LJ3PN28175.jpg)

デモ
● Unreal Engine上でexecute_test.pyを実行する
87/96


# Page. 88

![Page Image](https://bcdn.docswell.com/page/8JDLWZMK7G.jpg)

わざとテストコードを失敗させてみる
● Unreal Engine上でexecute_test.pyを実行する
from sample import Sample
class TestSample:
def test_increment(self):
result = Sample().increment(4)
assert result == 6
⇐ 4をインクリメントしても絶対に６にならないので、エラーになる
88/96


# Page. 89

![Page Image](https://bcdn.docswell.com/page/VEP29DR3E8.jpg)

Unreal Pythonでのテスト実行方法
● コマンドラインでPythonコマンドレットを使用する
● Python Pluginを有効化すると追加されるコマンドライン実行機能
UnrealEditor-Cmd.exe “UProjectパス” -run=pythonscript -script=“execute_test.py“
-FullStdOutLogOutput
● JenkinsなどのCI環境向け
89/96


# Page. 90

![Page Image](https://bcdn.docswell.com/page/27VDM1RN7Q.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
90/96


# Page. 91

![Page Image](https://bcdn.docswell.com/page/5JGYG2M5JL.jpg)

配布方法と配布物について
● Version Control管理のフォルダに配布物を配置し、同期する方式をとる
techart
Content
ツール用
┗ tools
┗ Python
┗ exporter
┗ init_unreal.py
スタートアップ。Menu.pyのメニュー起動
┗ __init__.py
を追加しておく。
┗ app.py
┗ menus
メニュー
┗ menu.py
┗ menu.yaml
テスト用
┗ tests
┗ exporter
┗ test_app.py
┗ site-packages ⇐ 外部ライブラリ。Pysideやpytestなど
91/96


# Page. 92

![Page Image](https://bcdn.docswell.com/page/47QMXPRLEP.jpg)

アジェンダ
1. Unreal Pythonの導入方法と各種設定
2. 基本アセットのエクスポーター作成
3. ツールに使うGUIについて
4. メニューへの追加
5. テスト実行について
6. 配布方法
7. まとめ
92/96


# Page. 93

![Page Image](https://bcdn.docswell.com/page/KE4V6Y8571.jpg)

Unreal Pythonのメリット
● 自由度の高さ
● ほぼBlueprint関数が利用できるのでかなり自由にできることがわかる
● 豊富なライブラリによる、目的達成の速さ
● Python はパッケージマネージャーが広く普及しており、簡単に導入し、利用できる
● 学習難易度が低い = 利用者を増やしやすい
● C++クラスに詳しくなる
● Unreal PythonのAPIは実質的にC++のクラスなので、エディター用のC++クラスに詳しくなる。
93/96


# Page. 94

![Page Image](https://bcdn.docswell.com/page/L713V622EG.jpg)

Unreal Pythonのデメリット
● 実行速度の遅さ
● BPとC++に比べると明らかに実行速度は遅い。
● 実行時にしかエラーがわからない
● 静的解析が充実してきたが、完全ではない。
● エクスポーターなどアセットパイプラインを担う部分ではいつの間にか動かなくなるのは問題あり。
● 前述したEngineアップデート時にも問題となりやすい。
● テストコードなどで補強する必要がある。
● 自由度は高いが完全ではない
● かなりの範囲が公開されているが、Editor 機能全てにアクセスできる訳ではない
● アクセスできない部分は、C++経由で公開する必要がある
94/96


# Page. 95

![Page Image](https://bcdn.docswell.com/page/G7WPLWR2E2.jpg)

C++でツールを作った方が良いケース
● アーティストのワークフローをサポートするエンジニアが多く居る場合
● 現実、中々難しいが・・・。
● 速度が要求される箇所
● Unreal Pythonのデメリットでも記載した通り、速度が要求されるところは苦手。
● Blueprintと混ぜて使いたい場合
● 具体的なケースでBlueprint内の関数をpythonで定義するなど。
● 読み込み順の問題でBlueprintがコンパイルエラーになることがあった。
● ModuleのLoading Phaseを変更することで、回避は可能そう (未検証)
● 元々C++からBlueprint、Pythonに公開するという主従関係になってるので使い方が違う。
● =&gt; 素直にC++で定義するかBlueprintで定義する。
95/96


# Page. 96

![Page Image](https://bcdn.docswell.com/page/4JZKM5R4J3.jpg)

おわりに
● Unreal Pythonの導入方法から自動化事例と続き、リリース方法までの一連の
流れをご紹介させていただきました。
● 本講演でUnreal Python利用で参考になるものがあれば幸いです。
ご清聴ありがとうございました！
96/96


# Page. 97

![Page Image](https://bcdn.docswell.com/page/YE6QN9RGEV.jpg)

Q＆A


# Page. 98

![Page Image](https://bcdn.docswell.com/page/GE5R5N8XJ4.jpg)

アンケートへのご協力をよろしくお願いします
アンケートQRコード
（後日ご支給いたします）
https://docs.google.com/forms/d/e/1FAIpQLSeeq3O53ausrSoHXHl
uKskiabOlucYgXBajAhVQ2IbL__Km7Q/viewform
98/96


