【UE4】Pythonつかって、MaterialInstanceアセットのパラメーターとかいじってみた-前編 【★★~★★★】

※この記事で使用しているUnrealのVersionは04.22.1 です。

※記事が長くなってしまったので、2ページに分けて投稿します。@ページ1

後編はこちら

※この記事のサンプルプロジェクトは以下URLにアップされています。
サンプルプロジェクト

前置き

1ケ月前のUNREAL FEST WEST 2019で、「PythonとHoudiniは勉強しよう!」と思い立ち、まずは手軽にPythonから勉強を始めました。
(が、公私ともにバタついており、あまり思うようにはできていません……)

そもそも文字でコードを書いたことが皆無なので、こちらの記事にあるコードはプログラマさんからすれば割とトンチンカンなものに見えるかと思いますが、備忘と戒め用のものですので、あらかじめご了承ください。

また、環境整備系は、↓の公式ドキュメントから抜き出しているようなものなので、詳しくは以下ドキュメントを参照してください。

公式ドキュメント
Pythonを使用したエディタのスクリプト作成

また、UnrealPythonで使えるUnrealのAPIは以下を参考にしてください。
Unreal Editor Python APIリファレンス

Pythonを使える環境にしてみる レベル【★★】

まずは、Pythonを使えるように、プラグインを追加します。

UnrealEditorを開き、「Edit」→「Plugin」を選択します。

「Scripting」カテゴリーの中の「Python Editor Scripting Plugin」にチェックを入れます。
入れたらエディターを再起動しましょう。

エディタ左上の「File」内に「Python」という項目ができていれば、問題なくプラグインが入っております。

基本的にはこれでPythonを実行する環境は整いました。

コマンド実行方法 レベル【★★】

その①
↑の項目にある、「Execute Python Script」 を選択すると、Pythonファイルを選択するウィンドウが開き、選択することで、そのPythonファイルを実行してくれます。

その②
一度起動したPythonは↑の項目の「Recent Python Script」に一覧で登録されているので、そこから選択することでも実行可能です。

その
エディター左上の「Edit」→「Project Settings」の中にある、「Python」という項目の中の、「Startup Script」という項目の配列に、Pythonファイルのパスを登録しておくことで、エディターが起動したときに自動でこのPythonを実行してくれます。

その
「Output Log」内のコマンドで「py “Pythonファイルのパス”」 (例:py “C:\KA_PythonMI\Content\Python\EditMaterialScalarParameter.py”) と入力することでも、実行できます。

さらに、Output Log内の「Cmd」という部分をクリックすると、「Python」という項目があります。

これを選択することで、ファイルを指定して実行するのではなく、Pythonコマンドを入力して実行することができます。

※自作したPythonファイルの中の特定の関数を実行する際は、
「Edit」→「Project Settings」の中にある、「Python」 項目内の「Additional Paths」に、呼び出したい関数などが記載されているPythonファイルのフォルダパスを追加し、一度エディタを再起動することで、Pythonコマンドの「import」から、Pythonファイルをimportすることができる。

尚、一度importしたら、エディタを閉じるまではimportしなくても、関数などを呼び出すことが可能のようです。

※ちなみにOutput Logの「Cmd」時の「py [コマンド]」で「[コマンド]」にPythonコマンドを入力することでも実行可能です。

注意点

一度インポートした後に、Pythonファイルを書き換えても、その変更は適応されません。
関数を実行する前に「py reload (~~~)」というコマンドを打つことで、編集が適応されます。

※importやreloadをする際に「LogPython: Error: SyntaxError: Non-ASCII character ~~~」というログが出る場合には、コメントなどで使われている「全角文字」を消してください。
よくわかりませんが、全角文字があるとこのエラーが出るようです。

—————————————————————————————————————————–

2018/06/13追記:
どうやら文頭に
「# coding: utf-82018/06/13追記:」
と記載しておくことで、コメント・文字列への日本語入力が可能なようでした。
無知ですみませんでした…orz

—————————————————————————————————————————–

ではここからは、Pythonを使った処理の例を紹介していきます。

Python自動化例:MaterialInstanceのパラメーターをいじくる-前半 【★★★】

まずは、適当にマテリアルを作成します。

マテリアルを開いて、パラメーターノードを追加していきます。
※今回はScalarParameterをいじるので、適当にパラメーターをいくつか作りました。

出来ましたらいったん閉じて、これを親にしたMaterialInstanceを作ります。
(マテリアルを右クリック→Create Material Instanceを選択)

作成できましたら、Unreal側の準備は完了です。

いよいよPythonを書いていきます。

が、その前に、先ほど冒頭の「その4」で説明した、Pythonファイル内の関数を実行するための準備をしましょう。

プロジェクトフォルダ以下の適当な位置に、Pythonファイルを保存する場所を作ります。
(今回はわかりやすくするためにContent/にPythonフォルダを作りました)

作りましたら「Project Settings」内の「Python」カテゴリー→ 「Additional Paths」 にそのパスを登録しておきましょう。(その後エディタ再起動)

できたら、フォルダの中に.pyという拡張子のファイルを作成しましょう。
編集はメモ帳などでもできますが、「Idle」などのソフトを使うと見やすいです

では、コードを書いていきます。
※見ずらいコードかと思いますが、何卒温かい目で見てください。
コメント付きファイルはサンプルプロジェクトの「/Content/Python/Commented」内にあります。ご自由にご覧ください。

EditMaterialScalarParameter.py

# EditMaterialScalarParameter.py
#=======================================================================================================#
#ContentBrowserで選択しているMaterialInstanceの「Parameter.py」で指定したScalarParameterの値を上書きする#
#=======================================================================================================#
import unreal

#---------------------------------#
#GlobalEditorUtilityWidgetのクラス#
#---------------------------------#
@unreal.uclass()
class GEditUtil(unreal.GlobalEditorUtilityBase):
    pass

#------------------------------------------------------------------------------#
#Dictで作られたパラメーターの一覧から代入するScalarParameterValue型の配列を作成#
#------------------------------------------------------------------------------#

def InitSParamList(ExistParam,PND):                                                             
    
    SParamList = []                                                                             #配列を生成            
    PNDKeys = PND.keys()                                                                        #指定したDictのパラメーターの名前の配列を生成
    
    i = 0
    for Key in PNDKeys:
        
        SParamList.append(unreal.ScalarParameterValue())                                        #配列にScalarParameterValue型の要素を1つ追加
        SParamList[i].parameter_value = PND[Key]                                                #値を代入
        SParamList[i].parameter_info.name = Key                                                 #名前を代入
        i = i + 1
        
#編集するパラメーター以外のパラメーターの中にEdit状態のものがあれば、それも配列に追加(追加しないと編集がなかったことになる)

    for x in ExistParam:
        if str(x.parameter_info.name) not in PNDKeys:                                           #Overrideにチェックが入っているか?(存在するか)
            SParamList.append(x)                                                                #配列に要素を追加
        
    return SParamList                                                                           #ScalarParameter型の配列を返す

#---------------------------#
#ScalarParameterの値を上書く#
#---------------------------#

def SetSelectedMIScalarParameter(PND):                                                          #引数の型はDict配列 例:({"Color_R":0.0,"Color_G":0.0})#
    
    Util = GEditUtil()                                                                          #GlobalEditorUtilを生成#                                        
    AssetList = Util.get_selected_assets()                                                      #ContentBrowserで選択しているアセットの一覧#                           

#選択しているアセットをFor分で回して、MaterialInstanceだったら、指定したScalarParameterを指定した値にする

    for Asset in AssetList :                                                                    
        AssetName = Asset.get_name()                                                            
        
        if type(Asset) != unreal.MaterialInstanceConstant:                                      #MaterialInstance以外ははじく(正確にはMaterialInstanceConstant)
            print ("Error:Selected [" + AssetName + "] is Not MaterialInstanceAsset.")
            continue

        MI = unreal.MaterialInstanceConstant.cast(Asset)                                        #MaterialInstanceConstantにキャスト#                
        ExistParam = MI.get_editor_property("scalar_parameter_values")                          #MaterialInstanceが持っているScalarParameterをすべて取得#    



        MI.set_editor_property("scalar_parameter_values",InitSParamList(ExistParam,PND))        #指定したScalarParameterの値を指定した値にする
        print ("Edit ScalarParameterValue that " + AssetName + " has.")

※内部的な仕様になりますが、ScalarParameterなどのパラメーターは、構造体の配列で管理されているようなので、UnrealAPIから構造体の型を拝借して、そこに値を入れていっています。

※「Global Editor Utility」クラスの機能を使って、コンテンツブラウザで選択しているアセットを取得しています。

※また、Get~~~Parameters() で帰ってくる値は、Materialが持っているすべてのParameterではなく、Overrideフラグにチェックが入っているParameteたちが返ってくるようです。
なので、本当は親マテリアルが持っているパラメーター一覧をすべて持ってきて、そこにないパラメーターを上書こうとしたらエラーを吐くというのもやりたかったのですが、一覧はネイティブでMaterialExpressionから取得しているっぽく、その部分が公開されていなかったのでできませんでした…orz

追記:

——————————————————————————————————————————
「Material Editing Library」というクラスに、「set_material_instance_scalar_parameter_value」という関数があって、返り値がboolなので、「これ使えばいいじゃん!」と思ったのですが、どうやらこのboolは何をしてもFalseが返ってくるようでした。…orz

ですが、こちらの機能を使った方が、スクリプトが簡素になったので、その機能を使った場合のコードも上げます。

EditMaterialScalarParameter_Instant.py

# EditMaterialScalarParameter.py
#=======================================================================================================#
#ContentBrowserで選択しているMaterialInstanceの「Parameter.py」で指定したScalarParameterの値を上書きする#
#=======================================================================================================#
import unreal

#---------------------------#
#GlobalEditorUtilityのクラス#
#---------------------------#
@unreal.uclass()
class GEditUtil(unreal.GlobalEditorUtilityBase):
    pass

@unreal.uclass()
class MatEditLib(unreal.MaterialEditingLibrary):
    pass

#------------------------------------------------------------------------------#
#Dictで作られたパラメーターの一覧から代入するScalarParameterValue型の配列を作成#
#------------------------------------------------------------------------------#

def SetScalarParameter(PND,MI):                                                             

    MEL = MatEditLib()                                                                          #指定したDictのパラメーターの名前の配列を生成
    PNDKeys = PND.keys()                                                                        
    
    for Key in PNDKeys:

        RetVal = MEL.set_material_instance_scalar_parameter_value(MI,unreal.Name(Key),PND[Key]) #指定したScalarParameterの値を指定した値にする

#RetValがFalseなら編集できなかったというログはいてるけど、実際は絶対Falseなので、これ不要
'''        
        if RetVal == False:
            print("Failed 'set_material_instance_scalar_parameter_value' MI = '"  + MI.get_name() + "' ParamName = '" + Key + "'")
'''
#---------------------------#
#ScalarParameterの値を上書く#
#---------------------------#

def SetSelectedMIScalarParameter(PND):                                                          #引数の型はDict配列 例:({"Color_R":0.0,"Color_G":0.0})#                                                      
    
    Util = GEditUtil()                                                                          #GlobalEditorUtilを生成#                                                                          
    AssetList = Util.get_selected_assets()                                                      #ContentBrowserで選択しているアセットの一覧#                                                                              

#選択しているアセットをFor分で回して、MaterialInstanceだったら、指定したScalarParameterを指定した値にする

    for Asset in AssetList :                                                                    
        AssetName = Asset.get_name()                                                            
        
        if type(Asset) != unreal.MaterialInstanceConstant:                                      #MaterialInstance以外ははじく(正確にはMaterialInstanceConstant)                                     
            print ("Error:Selected [" + AssetName + "] is Not MaterialInstanceAsset.")
            continue

        MI = unreal.MaterialInstanceConstant.cast(Asset)                                        #MaterialInstanceConstantにキャスト#       

        SetScalarParameter(PND,MI)
        
        print ("Edit ScalarParameterValue that " + AssetName + " has.")


——————————————————————————————————————————

※ちなみに、任意のパラメーターのOverrideのフラグを外すには、「get_editor_property」から「scalar_parameter_values」で返ってきたScalarParameter配列から任意のものをRemoveし、「set_editor_property」の「scalar_parameter_values」でそれを入れてやると、チェックが外れます。
追記:または、「Material Editing Library」の「clear_all_material_instance_parameters」を使えば、パラメーターのOverrideフラグを一括でリセットすることもできます。

ひとまず、このコードを書いたPythonファイルを、先ほど作成したフォルダに入れておきます。

Unrealエディターに戻ります。

先ほど作成したマテリアルインスタンスをコンテンツブラウザ上であらかじめ選択状態にしておきます

そして、冒頭の「その4」で説明しました、Output Logから直接Pythonコマンドを実行する方法で、コマンドを入力します。

まずはファイルをインポートするために「import EditMaterialScalarParameter」
(EditMaterialScalarParameterは自分で作成したファイル名)を入力し実行
実行結果↓

次に、パラメーター変更の関数を実行します。
「EditMaterialScalarParameter.SetSelectedMIScalarParameter ({“Color_R”:1.0,”Color_G”:1.0})」
実行結果↓

選択しているマテリアルインスタンスのパラメーターが任意の値に変更されていれば成功です!

実行前

実行後

ひとまずここまでで前半を終わります。
後編では、Blutilityを使ってエディター内でより使いやすくしてみたいと思います。

後編はこちら

※この記事のサンプルプロジェクトは以下URLにアップされています。
サンプルプロジェクト