Open1

Unity Python Scripting導入編(Windows向け)

kajitaj63b3kajitaj63b3

Python Scriptingパッケージ

Unity Registryに含まれるPython Scriptingパッケージを使ってUnity開発にPythonを使用できる。
UnityAPIもPythonから実行でき、DCCツールなどのUnity外部ツールとの連携や既存のPython資産の力を低コストにUnity開発に持ち込めるメリットがある。ランタイムでは動作せずエディタ処理の為のPythonという位置付けですが、Pythonで書いた処理をC#に変換して.csで出力する機能もあるので工夫次第でランタイムスクリプトの開発にも役立つでしょう。

パッケージはこれ。
https://docs.unity3d.com/Packages/com.unity.scripting.python@6.0/manual/index.html
PackageManagerからimportするだけ。

パッケージをインポートすると以下に諸々追加される。

  • Library/PythonImstall/python.exeや色々が追加される。
  • Library/PackageCache/com.unity.scripting.python/にも色々追加される。

パッケージに依存せずに元々UnityAPIのDLLはC:/UnityEditors/[unity version]/Editor/...に置かれている。PythonからUnityAPIを実行する場合もこれを参照しているっぽい。
以下のコードで、パッケージに含まれるPython実行環境が動作に必要なものをどこからかき集めてきているかを調べるとこのパスが含まれている事がわかる。パッケージに含まれるUnityPythonConsoleで実行してみるとこうなる。

import sys
import pprint

# sys.pathはモジュールを検索するパスのリストらしい
pprint.pprint(sys.path)

# 結果
['D:/UnityProjects/[projectName]/Library/PackageCache/com.unity.scripting.python@6.0.1/Python~/site-packages',
 'D:/UnityProjects/[projectName]/Library/ScriptAssemblies',
 'D:/UnityProjects/[projectName]/Assets/site-packages',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\python39.zip',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\DLLs',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\lib',
 'C:\\UnityEditors\\2022.3.22f1\\Editor',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\lib\\site-packages',
 'C:\\UnityEditors\\2022.3.22f1\\Editor\\Data\\MonoBleedingEdge\\lib\\mono\\unityjit-win32']

sys.pathについてのメモ
https://docs.python.org/ja/3/library/sys.html#sys.path

モジュールを検索するパスを示す文字列のリスト。 PYTHONPATH 環境変数と、インストール時に指定したデフォルトパスで初期化されます。

IDEでコーディング

パッケージを入れるとWindow/General/PythonConsoleからUnity上でPythonを書いて実行てきるエディタとコンソールが追加される。前述のコードの出力結果もこのエディタで実行したもの。しかし、コーディング環境としては結構厳しい。
(TODO:画像差し替え)

当然VSCodeやPyCharmやCursorでコーディングしたい。できれば実行までしたい。
というわけで、そういった環境構築に関する試行錯誤を書き残す。
Pythonの知識が足らず情報も少なく苦労したので導入についてのメモを残す。

インタプリタの指定

IDE側でもpythonインタプリターは、パッケージの実行環境と揃える為にLibrary/PythonImstall/python.exeを指定しておきます。別にそろえなくても良さそうではあるけど念のため。
VSCodeとCursorならsettings.jsonに"python.defaultInterpreterPath": "D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\python.exe",を追加する。
PyCharmならプロジェクトを作成する時にインタプリタを指定する項目があるのでそこで指定する。
どちらの環境でもウィンドウの右下のPythonインタプリタのバージョンが表示されている部分をクリック数れば後からでも変えられるのでよしなに設定する。

モジュールのパスの追加

インタプリタを揃えた時点でsys.pathを確認してみると結果は以下の通り。

import sys
import pprint

pprint.pprint(sys.path)

# Unity上のエディタの結果
['D:/UnityProjects/[projectName]/Library/PackageCache/com.unity.scripting.python@6.0.1/Python~/site-packages',
 'D:/UnityProjects/[projectName]/Library/ScriptAssemblies',
 'D:/UnityProjects/[projectName]/Assets/site-packages',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\python39.zip',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\DLLs',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\lib',
 'C:\\UnityEditors\\2022.3.22f1\\Editor',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\lib\\site-packages',
 'C:\\UnityEditors\\2022.3.22f1\\Editor\\Data\\MonoBleedingEdge\\lib\\mono\\unityjit-win32']

# IDE上の結果
['d:\\UnityProjects\\[projectName]\\Assets\\Genera\\Scripts',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\python39.zip',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\DLLs',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\lib',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall',
 'D:\\UnityProjects\\[projectName]\\Library\\PythonInstall\\lib\\site-packages']

# 不足しているパスだけ抜き出すと以下の5つ
['D:/UnityProjects/[projectName]/Library/PackageCache/com.unity.scripting.python@6.0.1/Python~/site-packages',
 'D:/UnityProjects/[projectName]/Library/ScriptAssemblies',
 'D:/UnityProjects/[projectName]/Assets/site-packages',
 'C:\\UnityEditors\\2022.3.22f1\\Editor',
 'C:\\UnityEditors\\2022.3.22f1\\Editor\\Data\\MonoBleedingEdge\\lib\\mono\\unityjit-win32']

不足分の5つを各IDE上で追加します。
VSCodeとCursorの場合はSetting.jsonに追記する。

追記した状態を載せる

PyCharmではGUIから追加します。
画面右下のボタン>Interperter Settings>InterpreterのプルダウンのShowAll>左上のフォルダのアイコンのボタン>+ボタンからパスを追加


ここまでで、最低限書ける状態になったと思います。

入力補完など

通常のPythonモジュールであればVSCodeでちょっと設定すれば入力補完が可能のようですが、UnityAPIはDLLになっていて同じ手順ではできないようです。モジュールもmoduleクラスではなくCLR.ModuleObjectクラスになっていました。前述のsys.pathのリストを読むように設定しただけでは、型の情報が必要な機能は諸々使えないことになります。
こういった場合スタブファイルという型の情報だけをまとめたファイルを生成することで、入力補完や型検査などを実現する事が可能らしいです。しかし、Unityはこのスタブファイルを用意してくれているわけではないです。この問題はdiscussionsでも投稿されているが反応はなく期待薄に見えます。
https://discussions.unity.com/t/stub-files-for-python-scripting/917879

スタブファイルを生成することで解決しますが、既に生成したスタブファイルをgithubに上げているのでこれをcloneして前述のモジュールのパスの追加と同じように追加すればokです。
この場合スタブ生成の手順はスキップして問題ありません。clone後にスタブファイルをIDEで参照の節から読めばok。
https://github.com/HarukaKajita/UnityEngine_stubs

スタブ生成

ということで自分でスタブファイルを生成して解決します。
スタブファイルの生成に使用した仕組みはこちら。
https://github.com/MHDante/pythonnet-stub-generator
スタブ生成の仕組みはいくつかあるようで調べつつ雰囲気で選びました。他にもっと良い仕組みがあるのかもしれないですが、細かい判断は出来なかったのでアドレスなどあれば教えて下さい。

リポジトリのREADMEのUsageにあるコマンドはこれ

dotnet tool install --global pythonnetstubgenerator.tool
GeneratePythonNetStubs --dest-path="../py_project/typings" --target-dlls="dll_folder/MyLib1.dll;other_folder/MyLib2.dll"

.NET上でリポジトリのツールが公開されていて、それをインストールしてスタブファイルの出力先と基になる.dllファイルをまとめて指定してコマンドを叩けば良さそう。このサンプルコマンドでは.dll;区切りで記述してますがソースコードをみると,区切りで処理されているので,で実行してみます。

前準備として.NETと.NET frameworkが必要です。なければインストールする。分からなければとりあえずコマンド叩いてエラーが出たらインストールすればok。Windows以外の環境だと.NET frameworkは必要なかったり何か別のが必要だったりするかも。未検証。
.NETのインストール手順
https://dotnet.microsoft.com/ja-jp/learn/dotnet/hello-world-tutorial/install
.NET framework
https://dotnet.microsoft.com/ja-jp/download/dotnet-framework

準備ができたら、コマンドプロンプトでスタブ生成のツールをインストール

>dotnet tool install --global pythonnetstubgenerator.tool

次にスタブ生成をしますがDLLはUnityのバージョンごとに別ディレクトリにあるので、生成したいバージョンのディレクトリに移動します。

>cd \UnityEditors\2022.3.22f1\Editor\Data\Managed

出力先を指定してスタブ生成。

GeneratePythonNetStubs --dest-path="C:/Users/kajit/Desktop/stubs/2022.3.22f1/" --target-dlls="UnityEngine.dll,UnityEditor.dll"

ディレクトリには沢山.dllが存在していてどこまで生成対象にすれば良いのか分からなかったので、とりあえずUnityEngine.dllUnityEditor.dllで実行すれば問題なさそうでした。
生成結果はこんな感じで各フォルダに__init__.pyiファイルがありのこ.pyiファイルがスタブファイルです。

よしなにフォルダごと移動しても問題ないのでどこかにUnityのバージョン違いとかもまとめておいておいて、固定でこのパスをIDEに読ませるようにしておけば今後が楽です。オフィスの環境とかであればNASにおいても良いかもしれない。gitで管理しつつ各PCで揃えたいならリポジトリに含めるか、スタブ用のリポジトリにまとめておいてgitsubmoduleで各Unityプロジェクトに追加するとかが良さそう。

スタブファイルをIDEで参照

スタブファイルを出力したフォルダのパスをmoduleのパスを追加したのと同じ手順で追加すれば入力補完とか諸々が機能するようになります。

IDEから実行したい

ここまででコーディングはいい感じ。
IDE上でUnityAPIを実行するとエラーが発生する。AttributeError: module 'UnityEngine' has no attribute 'GameObject'とか。
UnityPythonConsoleではエラーなく動作する。
この違いはどこからきているか?

PyCharmではExternalToolsからUnityにコードをファイルごと投げる感じで実行するとか。

PythonRunner.Excure(string code)を外部から呼べれば良い。
Pythonから直接C#を呼べるのか?.dllを読みこんでコールすればいける?
コールするだけの存在を.exeや.batで用意してIDEのカスタムなRunでよぶ?

Pytho for .NET(pythonnet)

When you access the Unity API in Python it is not actual bindings of our C# API. This is possible thanks to Python for .NET that allows you to call the functionnalities of any loaded C# assemblies in Python dirrectly. This can sometimes cause unexpected issues due to Python for .NET limitations regarding Unity's usage of C#.
PythonでUnity APIにアクセスするとき、それは私たちのC# APIの実際のバインディングではありません。これはPython for .NETのおかげで、ロードされたC#アセンブリの機能をPythonで直接呼び出すことができます。これは、UnityのC#の使用に関するPython for .NETの制限により、予期しない問題を引き起こすことがあります。

You can't use a virtual environment manager like venv or conda.
venvやcondaのような仮想環境は使えない。