Python / Blueprintによる
Unreal Engineの自動化
Epic Games Japan / Technical Artist
小林浩之
#UE4 | @UNREALENGINE
自己紹介
小林 浩之
Epic Games Japan / Technical Artist
スクウェア・エニックス大阪で背景TAを2年ほど
今年2月からEGJのエンタープライズ分野のサポートとして入社
#UE4 | @UNREALENGINE
目次
• 大規模開発における作業効率化・自動化の需要
• Unreal Engine 4の作業効率化・自動化ツール紹介
• 実装例
• Datasmithインポートの効率化 Blueprint
• 命名規則に応じたアセットリネーム Editor Utility Widget & Blueprint
• Pythonによるインスタンシング Python
• インスタンスを個別のStatic Meshに変換 Python
#UE4 | @UNREALENGINE
目次
• 大規模開発における作業効率化・自動化の需要
• Unreal Engine 4の作業効率化・自動化ツール紹介
• 実装例
• Datasmithインポートの効率化 Blueprint
• 命名規則に応じたアセットリネーム Editor Utility Widget & Blueprint
• Pythonによるインスタンシング Python
• インスタンスを個別のStatic Meshに変換 Python
#UE4 | @UNREALENGINE
大規模開発における作業効率化・自動化の需要
大量に配置されたオブジェクトの整理、膨大なアセットの管理など、
手作業でやっているとコストがかかるりすぎる・・・
時間が足りない・・・
クオリティアップにコストを割けない・・・
#UE4 | @UNREALENGINE
大規模開発における作業効率化・自動化の需要
オブジェクト整理作業
アセット管理作業
などなど
自動化
#UE4 | @UNREALENGINE
McLarenによる事例
Unreal Engineへの
CADデータインポートの自動化
#UE4 | @UNREALENGINE
McLarenによる事例
インポートフローの改善
#UE4 | @UNREALENGINE
目次
• 大規模開発における作業効率化・自動化の需要
• Unreal Engine 4の作業効率化・自動化ツール紹介
• 実装例
• Datasmithインポートの効率化 Blueprint
• 命名規則に応じたアセットリネーム Editor Utility Widget & Blueprint
• Pythonによるインスタンシング Python
• インスタンスを個別のStatic Meshに変換 Python
#UE4 | @UNREALENGINE
Bluetility (Blueprint Utility)
Blueprintを使ったスクリプティング
#UE4 | @UNREALENGINE
Bluetility
プラグイン > Editor Scripting Utilities
#UE4 | @UNREALENGINE
Unreal Python
Pythonによるエディタスクリプティング
#UE4 | @UNREALENGINE
Unreal Python
プラグイン > Python Editor Script Pluginにチェックで有効化
#UE4 | @UNREALENGINE
Unreal Python
アウトプットログから直接入力
実行方法
#UE4 | @UNREALENGINE
Unreal Python
.pyファイルのパス指定で実行
実行方法
#UE4 | @UNREALENGINE
Unreal Python
エディタ起動時に実行
#UE4 | @UNREALENGINE
Editor Utility Widget
UMG&Blueprintでエディタ拡張
#UE4 | @UNREALENGINE
Editor Utility Widget
コンテンツブラウザで右クリック>Editor Utilities>Editor Widgetから作成
#UE4 | @UNREALENGINE
Editor Utility Widget
#UE4 | @UNREALENGINE
Editor Utility Widget
UIに必要な機能(ボタンやテキストなど)をD&Dで置く
#UE4 | @UNREALENGINE
Editor Utility Widget
UIからの処理をBlueprintで作成
#UE4 | @UNREALENGINE
Editor Utility Widget
アセット右クリック>Run Editor Utility WidgetでWindow立ち上げ、実行
#UE4 | @UNREALENGINE
Editor Utility Widget
EGJ 岡田による解説記事
[UE4]エディタ上で動作するツール・エディタ拡張をUMGで簡単に作れる
Editor Utility Widget について
https://qiita.com/EGJ-Kaz_Okada/items/9f530db3b53d0fde3f20
[UE4]Editor Utility Widgetでツール・エディタ拡張を作る際のUndo/Redo
の実装方法について
https://qiita.com/EGJ-Kaz_Okada/items/985b98fb934d751f4f69
#UE4 | @UNREALENGINE
目次
• 大規模開発における作業効率化・自動化の需要
• Unreal Engine 4の作業効率化・自動化ツール紹介
• 実装例
• Datasmithインポートの効率化 Blueprint
• 命名規則に応じたアセットリネーム Editor Utility Widget & Blueprint
• Pythonによるインスタンシング Python
• インスタンスを個別のStatic Meshに変換 Python
#UE4 | @UNREALENGINE
実装例:Datasmithインポートの効率化
#UE4 | @UNREALENGINE
Datasmithとは
CADソフトなどのデータをUE4用に変換してインポートする機能 (Unreal Studioのみ)
Datasmith
#UE4 | @UNREALENGINE
Datasmithによって大幅に効率化されるが・・・
CADデータの場合細かいネジなどのパーツまで含んだデータになっている場合が多い
環境によっては処理負荷が高くなってしまう可能性も
#UE4 | @UNREALENGINE
Datasmithによって大幅に効率化されるが・・・
リアルタイムエンジンでスムーズに描画するためには
ほとんど描画されないような小さいパーツは削除したり、一つにまとめる必要がある
#UE4 | @UNREALENGINE
普通にインポートして後から削除しようとするとする場合
パーツ数が膨大だと作業コストが高くなってしまう
インポート 小さいパーツを探す 削除
数百パーツを手作業でとか・・・
#UE4 | @UNREALENGINE
BlueprintやPythonを使うことでこれらの作業を自動化できる
インポート 小さいパーツを探す 削除
自動化
#UE4 | @UNREALENGINE
小さいパーツを除外してインポート
#UE4 | @UNREALENGINE
小さいパーツを除外してインポート
#UE4 | @UNREALENGINE
Blueprint
※拡大して解説していきます
#UE4 | @UNREALENGINE
解説
インポートするデータからDatasmith Sceneを構築
#UE4 | @UNREALENGINE
Datasmith Scene
Datasmithでは、実際にデータをインポートする前に
メモリ上で一度シーン構築を行う
インポート
Datasmith Scene in Memory
メモリ上でシーンを構築
CADデータ
#UE4 | @UNREALENGINE
Datasmith Scene
Datasmithでは、実際にデータをインポートする前に
メモリ上で一度シーン構築を行う
インポート
Datasmith Scene in Memory
メモリ上でシーンを構築
CADデータ
#UE4 | @UNREALENGINE
解説
Datasmith Scene内のアクタを取得
#UE4 | @UNREALENGINE
解説
バウンディングボックスの大きさを評価
#UE4 | @UNREALENGINE
解説
バウンディングボックス
オブジェクトを囲む最小の立方体
この立方体の幅、奥行き、高さから
オブジェクトの大体の大きさを測る
#UE4 | @UNREALENGINE
解説
条件に当てはまればアクタを削除
#UE4 | @UNREALENGINE
解説
インポートオプションを設定
#UE4 | @UNREALENGINE
解説
実際にインポートし、最後にDatasmith Sceneを削除
#UE4 | @UNREALENGINE
実装例:命名規則に応じたアセットリネーム
#UE4 | @UNREALENGINE
命名規則について
アセットの種類や用途に応じて名前の前後に付ける文字列
#UE4 | @UNREALENGINE
命名規則について
アセットの種類や用途に応じて名前の前後に付ける文字列
例えば・・・
Static Mesh アセット「Table」があるとしたら
Static Meshの省略 SM を付け 「SM_Table」
バリエーションがある場合は
番号やアルファベットを付け「 SM_Table _A」にする
#UE4 | @UNREALENGINE
命名規則について
参考
Unreal Engine Assets Naming Convention
https://wiki.unrealengine.com/Assets_Naming_Convention_JP
#UE4 | @UNREALENGINE
命名規則について
プロジェクトが大規模化するにつれ、命名規則はより重要に
#UE4 | @UNREALENGINE
命名規則について
プロジェクトが大規模化するにつれ、命名規則はより重要に
作業者が自由に名前を付けていると・・・
● 他の作業者から見たとき用途や種類が判別しにくい
● 特定のアセットを探しずらい
#UE4 | @UNREALENGINE
実装例:命名規則に応じたアセットリネーム
#UE4 | @UNREALENGINE
解説 UI
エディットできるText BoxやButtonなどを置いただけ
シンプルな構成
#UE4 | @UNREALENGINE
解説 UI
エディットできるText BoxやButtonなどを置いただけ
シンプルな構成
#UE4 | @UNREALENGINE
解説 Blueprint
#UE4 | @UNREALENGINE
解説 Blueprint
ボタンが押されたら選択しているアセットを取得
#UE4 | @UNREALENGINE
解説 Blueprint
アセットの種類毎にリネーム処理
#UE4 | @UNREALENGINE
解説 Blueprint
関数Asset Renameの中身
#UE4 | @UNREALENGINE
解説 Blueprint
処理するクラスを設定
#UE4 | @UNREALENGINE
解説 Blueprint
リネーム処理
#UE4 | @UNREALENGINE
実装例:Pythonによるインスタンシング
#UE4 | @UNREALENGINE
インスタンシング
大量のオブジェクトを描画する際に有効な手法
ドローコールを削減し、描画コストを下げる
ドローコール
現在の画面を描画するために必要な情報を呼び出す命令のこと
回数が多いほど処理負荷につながる可能性がある
#UE4 | @UNREALENGINE
インスタンス化によるドローコールの削減
非インスタンス インスタンス
9回分のドローコール 1回分のドローコール
#UE4 | @UNREALENGINE
Instanced Static Mesh
Unreal Engineでのインスタンス化メッシュ
#UE4 | @UNREALENGINE
Instanced Static Mesh
#UE4 | @UNREALENGINE
Instanced Static Mesh
• Static Mesh
#UE4 | @UNREALENGINE
Instanced Static Mesh
• Static Mesh
• インスタンス数分の位置、回転、スケール
#UE4 | @UNREALENGINE
Static Meshをインスタンス化
ツールやスクリプトを使わずに
手作業で変換しようとすると・・・
アクタ一つ一つの位置、回転、
スケールをコピーして・・・
#UE4 | @UNREALENGINE
Static Meshをインスタンス化
ツールやスクリプトを使わずに
手作業で変換しようとすると・・・
インスタンスに追加
#UE4 | @UNREALENGINE
Static Meshをインスタンス化
Merge Actors
選択アクタを一つのインスタンスに変換
#UE4 | @UNREALENGINE
Static Meshをインスタンス化
Merge Actors
選択アクタを一つのインスタンスに変換
複数インスタンスを一度に生成はできない
#UE4 | @UNREALENGINE
Static Meshをインスタンス化
Merge Actors
多数のアクタがあるとして・・・
#UE4 | @UNREALENGINE
Static Meshをインスタンス化
Merge Actors
複数グループに分けてインスタンス化したい場合
インスタンス化したいグループ毎に
選択して変換する作業が必要
#UE4 | @UNREALENGINE
インスタンス化によるドローコールの削減
大量のアクタを複数インスタンス化していくのは高コスト
なるべく自動で、いい感じのグループに分けてインスタンス化したい・・・
#UE4 | @UNREALENGINE
インスタンス化によるドローコールの削減
大量のアクタを複数インスタンス化していくのは高コスト
なるべく自動で、いい感じのグループに分けてインスタンス化したい・・・
Python外部ライブラリからK-means法を使ってインスタンシング!
#UE4 | @UNREALENGINE
K-means法とは ざっくり解説
クラスタ分析を行う手法の一つ
Pythonの外部ライブラリScikit Learnに含まれている
#UE4 | @UNREALENGINE
K-means法とは ざっくり解説
バラバラな座標のリストがあるとして・・・
#UE4 | @UNREALENGINE
K-means法とは ざっくり解説
クラスタ数=5
設定したクラスタ数に応じて、近い属性同士のグループを作る
#UE4 | @UNREALENGINE
Unreal Pythonで外部ライブラリを使う
ライブラリインストール後、PythonLibsite-packagesを
EngineBinariesThirdPartyPythonWin64Lib以下に丸ごとコピー
import 〇〇でインポート出来るようになる
#UE4 | @UNREALENGINE
K-means法によるインスタンシング
#UE4 | @UNREALENGINE
コード 1/2
import unreal
import numpy as np
import sklearn
from sklearn.cluster import KMeans
bp_instance = unreal.EditorAssetLibrary.load_blueprint_class('/Game/BP_Instance.BP_Instance')
list_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
list_static_mesh_actors = unreal.EditorFilterLibrary.by_class(list_actors,unreal.StaticMeshActor)
list_unique = np.array([])
for lsm in list_static_mesh_actors:
static_mesh = lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh")
list_unique = np.append(list_unique,static_mesh)
list_unique = np.unique(list_unique)
for lu in list_unique:
list_transform = np.array([])
list_locations = np.array([[0,0,0]])
#UE4 | @UNREALENGINE
コード 2/2
for lsm in list_static_mesh_actors:
if lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh") == lu:
list_transform = np.append(list_transform,lsm.get_actor_transform())
location = np.array([[lsm.get_actor_location().x,lsm.get_actor_location().y,lsm.get_actor_location().z]])
list_locations = np.append(list_locations,location,axis=0)
list_locations = np.delete(list_locations,0,axis=0)
num_clusters = 3
pred = KMeans(n_clusters=num_clusters).fit_predict(list_locations)
instanced_components = np.array([])
for i in range(num_clusters):
instanced_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(bp_instance,(0,0,0),(0,0,0))
instanced_component = instanced_actor.get_component_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
for j, pd in enumerate(pred):
instanced_components[pd].add_instance(list_transform[j])
for k in range(num_clusters):
instanced_components[k].set_editor_property("StaticMesh",lu)
for lsm in list_static_mesh_actors:
lsm.destroy_actor()
#UE4 | @UNREALENGINE
解説 1/5
#ライブラリをインポート
import unreal
import numpy as np
import sklearn
from sklearn.cluster import KMeans
#インスタンス用のクラスをロード
bp_instance = unreal.EditorAssetLibrary.load_blueprint_class('/Game/BP_Instance.BP_Instance')
#選択しているアクタを取得
list_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
list_static_mesh_actors = unreal.EditorFilterLibrary.by_class(list_actors,unreal.StaticMeshActor)
#UE4 | @UNREALENGINE
解説 1/5
#ライブラリをインポート
import unreal
import numpy as np
import sklearn
from sklearn.cluster import KMeans
#インスタンス用のクラスをロード
bp_instance = unreal.EditorAssetLibrary.load_blueprint_class('/Game/BP_Instance.BP_Instance')
#選択しているアクタを取得
list_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
list_static_mesh_actors = unreal.EditorFilterLibrary.by_class(list_actors,unreal.StaticMeshActor)
#UE4 | @UNREALENGINE
解説 1/5
#ライブラリをインポート
import unreal
import numpy as np
import sklearn
from sklearn.cluster import KMeans
#インスタンス用のクラスをロード
bp_instance = unreal.EditorAssetLibrary.load_blueprint_class('/Game/BP_Instance.BP_Instance')
#選択しているアクタを取得
list_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
list_static_mesh_actors = unreal.EditorFilterLibrary.by_class(list_actors,unreal.StaticMeshActor)
#UE4 | @UNREALENGINE
解説 1/5
#ライブラリをインポート
import unreal
import numpy as np
import sklearn
from sklearn.cluster import KMeans
#インスタンス用のクラスをロード
bp_instance = unreal.EditorAssetLibrary.load_blueprint_class('/Game/BP_Instance.BP_Instance')
#選択しているアクタを取得
list_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
list_static_mesh_actors = unreal.EditorFilterLibrary.by_class(list_actors,unreal.StaticMeshActor)
後で選択アクタから
位置、回転、メッシュ情報などを取得
#UE4 | @UNREALENGINE
解説 2/5
#メッシュの種類毎にクラスタリングするため、アクタのリストからメッシュの種類がいくつあるかを求める
list_unique = np.array([])
for lsm in list_static_mesh_actors:
static_mesh = lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh")
list_unique = np.append(list_unique,static_mesh)
list_unique = np.unique(list_unique)
#インスタンスに追加するトランスフォームリストと、クラスタリングに使うための位置リストを作成
for lu in list_unique:
list_transform = np.array([])
list_locations = np.array([[0,0,0]])
#UE4 | @UNREALENGINE
複数種類のメッシュがあった場合
Instanced Static Meshが
持てるメッシュは一種類のみ
#UE4 | @UNREALENGINE
複数種類のメッシュがあった場合
クラスタ数=3
#UE4 | @UNREALENGINE
複数種類のメッシュがあった場合
種類ごとにクラスタリング
クラスタ数=3
#UE4 | @UNREALENGINE
複数種類のメッシュがあった場合
クラスタ数=3
種類ごとにクラスタリング
#UE4 | @UNREALENGINE
解説 3/5
#インスタンスに追加するトランスフォームリストと、クラスタリングに使うための位置リストを作成
for lsm in list_static_mesh_actors:
if lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh") == lu:
list_transform = np.append(list_transform,lsm.get_actor_transform())
location =
np.array([[lsm.get_actor_location().x,lsm.get_actor_location().y,lsm.get_actor_location().z]])
list_locations = np.append(list_locations,location,axis=0)
list_locations = np.delete(list_locations,0,axis=0)
#クラスタリング
num_clusters = 3
pred = KMeans(n_clusters=num_clusters).fit_predict(list_locations)
#UE4 | @UNREALENGINE
解説 3/5
#インスタンスに追加するトランスフォームリストと、クラスタリングに使うための位置リストを作成
for lsm in list_static_mesh_actors:
if lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh") == lu:
list_transform = np.append(list_transform,lsm.get_actor_transform())
location =
np.array([[lsm.get_actor_location().x,lsm.get_actor_location().y,lsm.get_actor_location().z]])
list_locations = np.append(list_locations,location,axis=0)
list_locations = np.delete(list_locations,0,axis=0)
#クラスタリング
num_clusters = 3
pred = KMeans(n_clusters=num_clusters).fit_predict(list_locations)
#UE4 | @UNREALENGINE
解説 3/5
#インスタンスに追加するトランスフォームリストと、クラスタリングに使うための位置リストを作成
for lsm in list_static_mesh_actors:
if lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh") == lu:
list_transform = np.append(list_transform,lsm.get_actor_transform())
location =
np.array([[lsm.get_actor_location().x,lsm.get_actor_location().y,lsm.get_actor_location().z]])
list_locations = np.append(list_locations,location,axis=0)
list_locations = np.delete(list_locations,0,axis=0)
#クラスタリング
num_clusters = 3
pred = KMeans(n_clusters=num_clusters).fit_predict(list_locations)
Get_actor_location() ・・・Unreal Vector型を返す
#UE4 | @UNREALENGINE
Unreal Vector型
Unreal Engine上でVector型をやり取りするための型
Kmeansでも使えるように
#UE4 | @UNREALENGINE
解説 3/5
#インスタンスに追加するトランスフォームリストと、クラスタリングに使うための位置リストを作成
for lsm in list_static_mesh_actors:
if lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh") == lu:
list_transform = np.append(list_transform,lsm.get_actor_transform())
location =
np.array([[lsm.get_actor_location().x,lsm.get_actor_location().y,lsm.get_actor_location().z]])
list_locations = np.append(list_locations,location,axis=0)
list_locations = np.delete(list_locations,0,axis=0)
#クラスタリング
num_clusters = 3
pred = KMeans(n_clusters=num_clusters).fit_predict(list_locations)
#UE4 | @UNREALENGINE
解説 3/5
#インスタンスに追加するトランスフォームリストと、クラスタリングに使うための位置リストを作成
for lsm in list_static_mesh_actors:
if lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh") == lu:
list_transform = np.append(list_transform,lsm.get_actor_transform())
location =
np.array([[lsm.get_actor_location().x,lsm.get_actor_location().y,lsm.get_actor_location().z]])
list_locations = np.append(list_locations,location,axis=0)
list_locations = np.delete(list_locations,0,axis=0)
#クラスタリング
num_clusters = 3
pred = KMeans(n_clusters=num_clusters).fit_predict(list_locations)
#UE4 | @UNREALENGINE
解説 3/5
#インスタンスに追加するトランスフォームリストと、クラスタリングに使うための位置リストを作成
for lsm in list_static_mesh_actors:
if lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh") == lu:
list_transform = np.append(list_transform,lsm.get_actor_transform())
location =
np.array([[lsm.get_actor_location().x,lsm.get_actor_location().y,lsm.get_actor_location().z]])
list_locations = np.append(list_locations,location,axis=0)
list_locations = np.delete(list_locations,0,axis=0)
#クラスタリング
num_clusters = 5
pred = KMeans(n_clusters=num_clusters).fit_predict(list_locations)
n_clusters = 5
#UE4 | @UNREALENGINE
解説 3/5
#インスタンスに追加するトランスフォームリストと、クラスタリングに使うための位置リストを作成
for lsm in list_static_mesh_actors:
if lsm.get_component_by_class(unreal.StaticMeshComponent).get_editor_property("StaticMesh") == lu:
list_transform = np.append(list_transform,lsm.get_actor_transform())
location = np.array([[lsm.get_actor_location().x,lsm.get_actor_location().y,lsm.get_actor_location().z]])
list_locations = np.append(list_locations,location,axis=0)
list_locations = np.delete(list_locations,0,axis=0)
#クラスタリング
num_clusters = 5
pred = KMeans(n_clusters=num_clusters).fit_predict(list_locations)
0
1
2
3
4
0
0
0
1
1
1
2
2
2
2
3
3
3
4
4
4
fit_predict
#UE4 | @UNREALENGINE
解説 4/5
#コンポーネントのリストを作成
instanced_components = np.array([])
#クラスタ毎にアクタをスポーン、コンポーネントをリストに入れる
for i in range(num_clusters):
instanced_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(bp_instance,(0,0,0),(0,0,0))
instanced_component = instanced_actor.get_component_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
#UE4 | @UNREALENGINE
解説 4/5
#コンポーネントのリストを作成
instanced_components = np.array([])
#クラスタ毎にアクタをスポーン、コンポーネントをリストに入れる
for i in range(num_clusters):
instanced_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(bp_instance,(0,0,0),(0,0,0))
instanced_component = instanced_actor.get_component_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
#UE4 | @UNREALENGINE
解説 4/5
#コンポーネントのリストを作成
instanced_components = np.array([])
#クラスタ毎にアクタをスポーン、コンポーネントをリストに入れる
for i in range(num_clusters):
instanced_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(bp_instance,(0,0,0),(0,0,0))
instanced_component = instanced_actor.get_component_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
#UE4 | @UNREALENGINE
解説 4/5
#コンポーネントのリストを作成
instanced_components = np.array([])
#クラスタ毎にアクタをスポーン、コンポーネントをリストに入れる
for i in range(num_clusters):
instanced_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(bp_instance,(0,0,0),(0,0,0))
instanced_component = instanced_actor.get_component_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
スポーンするアクタは
あらかじめ用意しておく
Blueprintを作成し
Instanced Static Meshを追加
#UE4 | @UNREALENGINE
解説 4/5
#コンポーネントのリストを作成
instanced_components = np.array([])
#クラスタ毎にアクタをスポーン、コンポーネントをリストに入れる
for i in range(num_clusters):
instanced_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(bp_instance,(0,0,0),(0,0,0))
instanced_component = instanced_actor.get_component_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
#UE4 | @UNREALENGINE
解説 5/5
#クラスタ番号を元に親となるインスタンスを選択し、トランスフォームを追加
for j, pd in enumerate(pred):
instanced_components[pd].add_instance(list_transform[j])
#スタティックメッシュを割り当て
for k in range(num_clusters):
instanced_components[k].set_editor_property("StaticMesh",lu)
#最初に選択していたアクタを削除
for lsm in list_static_mesh_actors:
lsm.destroy_actor()
#UE4 | @UNREALENGINE
解説 5/5
#クラスタ番号を元に親となるインスタンスを選択し、トランスフォームを追加
for j, pd in enumerate(pred):
instanced_components[pd].add_instance(list_transform[j])
#スタティックメッシュを割り当て
for k in range(num_clusters):
instanced_components[k].set_editor_property("StaticMesh",lu)
#最初に選択していたアクタを削除
for lsm in list_static_mesh_actors:
lsm.destroy_actor()
#UE4 | @UNREALENGINE
解説 5/5
#クラスタ番号を元に親となるインスタンスを選択し、トランスフォームを追加
for j, pd in enumerate(pred):
instanced_components[pd].add_instance(list_transform[j])
#スタティックメッシュを割り当て
for k in range(num_clusters):
instanced_components[k].set_editor_property("StaticMesh",lu)
#最初に選択していたアクタを削除
for lsm in list_static_mesh_actors:
lsm.destroy_actor()
空のインスタンス
#UE4 | @UNREALENGINE
解説 5/5
#クラスタ番号を元に親となるインスタンスを選択し、トランスフォームを追加
for j, pd in enumerate(pred):
instanced_components[pd].add_instance(list_transform[j])
#スタティックメッシュを割り当て
for k in range(num_clusters):
instanced_components[k].set_editor_property("StaticMesh",lu)
#最初に選択していたアクタを削除
for lsm in list_static_mesh_actors:
lsm.destroy_actor()
#UE4 | @UNREALENGINE
解説 5/5
#クラスタ番号を元に親となるインスタンスを選択し、トランスフォームを追加
for j, pd in enumerate(pred):
instanced_components[pd].add_instance(list_transform[j])
#スタティックメッシュを割り当て
for k in range(num_clusters):
instanced_components[k].set_editor_property("StaticMesh",lu)
#最初に選択していたアクタを削除
for lsm in list_static_mesh_actors:
lsm.destroy_actor()
#UE4 | @UNREALENGINE
解説 5/5
#クラスタ番号を元に親となるインスタンスを選択し、トランスフォームを追加
for j, pd in enumerate(pred):
instanced_components[pd].add_instance(list_transform[j])
#スタティックメッシュを割り当て
for k in range(num_clusters):
instanced_components[k].set_editor_property("StaticMesh",lu)
#最初に選択していたアクタを削除
for lsm in list_static_mesh_actors:
lsm.destroy_actor()
#UE4 | @UNREALENGINE
これでインスタンス化できたが・・・
再調整したい場合は元のバラバラな状態に戻す必要がある
#UE4 | @UNREALENGINE
実装例:インスタンスを個別のStaticMeshに変換
#UE4 | @UNREALENGINE
インスタンスを個別のStaticMeshに変換
#UE4 | @UNREALENGINE
コード
import unreal
import numpy as np
selected_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
for sa in selected_actors:
instanced_components = np.array([])
instanced_component = sa.get_components_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
for ic in instanced_components:
instance_transform = np.array([])
instance_count = ic.get_instance_count()
for j in range(instance_count):
instance_transform = np.append(instance_transform,ic.get_instance_transform(j,1))
spawned_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor,(0,0,0),(0,0,0))
spawned_actor.set_actor_transform(instance_transform[j],0,0)
smc = spawned_actor.get_component_by_class(unreal.StaticMeshComponent)
smc.set_editor_property("StaticMesh",ic.get_editor_property("StaticMesh"))
for sa in selected_actors:
sa.destroy_actor()
#UE4 | @UNREALENGINE
解説 1/3
#ライブラリをインポート
import unreal
import numpy as np
#選択したアクタを取得
selected_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
#UE4 | @UNREALENGINE
解説 1/3
#ライブラリをインポート
import unreal
import numpy as np
#選択したアクタを取得
selected_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
#UE4 | @UNREALENGINE
解説 1/3
#ライブラリをインポート
import unreal
import numpy as np
#選択したアクタを取得
selected_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
#UE4 | @UNREALENGINE
解説 2/3
#インスタンスコンポーネントを配列に入れる
for sa in selected_actors:
instanced_components = np.array([])
instanced_component = sa.get_components_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
#インスタンス数を取得
for ic in instanced_components:
instance_transform = np.array([])
instance_count = ic.get_instance_count()
#UE4 | @UNREALENGINE
解説 2/3
#インスタンスコンポーネントを配列に入れる
for sa in selected_actors:
instanced_components = np.array([])
instanced_component = sa.get_components_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
#インスタンス数を取得
for ic in instanced_components:
instance_transform = np.array([])
instance_count = ic.get_instance_count()
#UE4 | @UNREALENGINE
解説 2/3
#インスタンスコンポーネントを配列に入れる
for sa in selected_actors:
instanced_components = np.array([])
instanced_component = sa.get_components_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
#インスタンス数を取得
for ic in instanced_components:
instance_transform = np.array([])
instance_count = ic.get_instance_count()
#UE4 | @UNREALENGINE
解説 2/3
#インスタンスコンポーネントを配列に入れる
for sa in selected_actors:
instanced_components = np.array([])
instanced_component = sa.get_components_by_class(unreal.InstancedStaticMeshComponent)
instanced_components = np.append(instanced_components,instanced_component)
#インスタンス数を取得
for ic in instanced_components:
instance_transform = np.array([])
instance_count = ic.get_instance_count()
インスタンスがいくつあるか取得し、
その数分新たにアクタをスポーンしていく
#UE4 | @UNREALENGINE
解説 3/3
#インスタンス数の分だけトランスフォーム値を取得
for j in range(instance_count):
instance_transform = np.append(instance_transform,ic.get_instance_transform(j,1))
#スポーンしてトランスフォームを適用
spawned_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor,(0,0,0),(0,0,0))
spawned_actor.set_actor_transform(instance_transform[j],0,0)
smc = spawned_actor.get_component_by_class(unreal.StaticMeshComponent)
#スタティックメッシュを割り当て
smc.set_editor_property("StaticMesh",ic.get_editor_property("StaticMesh"))
#最初に選択していたアクタを削除
for sa in selected_actors:
sa.destroy_actor()
#UE4 | @UNREALENGINE
解説 3/3
#インスタンス数の分だけトランスフォーム値を取得
for j in range(instance_count):
instance_transform = np.append(instance_transform,ic.get_instance_transform(j,1))
#スポーンしてトランスフォームを適用
spawned_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor,(0,0,0),(0,0,0))
spawned_actor.set_actor_transform(instance_transform[j],0,0)
smc = spawned_actor.get_component_by_class(unreal.StaticMeshComponent)
#スタティックメッシュを割り当て
smc.set_editor_property("StaticMesh",ic.get_editor_property("StaticMesh"))
#最初に選択していたアクタを削除
for sa in selected_actors:
sa.destroy_actor()
#UE4 | @UNREALENGINE
解説 3/3
#インスタンス数の分だけトランスフォーム値を取得
for j in range(instance_count):
instance_transform = np.append(instance_transform,ic.get_instance_transform(j,1))
#スポーンしてトランスフォームを適用
spawned_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor,(0,0,0),(0,0,0))
spawned_actor.set_actor_transform(instance_transform[j],0,0)
#スタティックメッシュを割り当て
smc = spawned_actor.get_component_by_class(unreal.StaticMeshComponent)
smc.set_editor_property("StaticMesh",ic.get_editor_property("StaticMesh"))
#最初に選択していたアクタを削除
for sa in selected_actors:
sa.destroy_actor()
#UE4 | @UNREALENGINE
解説 3/3
#インスタンス数の分だけトランスフォーム値を取得
for j in range(instance_count):
instance_transform = np.append(instance_transform,ic.get_instance_transform(j,1))
#スポーンしてトランスフォームを適用
spawned_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor,(0,0,0),(0,0,0))
spawned_actor.set_actor_transform(instance_transform[j],0,0)
#スタティックメッシュを割り当て
smc = spawned_actor.get_component_by_class(unreal.StaticMeshComponent)
smc.set_editor_property("StaticMesh",ic.get_editor_property("StaticMesh"))
#最初に選択していたアクタを削除
for sa in selected_actors:
sa.destroy_actor()
#UE4 | @UNREALENGINE
解説 3/3
#インスタンス数の分だけトランスフォーム値を取得
for j in range(instance_count):
instance_transform = np.append(instance_transform,ic.get_instance_transform(j,1))
#スポーンしてトランスフォームを適用
spawned_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor,(0,0,0),(0,0,0))
spawned_actor.set_actor_transform(instance_transform[j],0,0)
#スタティックメッシュを割り当て
smc = spawned_actor.get_component_by_class(unreal.StaticMeshComponent)
smc.set_editor_property("StaticMesh",ic.get_editor_property("StaticMesh"))
#最初に選択していたアクタを削除
for sa in selected_actors:
sa.destroy_actor()
#UE4 | @UNREALENGINE
おまけ:UIを作る
#UE4 | @UNREALENGINE
おまけ:UIを作る
#UE4 | @UNREALENGINE
おまけ:UIを作る
特定のアクタを選択
#UE4 | @UNREALENGINE
おまけ:UIを作る
特定のアクタを選択
#UE4 | @UNREALENGINE
おまけ:UIを作る
#UE4 | @UNREALENGINE
実装例は後日Git Hubなどで公開予定
※あくまで一例として作ったものなので、より実用的にするには改良が必要
※公開した実装例の保守、サポートは致しません
#UE4 | @UNREALENGINE
参考
Unreal Python API リファレンス
https://api.unrealengine.com/INT/PythonAPI/
#UE4 | @UNREALENGINE
参考
Mclarenによる事例:ホワイトペーパー
Unreal Studio を使用した CAD データの準備および
リアルタイム ビジュアライゼーションでの自動化
https://cdn2.unrealengine.com/Unreal+Engine%2Fresources%2FMcLaren+W
hitepaper%2FADCaV-Whitepaper-JPN-V2-
f4fd0b09e8b279171149eef9220370e1b7495092.pdf
#UE4 | @UNREALENGINE
ご清聴ありがとうございました

Python / BlueprintによるUnreal Engineの自動化 / GTMF2019

Editor's Notes

  • #4 本日はこのような内容でお話しさせていただきます。
  • #5 まず最初に大規模開発における作業効率化・自動化の需要についてお話したいと思います。
  • #6 昨今ではコンテンツ開発の規模が大きくなってきており、それに伴って大量のアセットの管理、製作コストも上がって 手作業だけでは時間がかかりすぎたり、クオリティに影響が出る可能性なども考えられます。
  • #7 そのようなことが起きないように、大規模開発ではこういった単純作業をスクリプト化し自動、または半自動で行えるようにしていく必要が出てくると思います。 実際AAAタイトルのゲームやゲーム以外の分野でも、大規模開発になると単純作業の自動化は積極的に行われています。 もちろんUnreal Engineの事例も
  • #8 McLarenによるUnreal Engine内での自動化事例を軽く紹介させていただきたいと思います。
  • #9 McLarenではUnreal Engineを活用して車のデザインツールを開発しています。 様々な種類の車をデザインする過程で部品などのデータをUnreal Engineに複数回インポートする必要が出てきます。 インポート後にもフォルダ構成を整理したり、複数のパーツをマージしたりなど、更に作業が必要になる場合もあります。 数十個程度のパーツであれば手作業でもさほど苦労はしませんが、車のデータになると数千パーツになることも。
  • #10 そこでMcLarenではPythonや後程紹介するDatasmithを活用することによってインポートに必要な作業の効率化を図っています。 インポート後にデータの解析、テッセレーションやLODの作成、解析したメタデータを元にマテリアル割り当てやシーンへの配置。 これらを自動化してインポートワークフローを大幅に単純化しました。 このようにプロジェクトが大きくなるにつれ、大量のデータを一度に捌く必要が出てきますが、Unreal Engineにはこういった作業効率化に役立つツールが備わっています。
  • #11 Unreal Engineに備わっている効率化や自動化に役立つツールを紹介したいと思います。
  • #12 Bluetility Blueprint Utiltyのことなんですが Blueprint、Unreal Engineを触ったことのある方ならご存じかと思いますが、ノードベースのプログラミング機能です。 これによってプログラマがいなくてもゲームのようなロジックを組むことができます。 主にゲーム上で動作するロジックを作るのものなのですが、エディタ上での作業をスクリプト化することも可能です。
  • #13 Blueprintに限らず、エディタスクリプティングにはプラグインからEditor Scripting Utilitiesを有効にする必要があります。 これによってエディタ効率化に必要な関数やノードがいくつか公開され、Blueprintからもアクセスすることができるようになります。
  • #14 Pythonもエディタ上で動かすことができます。 Pythonバージョンは2.7になります。
  • #15 Unreal Pythonはプラグインになっていますのでプラグインからチェックを入れて有効にする必要があります。 これでUnreal Engine上でPythonを実行できるようになります。
  • #16 実行方法は複数ありまして、アウトプットログというログを表示したりコマンドを入力したりできるウィンドウで 左隅を押すとPythonモードに切り替えられますので、その状態で直接入力が可能です。
  • #17 また、用意しておいたスpyファイルのパスを直接指定することでも実行可能です。 頭にpyとつけてスペースを置いて、パスを指定することでスクリプトが実行されます。 こちらはPythonモードではなく、コマンドモードでないと動きませんのでご注意ください。
  • #18 起動時に実行するpyファイルを指定することも可能です。 Project Settings>Plugin>Python>Startup Scriptsにパスを入れることで、起動時に毎回実行されるようになります。
  • #19 次にEditor Utility Widgetを紹介したいと思います。 簡単に説明すると、Unreal Engine上でツールやエディタ拡張の作成ができる機能です。 UMGと呼ばれる機能を使うのですが、これは本来ゲーム中のUIを作成する機能なんですが、エディタ上で動作するツールのUIを簡単に作れるようになります。
  • #20 新しい機能なので使ったことのない方もいるかと思いますので少し説明させていただきます。 コンテンツブラウザで右クリックEditor Utilities > Editor Widgetで作成できます。
  • #21 エディタを開くとこんな画面になっています
  • #22 左側のメニューから、UIとして必要な機能をD&Dで好きな場所に置くことができます。 UIのデザインはすごく簡単にできます。
  • #23 そして、配置したボタンを押したときなどの処理をBlueprintで作成していきます。
  • #24 出来上がったらアセット右クリックからRun Editor Utility Widgetで作成したツールが立ち上がります。 こんな感じで簡単に独自のUIを持ったツールを作ることができるので、BlueprintやPythonと組み合わせて使うとより効率的に作業ができるようになるかと思います。
  • #25 Editor Utility Widgetに関しては弊社岡田がQiitaにて解説記事を書いています。 興味のある方はこちらも合わせてご覧ください。
  • #26 ではこれらを使って何ができるか、プロジェクトによって需要は様々かと思いますが 今回はあくまで一例として実装例をいくつか作ってきました。
  • #28 ポリゴンへの変換、マテリアルの変換やライトマップ用UV展開までを短時間で行うことができます
  • #31 数百パーツとか、数千パーツになると、無駄なパーツを一つ一つ探して削除なりマージ作業を何回も繰り返さないといけません。大半の人はやりたくないと思います。
  • #33 実装例として、Datasmithで小さいパーツを除外してインポートというのをBlueprintでやってみました。 こんなかんじのテストシーンをインポートしてみます。
  • #38 この段階、まだEngine上にインポートされていない段階で Datasmith SceneにBlueprintノードでアクセスして、いろいろな処理を行うことができます。
  • #39 Get All Mesh Actorsでシーン上のアクタを全取得し、
  • #40 そのアクタ一つ一つのバウンディングボックスの大きさを見ていきます。
  • #41 これで大きさを判断して
  • #44 今回は大きさを判定して削除、という処理になっていますが、削除ではなく一つのオブジェクトにマージする、とか 大きさで判定するのではなく、パーツの名前から判定するなど、プロジェクトの需要に合わせて色々なパターンが作れると思いますので、ぜひお試しください。
  • #45 次はEditor Utility Widgetの実装例になります。
  • #46 プレフィックスは先頭につける文字列、サフィックスは後ろにつける文字列
  • #48 Unreal Engineでの命名規則の一例がこちらのサイトに載っていますので参考にしてみてください。 Epicが出しているサンプルプロジェクトやマーケットプレイスのコンテンツの命名も参考になると思います。
  • #49 なぜ命名規則を決めないておかないといけないのか、これもやはりプロジェクトが大規模化するにつれて重要になっていくからです。
  • #51 そこで今回はEditor Utility Widgetを使って、アセットの種類を判別して、設定した命名規則に応じたリネーム処理を行う仕組みを作ってみました。 左側にくっついているのがEditor Utility Wdgetで作成したもので、種類毎に名前の頭に何をつけるかを設定してあります。 ちゃんと命名されてないアセットが並んでいますが、すべて選択して適用すると一気にリネーム処理が行われ、命名規則に準じた文字列が付けられます。
  • #52 UIの方は命名規則を設定するテキスト入力の部分と
  • #53 適用するボタン一つを置いてみました。 UIの方はシンプルな構成になっています。 このUIに対して、ボタンを押したらどういった処理をするかというところをBlueprintを使って作っていきます。
  • #54 Blueprintの方はこんな感じになっています。
  • #55 左からボタンを押したときのイベントがあって、ボタンがおされたら選択しているアセットを全部取得し、
  • #56 この部分でアセットの種類ごとにリネーム処理をしていきます。 Asset Renameというノードは新規作成した関数ノードで、この中に更に色々ノードが入っています。
  • #57 関数ノードの中身がこんな感じです。 左側の方から解説していくと
  • #58 さっき取得した選択状態のアセットリストをFilter by classというノードでフィルタリングします。 スタティックメッシュのみにしたり、マテリアルのみにしたりとか。
  • #59 それらに対して命名規則が正しいかどうかチェックして、正しければ何もしない。 正しくなければアセットの種類に合わせた文字列を頭に追加する。 こんな感じの実装になっています。 命名規則はアセット管理をする上で重要になってきます。 Editor Utility WidgetやBlueprintでチェックや修正を行うツールを作れば、より効率的にアセット管理ができるようになるかと思います。
  • #62 言葉で言ってもわかりにくいかもしれないので、Unreal Engineの画像で説明するとこんな感じです。 左側は同じオブジェクトが9個並んでいるので9回分のドローコールになりますが、右側はインスタンスとして9個並べていて、こちらはドローコールは1回分です。 こんな風に画面内にオブジェクトを描画しなければならないとき、オブジェクトが呼び出される回数が少ない方が処理負荷が低くなる可能性があります。
  • #63 Unreal Engineではインスタンスを作るためにInstanced Static Meshというものが用意されています。
  • #64 このInstanced Static Meshは、
  • #65 このInstanced Static Meshは、一つのスタティックメッシュと
  • #66 このInstanced Static Meshは、一つのスタティックメッシュと インスタンス数分の位置、回転、スケールを含むトランスフォームを持っているという構成になっていて この追加したトランスフォームの数分複製されるようになっています。
  • #67 ですが、すでに置かれているStatic Meshをインスタンスに変換しようとすると面倒で・・・
  • #68 これを変換したいアクタの数分行います。やりたくないです。
  • #69 実は変換をやってくれるツールはすでにありまして Merge Actorsというツールを使うことで選択アクタを変換することは可能です。
  • #70 ただし、あくまで選択したアクタを一つのインスタンスにしてくれるだけです。
  • #71 例えば・・・
  • #77 これを利用してUnreal Pythonでオブジェクトのインスタンス化を行います。 この例では2Dの座標ですが3D座標でも可能なので、Unreal Engine上でのアクタの座標から同じことをやってみます。
  • #78 外部ライブラリの話が出てきましたが、Unreal Pythonでも外部ライブラリを使うことができます。 PythonでライブラリをインストールするとPython\Lib以下にsite-packagesというフォルダに色々入ると思いますが、Engine以下にも同じような階層がありますのでこちらに丸ごとコピーします。
  • #79 実際にKmeansを使ってインスタンシングしてみた結果がこちらになります。 バラバラなオブジェクトに対して実行すると、似通った位置のアクタ同士でインスタンス化されます。 どんな感じの実装になっているか解説していきます。
  • #80 コードはこんな感じです。 小さくて申し訳ないですが、後日公開予定ですのでじっくり読みたい方や使ってみたい方は、公開次第見ていただければと思います。 こちらも重要な部分をかいつまんで解説していきます。
  • #81 後半はこんな感じです。
  • #83 まずはライブラリインポート Import unrealでunreal engine用のクラスや関数をインポートします。 配列の操作が多くなるので、配列を扱うのに便利なnumpyもインポート。 今回使うKmeansはSkicit learnというライブラリに含まれているので、そちらもインポートしています。
  • #84 次に選択しているアクタを取得。 取得してどうするかというと
  • #85 後程、位置や回転、何のメッシュが割り当たっているかなどの情報を取得します。 get_selected_level_actorsという関数を使います。
  • #86 ここは選択アクタに複数種類のメッシュがあったときの処理になります。
  • #92 ここの部分でクラスタリング用のアクタの位置を配列にしています。
  • #93 このget_actor_locationでアクタの位置を取得するのですが、返ってくるのはUnreal Vectorという型です。
  • #94 位置などの情報は入っているのですが、この状態ではKmeansでは使えません。 なので下のような扱いやすい配列にします。
  • #95 それをやっているのがここの部分です。
  • #96 ここでようやくK-meansによるクラスタリング
  • #97 ここでようやくK-meansによるクラスタリング N-clustersでクラスタ数を指定、
  • #98 ここでようやくK-meansによるクラスタリング N-clustersでクラスタ数を指定、 その横のfit_predictは配列のクラスタ番号を返す どのクラスタに配属されたか この値を基準にインスタンスを作っていきます
  • #100 ここでクラスタの数分、空のインスタンスをスポーンします。
  • #101 Spawn_actor_from_class()という関数でスポーン処理を行います。 スポーンする対象として指定しているBp_instanceは新規作成したBlueprintです。
  • #102 こんな感じでInstanced Static Meshを追加してあらかじめ用意しておく必要があります。
  • #103 ただスポーンしただけだと、インスタンスの位置情報やメッシュの割り当ても無しの空の状態です。 なのでここからインスタンスの位置情報やメッシュの割り当てを行っていきます。
  • #105 先ほどKmeansを使って出したクラスタ番号を元に、トランスフォームを追加します。
  • #106 エンジン上の画像でいうとこんな感じのことをやっています。スポーンした空のインスタンスに右のようにトランスフォームを追加していきます。
  • #107 こちらはメッシュの割り当てです。
  • #108 エンジン上の画像でいうとこんな感じです。
  • #109 これでインスタンス化の処理は終わったので最初に選択していたアクタをすべて削除して終わりです。 これでいい感じにインスタンス化してくれるスクリプトが出来上がりました。
  • #115 まずはライブラリインポート Unrealライブラリと、今回も配列の操作が多いのでnumpyを使いました
  • #116 選択したアクタを取得
  • #118 ここでインスタンスの数を取得します。
  • #122 エディタ上の画面でいうとこの部分 位置、回転、スケールをすべて取得します
  • #123 ここでStatic Mesh Actorをスポーンして 今取得したトランスフォームをひとつづつ適用していきます。
  • #124 スポーンしてトランスフォームを適用しただけで、まだ空の状態なのでスタティックメッシュを割り当てます。 これで変換処理は完了したので
  • #125 最後に、元あったインスタンスを削除します。 これでインスタンスとバラバラなスタティックメッシュを行き来しながら調整することができるようになりましたが、
  • #126 せっかくなのでUIを作って使い勝手を良くしてみます。
  • #127 UIはこんな感じです。 Break Instanceの方はインスタンスをすべて選択するボタンと、実行ボタン Make Instanceの方はスタティックメッシュをすべて選択するボタンと、実行ボタン で、それぞれ実行するpyスクリプトファイルのパスを入力する感じになってます。
  • #128 Blueprintの方は、こちらが特定のアクタを選択する処理。 ボタンが押されたらGet All Actors Of Classで特定のアクタだけ取得、Set Selected Level Actorsでそれらのアクタを選択させます。
  • #129 こちらはPythonを実行する処理。 ボタンが押されたらUIに入力されたテキストのパスを取得して、Excute Console Commandで実行。 こんな感じの実装になっています。 このようなちょっとした機能もサッと作れるのがEditor Utility Widgetのいいところですね。
  • #130 実際動作しているところです。 選択してインスタンス化し、また選択してバラバラに戻します。 こんな感じで、PythonとWditor Utility WdgetとBlueprintを組み合わせて使うことで、さらに可能性が広がると思います。 プロジェクトによってツールの需要は多種多様だと思うので、いろいろ試して使っていただければと思います。 実装例は以上になります。
  • #131 今日紹介させていただいた実装例は、後日Git Hubなどで公開予定です。 注意事項として、あくまで一例として作ったものなので、より実用的にするには改良が必要になるかと思います。 また、公開した実装例の保守、サポートは致しませんのでご了承ください。
  • #132 最後に参考資料を共有させていただきます。 Unrealの専用クラスや関数はこちらにまとまっています。 数が膨大ですが、検索機能もあるのでUnreal Pythonを使う際はこちらを利用することをおすすめします。
  • #133 最初に紹介したMclarenの資料はホワイトペーパーとして無料で公開されております。興味のある方はご覧ください。