UnityとPythonを連携する【Python.NET】
はじめに
Python.NETを利用するとC#からPythonの関数を呼び出したり、逆にPythonからC#のメソッドを呼び出したりできるのでNumpyなどの資産をそのまま利用できます。
Unityからも問題なく使えるのですがアプリ配布先にPythonがインストールされていないと動きません。
そこでWindows限定ですがPython Embeddable PackageをStreamingAssetsフォルダに入れて配布先の環境に依存せずに実行できるプロジェクトを作ってみました。
サンプルだけを見たい方はこちらからご覧ください。
余談ですがPython Scriptingでも内部でPython.NETが使われていますがこちらはエディタ専用なのでランタイムから利用できません。
開発環境
- Unity2022.3.20f1
- NuGetForUnity 3.1.0
- Python.NET 3.0.3
- Python Embeddable Package 3.11.3
Pythonの導入
Unityプロジェクトを作ったら以下のフォルダにPython Embeddable Packageのzipを展開したフォルダをコピーします。
Assets
+- StreamingAssets
+- python-3.11.3-embed-amd64
pipの導入
Python Embeddable Packageはpipが入っていないので個別に導入する必要があります。
デフォルトでは実行できないのですが python311._pth
ファイルの import site
のコメントアウトを削除すると実行できるようになります。
変更前
python311.zip
.
# Uncomment to run site.main() automatically
#import site
変更後
python311.zip
.
# Uncomment to run site.main() automatically
import site
次にget-pip.pyを使ってインストールします。
cd Assets\StreamingAssets\python-3.11.3-embed-amd64
curl -L https://bootstrap.pypa.io/get-pip.py | .\python
pip list
コマンドでインストールできているか確認します。
cd Assets\StreamingAssets\python-3.11.3-embed-amd64
.\Scripts\pip list
私の環境では以下が出力されました。
Package Version
---------- -------
pip 23.0.1
setuptools 67.6.1
wheel 0.40.0
Pythonのライブラリをインストール
pywin32
のように sys.path
を追加するライブラリがあるので、ここで必要なライブラリをインストールします。
PYTHONPATHの確認
C#側でPYTHONPATHを初期設定するのでsys.pathを確認します。
cd Assets\StreamingAssets\python-3.11.3-embed-amd64
./python -c "import sys;print('\n'.join(sys.path))"
私の環境では F:\UnityProjects\Unity-PythonNet
へUnityプロジェクトを作って以下が出力されました。
F:\UnityProjects\Unity-PythonNet\Assets\StreamingAssets\python-3.11.3-embed-amd64\python311.zip
F:\UnityProjects\Unity-PythonNet\Assets\StreamingAssets\python-3.11.3-embed-amd64
F:\UnityProjects\Unity-PythonNet\Assets\StreamingAssets\python-3.11.3-embed-amd64\Lib\site-packages
Python.NETの導入
Python.NETのDLLはNuGetで提供されていますがUnityでは直接利用できないので以下のいずれかの方法で導入します。
UnityNugetを利用する
UnityNuGet はnugetパッケージの一部をパッケージマネージャとして利用できるようにScoped Registryを提供しています。そのためProject Settings > Package Managerを開き以下の設定を追加します。
name: Unity NuGet
URL: https://unitynuget-registry.azurewebsites.net
Scope(s): org.nuget
次にパッケージマネージャーを開き、マイレジストリ > Unity NuGetを開き、pythonnet
をインストールします。検索欄に python
を入力すると探しやすいです。
NuGet For Unityを利用する
NuGet For Unity はUnityからnugetを利用できるエディタ拡張です。
まずはReleases のunitypackageもしくはOpenUPM経由でupmからインストールできます。
- インストールしたらUnityのメニューの
NuGet > Manage NuGet Package
からNuGet画面を開き、pythonnet
でSearch
ボタンを押します。 - 先頭に
pythonnet
が表示されるのでリスト内右上のInstall
ボタンを押します。
- インストールが完了すると依存ライブラリも一緒に入るのですが不要なので
Installed
タブに移動してpythonnet
以外をアンインストールします。
nugetパッケージを展開して利用する
nugetパッケージはzipとして展開する事ができます。そのためpythonnetの Download package
からnugetパッケージをダウンロードしてzipとして展開します。すると lib/netstandard2.0
フォルダに Python.Runtime.dll
が含まれているのでUnityプロジェクトの Assets/Plugins
フォルダへコピーします。
初期化スクリプト
以下のような初期化スクリプトを用意しました。
それぞれ以下を想定および設定しています。
- アプリ起動時にPython.NETを初期化してアプリ終了時に停止
- Pythonスクリプトは
Assets/StreamingAssets/myproject
に入れる - 環境変数PATHは.exeと.dllがあるパスを追加
- 環境変数DYLD_LIBRARY_PATHはpipのdllがあるパスを設定
- 環境変数PYTHONNET_PYDLLはpython311.dllの絶対パスを設定
- エディタから実行する時だけ環境変数PYTHONDONTWRITEBYTECODEを設定して.pyc更新を抑制
- PythonEngine.PythonHomeはpythonを導入した絶対パスを設定
- PythonEngine.PythonPathはsys.pathの値を設定
using Python.Runtime;
using System;
using UnityEngine;
namespace UnityPython
{
public static class PythonLifeCycle
{
private const string PythonFolder = "python-3.11.3-embed-amd64";
private const string PythonDll = "python311.dll";
private const string PythonZip = "python311.zip";
private const string PythonProject = "myproject";
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void PythonInitialize()
{
Application.quitting += PythonShutdown;
Initialize(PythonProject);
}
private static void PythonShutdown()
{
Application.quitting -= PythonShutdown;
Shutdown();
}
public static void Initialize(string appendPythonPath = "")
{
var pythonHome = $"{Application.streamingAssetsPath}/{PythonFolder}";
var appendPath = string.IsNullOrWhiteSpace(appendPythonPath) ? string.Empty : $"{Application.streamingAssetsPath}/{appendPythonPath}";
var pythonPath = string.Join(";",
$"{appendPath}",
$"{pythonHome}/Lib/site-packages",
$"{pythonHome}/{PythonZip}",
$"{pythonHome}"
);
var scripts = $"{pythonHome}/Scripts";
var path = Environment.GetEnvironmentVariable("PATH")?.TrimEnd(';');
path = string.IsNullOrEmpty(path) ? $"{pythonHome};{scripts}" : $"{pythonHome};{scripts};{path}";
Environment.SetEnvironmentVariable("PATH", path, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("DYLD_LIBRARY_PATH", $"{pythonHome}/Lib", EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("PYTHONNET_PYDLL", $"{pythonHome}/{PythonDll}", EnvironmentVariableTarget.Process);
#if UNITY_EDITOR
Environment.SetEnvironmentVariable("PYTHONDONTWRITEBYTECODE", "1", EnvironmentVariableTarget.Process);
#endif
PythonEngine.PythonHome = pythonHome;
PythonEngine.PythonPath = pythonPath;
PythonEngine.Initialize();
}
public static void Shutdown()
{
PythonEngine.Shutdown();
}
}
}
サンプル
以下はmatplotlibでグラフを描画してその画像をUnityで表示するサンプルです。
matplotlibでグラフ画像のピクセル値の配列作り、そのアドレス値からUnity側で配列を読み出しているので高速に動作します。
アドレス値からの読み出しは「C#にpythonで作った処理を組み込む【pythonnetによるtensorflowモデルの組込】」が参考になりましたが、更にC#側で配列自体も参照してpythonの関数を抜けてもGCされないように工夫しています。
注意点
以下はPython.NET自体の注意点です。
-
using (Py.Gil())
ブロックを同時に2つ以上実行すると後から実行した方がクラッシュするので排他制御が必要 -
PythonEngine.Shutdown()
の後に再度初期化してもsys.path
が更新されない
また、アドレス値からバイト列を読み出す場合は以下の注意点があります。
- C#からnumpy配列をアドレス値から読み出す場合はメモリ上に連続したバイト列が必要なので
numpy.copy()
などで配列を作り直す - アドレス値だけを返した場合は配列本体の参照カウントが0になりpythonがいつGCしてもおかしくないので配列本体も返してC#で保持しておき読み出しが終わるまで参照カウントを増やしておく
Discussion
Unityからpythonを使用したいと思い記事を拝見いたしました。プログラミング初心者のため初歩的な質問で大変申し訳ありません。初期化スクリプトの項目で分からなくなりました。いくつか教えて頂きたいのですが、
・初期化スクリプトの置き場所はAssetes内でよろしいでしょうか?
・環境変数PATHに設定する.exeと.dllがあるパス、環境変数DYLD_LIBRARY_PATHに設定するpipのdllがあるパス、環境変数PYTHONNET_PYDLLに設定するpython311.dllのパスの探し方を教えて頂けないでしょうか?
ご多忙の中申し訳ありませんが宜しくお願い致します。
また、記事中にリンクした https://github.com/shiena/Unity-PythonNet は動作するプロジェクトなのでこちらも参考にしてください。
早速のご返答ありがとうございます。GitHubの方を参考にさせて頂き学んでいきたいと思います。ありがとうございます。
1つ注意する事があり、pipで追加するライブラリにネイティブライブラリが入っているとsys.pathの結果が増えます。
そのため必要なライブラリをpipでインストールした後のsys.pathをスクリプト側のPythonEngine.PythonPathへ追加してください。