When I first got my hands on Unity I was a bit disappointed to note that, unlike Maya, it doesn't include a built in interactive console environment. The console is a wonderful thing for a TA - it's great for printing out data, lightweight automation of the "find everything in the scene named 'foo' and rename it to 'bar'" variety. So, I thought, is there some way to get this into Unity? The fact that one could even ask it is a tribute to how flexible Unity is - and as it turned out it was not only possible, it wasn't too hard.
You got Python in my Unity!
To start with a console needs some kind of scripting language. Not surprisingly, I wanted to see I could do it in python. Fortunately, this is ridiculously easy thanks to IronPython, the dotnet flavor of Python. IronPython runs on dotnet, and so does Unity - so it's not tough to plug IronPython into Unity directly. Here's how:- You need a verstion of IronPython that will run on Unity's version of Mono , which as of this writing (Unity 4.22 , late 2013) is version 2.6. By a happy coincidence of naming, that points you at IronPython 2.6.2. (I've tried later versions but without much luck).
- Locate the IronPython dlls and the IronPython stdlib in the zip file. You will need
- IronPython.dll
- IronPython.Modules.dll
- Microsoft.Scripting.Core.dll
- Microsoft.Scripting.dll
- Microsoft.Scripting.Debugging.dll
- Microsoft.Scripting.ExtensionAttribute.dll
- Microsoft.Dynamic.dll
- CORRECTION : 12-22-2013 If you want access to the Python stdlib, you'll also need to grab a copy of the python 2.6 /Lib folder -- this is not distributed with IronPython 2.6. I unzipped the Python26.zip file from my Maya bin directory into the /Lib folder, taking care to leave the handful of IronPython files already there
- Copy all of the above into an Editor/Plugins/Resources folder in Unity. If you're not sure what that means:
-
- Naming a folder Editor tells Unity it only runs in the editor, not at runtime (IronPython won't run inside Unity on IOS devices, since those never run editor code)
- Naming it a folder Plugins tells Unity to load dlls from it
- Naming a folder Resources makes sure it loads before scripts are compiled
- Restart Unity and open up MonoDevelop. If you check the Assembly-CSharp Editor info in the Solution panel you should see all of your IronPython DLL's are referenced therein:
Once you've verified that the DLL's are in place, its time to test them. Hosting an IronPython session in another app is much simpler than it sounds. The best resource for how it works is Michael Foord's Voidspace site (his book on IronPython is a great resource if you plan on going far with this , btw) . However in overview the process is pretty simple (
- Create a ScriptEngine. This is the actual Python interpreter.
- Create a ScriptScope ( Microsoft.Hosting.ScriptScope) This corresponds to the global namespace of your interpreter - much like the interpeter namespace in Maya
- Create a ScriptSource ( Microsoft.Hosting.ScriptSource) using some text you've entered or composed.
- Execute the script
- Rinse & Repeat. You are in business.
Script hosting in practice
Here's an example .cs script that demostrates the process. Put it in an editor folder so that it can access the Unity Editor assembly (it's probably a good idea to keep it in the editor folder where you have your plugins/resources folder for cleanliness).
using UnityEngine;
using UnityEditor;
using IronPython;
using IronPython.Modules;
using System.Text;
// derive from EditorWindow for convenience, but this is just a fire-n-forget script
public class ScriptExample : EditorWindow {
[MenuItem("Python/HelloWorld")]
public static void ScriptTest()
{
// create the engine
var ScriptEngine = IronPython.Hosting.Python.CreateEngine();
// and the scope (ie, the python namespace)
var ScriptScope = ScriptEngine.CreateScope();
// execute a string in the interpreter and grab the variable
string example = "output = 'hello world'";
var ScriptSource = ScriptEngine.CreateScriptSourceFromString(example);
ScriptSource.Execute(ScriptScope);
string came_from_script = ScriptScope.GetVariable<string>("output");
// Should be what we put into 'output' in the script.
Debug.Log(came_from_script);
}
}
When it compiles you'll get a menu items that activates the script. When you hit it you should get a debug printout in your console like so.
Like the Maya python interpreter, you need to import the appropriate names so that you can get to them in scripts (it's always rather boggled my mind that Maya's own interpreter requires you to import cmds or pymel every single freakin' time. IronPython lets you import dotnet assemblies as if they were python modules, and since Unity and the Unity Editor are dotnet assemblies you can get access to the entire Unity environment just by importing them into your interpreter.
First, we need to load the assemblies to make them available to the intpereter itself. In dotnet land that's done by loading an assembly. Once an assembly is loaded, it can be imported using typical Python syntax
[MenuItem("Python/HelloWorldRuntime")]
public static void UnityScriptTest()
{
// create the engine like last time
var ScriptEngine = IronPython.Hosting.Python.CreateEngine();
var ScriptScope = ScriptEngine.CreateScope();
// load the assemblies for unity, using the types of GameObject
// and Editor so we don't have to hardcoded paths
ScriptEngine.Runtime.LoadAssembly(typeof(GameObject).Assembly);
ScriptEngine.Runtime.LoadAssembly(typeof(Editor).Assembly);
StringBuilder example = new StringBuilder();
example.AppendLine("import UnityEngine as unity");
example.AppendLine("import UnityEditor as editor");
example.AppendLine("unity.Debug.Log(\"hello from inside the editor\")");
var ScriptSource = ScriptEngine.CreateScriptSourceFromString(example.ToString());
ScriptSource.Execute(ScriptScope);
}
Running that one generates another console message - but this time from inside the script!
Next time: building a console
That may not seem like much, but in reality it's a big deal. You've got a working script interpreter running in Unity now, with all the power of Python and access to the innards of the Unity environment. What remains is to build a decent interactive environment. If you're content with something barebones, you can whip up a simple UI to allow you to type code into a text field and see the results in another text block. That may be enough for some purposes (hell, half the 3d packages on the market do little more than that). However a more featured editor is a little tougher and involves a little hacking to work around the limitations of Unity's GUI text handling, which makes it a bit involved for a single post. I'll save that one for next time
I'm curious about the issues you had with IronPython 2.7. Would you mind emailing me at jdhardy@gmail.com with some details?
ReplyDeletereplied via email
ReplyDeleteThanks Steve. Gonna give this one a try. It would be great to have a simple UI to enter into and execute from.
ReplyDeleteI'm hoping this will help me learn the Unity functions and methods faster as this is the way I'm used to learning an apps SDK.
Following your steps resulted in the following exception: TypeLoadException: Could not load type 'IronPython.Runtime.PythonContext' from assembly 'IronPython' during the CreateEngine() call.
ReplyDeleteHm - sorry for jumping the gun. I downloaded the 2.6.2 source and manually compiled it myself and everything worked just fine.
DeleteKind of surprised you needed to recompile, would have expect the binaries to work. Maybe an old version of .net lying around or polluting your GAC? Or possibly an earlier installation of IPY left something behind?
DeleteIn any case, glad it worked for you