16
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[UE4]Pythonによるインスタンシング

Last updated at Posted at 2019-07-17

GTMF2019にてPython / BlueprintによるUnreal Engineの自動化という内容で講演いたしました。
本記事はその中で紹介した実装例、Pythonによるインスタンシングのサンプル公開用記事です。

発表内容については以下のスライドをご覧ください。
[GTMF2019] Python / BlueprintによるUnreal Engineの自動化
https://www.slideshare.net/EpicGamesJapan/gtmf2019-python-blueprintunreal-engine/1

Unreal EngineではInstanced Static Meshを使うことでインスタンスを作成することができます。
Merge Actorsというツールを使うことで選択アクタを一気にインスタンス化することもできますが、いずれにしても大量のアクタをグループ分けしつつインスタンス化していくのは非常に手間です。

そこで、Pythonの外部ライブラリに含まれるK-meansを使ってクラスタリング&インスタンシングをしてみました。

##事前準備
###Python側
Python2.7が入っていない場合は公式サイトからダウンロード、インストールしておいてください。
インストール時、Add python.exe to Pathを有効にしておきます。
2019-07-16_22h56_50.png

Python外部ライブラリのインストールはコマンドプロンプトでpipを使って行います。
ややこしいですが、まずはそのpipをインストールします。

コマンドプロンプトを起動して"python -m pip install pyinstaller"と入力
2019-07-16_23h01_59.png

色々ログが流れてインストールされたようなメッセージが出ればOKです。

これで外部ライブラリをインストールする準備ができたので、今度は以下のように入力してscikit learnをインストールします。

"python -m pip install scikit-learn"

インストールされたライブラリはC:\Python2.7\lib\site-packagesに入ります。
同じ階層がEngine\Binaries\ThirdParty\Python\Win64\Lib内にもあるので、site-packagesフォルダを丸ごとコピーしてください。
これで、Unreal Pythonで外部ライブラリを使えるようになります。

###Unreal Engine側
検証バージョンはUE4.22です。

Python Editor Script Pluginを有効にしておく必要があります。
2019-07-04_18h47_10.png

次にインスタンスとして使うアクタを用意しておきます。
Blueprintを新規作成。
2019-07-16_23h12_04.png
Actorクラスを親にします。
2019-07-16_23h12_35.png

名前はとりあえずBP_Instanceにしてください。
作成したらBlueprintを開いて、Add ComponentからInstanced Static Meshを追加しておきます。
2019-07-16_23h15_35.png

##K-meansによるインスタンシング
こちらが実際に動作している結果になります。
Cluster.gif

コードはこちら

qiita.py
import unreal
import numpy as np
import sklearn
from sklearn.cluster import KMeans

#用意したインスタンス用Blueprintクラスをロード
bp_instance = unreal.EditorAssetLibrary.load_blueprint_class('/Game/BP_Instance.BP_Instance')

#選択したStatic Mesh Actorを取得
list_actors = unreal.EditorLevelLibrary.get_selected_level_actors()
list_static_mesh_actors = unreal.EditorFilterLibrary.by_class(list_actors,unreal.StaticMeshActor)

list_unique = np.array([])

#選択したアクタに含まれるStatic Meshの種類を調べる
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]])

#クラスタリング用の位置情報と、インスタンスに追加するトランスフォームの配列を作成
    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)

    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])

#インスタンスにStatic Meshを割り当て
    for k in range(num_clusters):
        instanced_components[k].set_editor_property("StaticMesh",lu)

#最初に選択したアクタを削除
for lsm in list_static_mesh_actors:
    lsm.destroy_actor()

※Scikit-learnライブラリがインストールされていない場合は動作しません。
※実行時にnum_clusters以上の数のアクタを選択していないと動作しません。

6行目、load_blueprint_class('/Game/BP_Instance.BP_Instance')の()内には事前準備したBlueprintのパスを入れます。

qiita.py
bp_instance = unreal.EditorAssetLibrary.load_blueprint_class('/Game/BP_Instance.BP_Instance')

アセット右クリックからCopy Referenceでコピーして貼り付けてください。
2019-07-10_10h49_47.png

##インスタンスを個別のStatic Meshに変換
一度インスタンス化してしまうと再調整ができないので、インスタンスからバラバラな状態に戻すスクリプトも作ってみました。
Break.gif

コードはこちら。

qiita.py
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()

#インスタンスの数分Static Mesh Actorをスポーンし、トランスフォーム割り当て、Static Mesh割り当てを繰り返す
        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()

※大量のインスタンスが含まれる場合、処理時間がかなり長くなる場合があります。

こちらのスクリプトはフォリッジに対しても実行可能です。

##参考
Python を使用したエディタのスクリプティング
https://api.unrealengine.com/JPN/Engine/Editor/ScriptingAndAutomation/Python/index.html

Unreal Python API Documentation
https://api.unrealengine.com/INT/PythonAPI/

16
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?