20
25

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.

UnityでIronPythonを使う

Last updated at Posted at 2019-04-04

#はじめに
ここ数か月、Unityで強化学習をしようとしていました。
その際、Pythonの資産とUnityの物理エンジンを使うためにIronPythonを導入することにしました。
これはその過程で得た知見のまとめです。
IronPythonの仕組みなどではなく、とりあえず早急にUnityでPythonを使いたいという人向けです

正直、どうして動いているのかわからないところのほうが多いです
それでも記事にしたのは、実際にUnityで使うことを説明した記事が少なく、僕がとても苦労したからです
間違いだらけかもしれませんが、指摘していただけたら嬉しいです

前回初めて書いた記事が記事と呼ぶのもはばかられるqiita界の粗大ごみだったので今回はできる限り丁寧に書いていきますよ頑張る。

#準備
##環境
今回使用したのは
windows10
unity 2018.2.14f1
IronPython 2.7.7
です

##導入
IronPythonのHPから最新のものをダウンロードします

2019/4/29修正
unityのデフォルトの.NETバージョンは3.5に設定されていますが、IronPythonでは2.7.8で.net 3.5、2.7.9では.net 4.0むけのライブラリが削除されました
なので、.NETのバージョンを変更したくない場合はIronPython 2.7.7を使用してください

image.png

一番上の.msiでインストールすると楽です。
インストーラの指示に従ってインストールしましょう
場所はどこでもいいです

次にunityプロジェクトに、
Assets/Plugins
フォルダを作ります

フォルダを作ったら、IronPythonをインストールした場所に行きましょう

image.png

こんな感じになっていると思います
この中の、
-/IronPython 2.7/Platforms/Net35
内にある、

IronPython.dll
IronPython.Modules.dll
Microsoft.Dinamic.dll
Microsoft.Scripting.dll
Microsoft.Scripting.Core.dll
Microsoft.Scripting.Metadata.dll

をコピーし、

-\Assets\Plugins

に貼り付けます

次に、エディター左上のFileから、BuildSettings → PlayerSettingsと進んでください

デフォルトではインスペクターと同じ場所に出てくる、PlayerSettingsの、OtherSettings → Configuration → API compatibilityを選択し、.NET 2.0に設定してください(デフォルトは.NET 2.0 subsetになっています)

これでもうIronPythonが使えるようになっているはずです!!!

早速試してみましょう

#とりあえず使ってみる
C#でスクリプトを書きます

test.cs
using System.Collections.Generic;
using UnityEngine;
using IronPython.Hosting;

public class test : MonoBehaviour {

    [SerializeField]
    TextAsset pythonScript;

	// Use this for initialization
	void Start () {
        var scriptCreator = new PythonScriptCreator(pythonScript);
        scriptCreator.CallScript();
	}
	
	// Update is called once per frame
	void Update () {
		
	}
}


public class PythonScriptCreator
{
  //pythonのスクリプトを文字列としてそのまま持つ
  //改行などもそのまま使える
    string script;

  Microsoft.Scripting.Hosting.ScriptEngine scriptEngine;
    Microsoft.Scripting.Hosting.ScriptScope scriptScope;
    Microsoft.Scripting.Hosting.ScriptSource scriptSource;

    public PythonScriptCreator(TextAsset PythonFile)
    {
    //TextAssetからstringに変換
        this.script = PythonFile.text;

        this.scriptEngine = Python.CreateEngine();

        scriptEngine.Runtime.LoadAssembly(typeof(GameObject).Assembly);
        this.scriptScope = scriptEngine.CreateScope();

    //stringを渡すとpythonスクリプトとして読み込んでくれる
        this.scriptSource = scriptEngine.CreateScriptSourceFromString(script);
    }

    public void CallScript()
    {
     //実行
        this.scriptSource.Execute();
    }
}

こんな感じになります

次はPythonのHello Worldを書きます

python.txt
import UnityEngine
from UnityEngine import *

UnityEngine.Debug.Log("Hello World")

注意すべき点は、.txtで保存することです
そうすることでUnityEditor上でTextAssetとして扱われます

あとはインスペクターで適当なGameObjectにつけたら完了です
image.png

実行すると、
image.png
こんな感じで動いているのが分ると思います

#実際に使えるようにする
##インスタンスの共有
C#からPythonのコードを実行することはできました
しかし、実際に開発に使うにはまだ問題があります
その一つがスコープの問題です

現状、C#からPythonに引数を渡すことはできませんし、結果をC#に渡すこともできません
また、PythonからC#のインスタンスを参照したりすることもできません
なので、それを可能にしたいと思います
なお、この方法はこのスライドの内容をパクっています
http://www.zengeren.com/content/data/02/02_AC_IronPython.pdf
このスライドには、IronPythonの少し詳しい説明も載っているので、見てください
公式ドキュメント読むに越したことは無いです(それはそう)

test.cs
public class PythonScriptCreator
{
    string script;
    Microsoft.Scripting.Hosting.ScriptEngine scriptEngine;
    Microsoft.Scripting.Hosting.ScriptScope scriptScope;
    Microsoft.Scripting.Hosting.ScriptSource scriptSource;

    public PythonScriptCreator(TextAsset PythonFile)
    {
        this.script = PythonFile.text;
        this.scriptEngine = Python.CreateEngine();

        scriptEngine.Runtime.LoadAssembly(typeof(GameObject).Assembly);
        this.scriptScope = scriptEngine.CreateScope();
        this.scriptSource = scriptEngine.CreateScriptSourceFromString(script);



        var log = new Log();
        log.debugLog();
        this.scriptScope.SetVariable("scope", log);
        log.debugLog();
    }

    public void CallScript()
    {
        this.scriptSource.Execute(this.scriptScope);
    }
}

public class Log
{
    public int testInt = 0;

    public void debugLog()
    {
        Debug.Log("Debug Log " + testInt);
    }
}

先ほどのコードに少し、追加しました
int型の変数一つと、それをログに流す関数を一つ持ったクラスを定義します

test.cs

        var log = new Log();
        log.debugLog();
        this.scriptScope.SetVariable("scope", log);
        log.debugLog();

この部分では、Logクラスのインスタンスをscopeという名前で、scriptScopeに追加しています
こうすることで、Python側から、Logクラスのlogという名前のインスタンスをscopeという名前で利用できるようになります

Pythonのコードも見てみましょう

python.txt
import UnityEngine
from UnityEngine import *

UnityEngine.Debug.Log("Hello World")

scope.testInt = 2

scope.debugLog()

scopeという名前でlogを参照していることが分かります

これを実行すると、
image.png
こうなります

4つのDebug.Logを上から順に1,2,3,4とします

1は、

test.cs

        var log = new Log();
        log.debugLog();   //←ここで呼ばれた
        this.scriptScope.SetVariable("scope", log);
        log.debugLog();

testIntを変更していないので、初期化したときの0が入っています

2は

test.cs

        var log = new Log();
        log.debugLog();
        this.scriptScope.SetVariable("scope", log);
        log.debugLog(); //←ここで呼ばれた

ここでは、まだpython.txtは実行されていないので、testIntに変更はありません

3は

python.txt
import UnityEngine
from UnityEngine import *

UnityEngine.Debug.Log("Hello World") #←これ

scope.testInt = 2

scope.debugLog()

python.txtのDebug.Logです

4は

python.txt
import UnityEngine
from UnityEngine import *

UnityEngine.Debug.Log("Hello World")

scope.testInt = 2

scope.debugLog() #←これ

testIntが変更された後なので2が出力されます

このように、

Microsoft.Scripting.Hosting.ScriptScope.SetVariable(string name,Object instance)

でC#のインスタンスをPythonに渡せます

僕が使っていた時はこれが分らなかったので、全部jsonにして渡していました、地獄だった、、

##ライブラリを使えるようにする
今のところ、Python側では、SystemやUnityEngineなどのライブラリを使うことができます
他にも、importに書くだけで、System.CollectionsなどのC#のライブラリやUnity関連のライブラリは普通に使えます
つまり、C#で使えるものはPythonでも普通に使える、ということです
(ただし、ジェネリックは対応する表記が分らないので使えていないです。
つまり、GetComponent<>()などができないので、これもPythonだけでゲームが作るのが厳しい一因になっています)

しかし、Pythonの膨大な資産も使いたいところです

まず、プロジェクトフォルダ内にPythonのライブラリを入れるためのフォルダを作ります
僕は
-\Assets\PythonLibrary
としました

次に、IronPythonをインストールしたフォルダに行き、
-\IronPython 2.7\Lib
をコピーして、先ほど作ったフォルダに貼り付けます

Libの中身だけをPythonLibrary内に貼り付けても構いません

次に、python.txtに追加します

python.txt
#Unity、C#などのライブラリのインポート
import UnityEngine
from UnityEngine import *

#Pythonのライブラリのインポート
import sys
sys.path.append('-\Assets\PythonLibrary\Lib')

#例
import random

こんな感じです
当然、先にPythonのライブラリをインポートしても構いません

また、自作のライブラリなども、-\PythonLibrary\Lib内に入れておけば同様にインポートされます

これでPythonのライブラリも自由に使えるようになったかと思います

#まとめ
##感想
UnityのC#からIronPythonをつかってPythonのスクリプトを動かすことができました。
これによって、豊富に提供されているPythonのライブラリがUnityでも使えるようになりました
最近Unityばゲーム以外にも研究用のソフトウェアの開発などにも利用されています
Pythonが使えることでもっとできることの幅が増えれば楽しいなと思います

僕が高校の課題研究でIronPythonを使おうとしたときは変数も共有できず、StreamLineが飛び交っていましたし、Pythonのライブラリも導入できずに結局あまり意味がなくなってしまった、という悲しい結果になってしまいました
やり方を書いてあるだけの記事でどこがバグの温床になっているかもわからないですが、この記事が誰かの役に立てればいいな、などと思いあがっています
最初にも書きましたが間違っているところがあれば教えていただけると幸いです

##今後
今回は、ScriptEngineをはじめ、おまじないが多かったことが記事を出すか迷ってた原因の一つです。原因としては、Microsoft.Scripting.dllなどの使っているライブラリのドキュメントがどこを探しても見つからない、ということがあります。
明らかにマイクロソフトが作っているんですが、MSDNなどを漁っても見つけられなかったんですよね
他にも実際に使う上での注意点なども書いていきたいと思ってはいます。思っているだけですが

普通に見つけてしまったのでゆっくり読んでいきます。
https://docs.microsoft.com/ja-jp/dotnet/framework/reflection-and-codedom/dynamic-language-runtime-overview
ここと、このページから飛べるリポジトリにおいてあるワードファイルに詳しい説明があります

まあそういうこともあるので、この記事もできるだけアップデートをしていきたいです

20
25
2

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
20
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?