Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Javascript engine inside a Unity game (aka user programmable games)

Discussion in 'Scripting' started by Rod-Galvao, Mar 28, 2011.

  1. Rod-Galvao

    Rod-Galvao

    Joined:
    Dec 12, 2008
    Posts:
    210
    Hi All,

    Just like to share what I did to include a javascript engine inside my game.

    After a lot of research for a good engine (jint, javascriptdonet, rhino+ikvm etc), I found Jurassic, a great piece of software.

    My main requirements was: the final solution must be runnable from a webplayer build.

    I successfully set up a solution using jint (btw too slow) and rhino(java)+ikvm (final filesize too big). But both had problems with webplayer.

    Since Jurassic doesn´t use third party dlls it was easier to put it to work with a webplayer build.

    You will need to download:

    a) Jurassic (of course) source code: http://jurassic.codeplex.com/
    b) Visual Studio 2010 C# Express: http://www.microsoft.com/visualstudio

    So...

    1) Unzip Jurassic´s source code and open the solution (.sln)
    2) Edit the file ScriptEngine.cs (line 275) from this:

    Code (csharp):
    1.         internal static bool LowPrivilegeEnvironment
    2.         {
    3.             get
    4.             {
    5.                 lock (lowPrivilegeEnvironmentLock)
    6.                 {
    7.                     if (lowPrivilegeEnvironmentTested == false)
    8.                     {
    9.                         var permission = new System.Security.Permissions.SecurityPermission(
    10.                             System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode);
    11.                         lowPrivilegeEnvironment = !System.Security.SecurityManager.IsGranted(permission);
    12.                         lowPrivilegeEnvironmentTested = true;
    13.                     }
    14.                 }
    15.                 return lowPrivilegeEnvironment;
    16.             }
    17.         }
    To this:

    Code (csharp):
    1.         internal static bool LowPrivilegeEnvironment
    2.         {
    3.             get
    4.             {
    5.                 return true;
    6.                 //lock (lowPrivilegeEnvironmentLock)
    7.                 //{
    8.                 //    if (lowPrivilegeEnvironmentTested == false)
    9.                 //    {
    10.                 //        var permission = new System.Security.Permissions.SecurityPermission(
    11.                 //            System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode);
    12.                 //        lowPrivilegeEnvironment = !System.Security.SecurityManager.IsGranted(permission);
    13.                 //        lowPrivilegeEnvironmentTested = true;
    14.                 //    }
    15.                 //}
    16.                 //return lowPrivilegeEnvironment;
    17.             }
    18.         }
    Also, remove the file FileScriptSource.cs and the methods from ScriptEngine.cs that refers to it (2 methods called ExecuteFile).

    3) Now click on project (not the solution) Jurassic and build it
    4) If you used the default settings, get the DLL file from bin\Release and put into your Unity project
    5) (Testing) Create and empty scene and drop the following script to a game object:

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class JavascriptTest : MonoBehaviour
    4. {
    5.     static float t1;
    6.     static float t2;
    7.    
    8.     void Start ()
    9.     {
    10.         Test();
    11.     }
    12.    
    13.     void OnGUI()
    14.     {
    15.         GUI.Label(new Rect(10, 10, 100, 50), "t1="+t1);
    16.         GUI.Label(new Rect(10, 50, 100, 50), "t2="+t2);
    17.     }
    18.    
    19.     public static void Test()
    20.     {
    21.         float st = Time.realtimeSinceStartup;
    22.         var engine = new Jurassic.ScriptEngine();
    23.         engine.Execute("for (i=0; i<1000000; i++) {}");
    24.         t1 = Time.realtimeSinceStartup-st;
    25.         Debug.Log("total time=" + t1);
    26.        
    27.         st = Time.realtimeSinceStartup;
    28.         for (int i=0; i<1000000; i++){}
    29.         t2 = Time.realtimeSinceStartup-st;
    30.         Debug.Log("total time=" + t2);
    31.     }
    32. }

    My main concern now is security and how to avoid processing power abuse from user´s scripts. Any suggestions?
     
    Last edited: Mar 28, 2011
  2. hima

    hima

    Joined:
    Oct 1, 2010
    Posts:
    183
    Hey, thank you very much for this!

    As for security, it's kinda hard really, since you are giving them a programming language to work with. I'd love to know more about this as well.

    Sorry that I couldn't be much of a help :( But I really appreciate what you post here. Thanks! ^ ^
     
  3. BotMo

    BotMo

    Joined:
    Mar 2, 2011
    Posts:
    101
    Isn't this kind of like putting a duck inside of a chicken inside of a turkey? You should call it Gajuruity!

    Seriously though, that's pretty cool. You should try to simplify things a bit for players ala maxscript or melscript where you can see the code being executed through the editor so all you really have to know how to do is copy/paste and a bit of basic programmer logic.
     
  4. digitalpowers

    digitalpowers

    Joined:
    Apr 6, 2011
    Posts:
    59
    I would be interested in seeing what you did to get Jint working. I am hoping to use Jint because it offers a debugger interface and allows you to execute statements one at a time which is something I need for my project. So if you wouldn't mind sharing that would be excellent.
     
  5. Joe0418

    Joe0418

    Joined:
    Aug 21, 2011
    Posts:
    6
    Firstly- this is amazing. Thank you for all the effort!


    I was wondering though, how can you set the Jurassic engine so that it remembers variables after a call to Evaluate has occurred? (much like you see on Jurassic's site...)

    For instance...

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using Jurassic;
    7.  
    8. public class JavascriptTest : MonoBehaviour
    9. {
    10.     private string inputCharSet;    // The character set for prompting user
    11.     private string userText;    // User input text
    12.  
    13.     // Reference to the jurassic engine
    14.     private static ScriptEngine engine = new Jurassic.ScriptEngine();
    15.  
    16.     // Initialize default settings
    17.     void Start ()
    18.     {
    19.         inputCharSet = ">> ";
    20.         userText = inputCharSet;
    21.     }
    22.  
    23.  
    24.     void OnGUI()
    25.     {
    26.         // Set the dimensions for a text field which is padded by 100px on all sides relative to the screen size
    27.         float xPos = 100;
    28.         float yPos = 100;
    29.         float xSize = Screen.width - (2.0F * xPos);
    30.         float ySize = Screen.height - (2.0F * yPos);
    31.  
    32.         // Show the text field to the user and grab their input
    33.         userText = GUI.TextField(new Rect(xPos, yPos, xSize, ySize), userText);
    34.  
    35.         // If the user presses return, evaluate their input
    36.         Event e = Event.current;
    37.         if (e.isKey  e.keyCode == KeyCode.Return) {
    38.             string evalText;    // To hold user's evaluation text
    39.            
    40.             // Try and grab the user's input
    41.             try {
    42.                 string[] inputs = userText.Split(inputCharSet.ToCharArray());
    43.                 evalText = inputs[inputs.Length - 1];
    44.             }
    45.             catch (Exception except) {
    46.                 // For debugging
    47.                 Debug.Log("caught: " + except);
    48.                 evalText = userText;
    49.             }
    50.  
    51.             // Evaluate the user's input and redisplay the prompt
    52.             try {
    53.                 string answer = engine.Evaluate(evalText).ToString();
    54.                 userText += "\n=>  " + answer + "\n" + inputCharSet;
    55.             }
    56.             catch (Exception excp) {
    57.                 userText += "\n=> \n" + evalText + "\n" + excp + "\n\n" + inputCharSet;
    58.             }
    59.         }
    60.  
    61.         // Move the cursor to the engine of the input box
    62.         TextEditor te = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
    63.         if (te != null) {
    64.             te.MoveCursorToPosition(new Vector2(userText.Length + xSize, userText.Length + ySize));
    65.         }
    66.     }
    67. }
    68.  

    I had in mind something along the lines of the python or ruby interpreter, but i get the following scenario:

    Code (csharp):
    1.  
    2. >> i = 30
    3. => 30
    4. >> i
    5. =>
    6. i
    7. Jurassic.JavaScript.Exception: ReferenceError: i is not defined
    8.  
    Any ideas?
     
  6. ZJP

    ZJP

    Joined:
    Jan 22, 2010
    Posts:
    2,649
    Hi,

    Nice work rgalvao. Works in standalone mode without recompiling.
    I changed the source, because the first timer initialization is setting before the jurassic init engine. This distorts the calculation.
    Code (csharp):
    1.  
    2.    public static void Test()
    3.     {
    4.         var engine = new Jurassic.ScriptEngine();
    5.         float st = Time.realtimeSinceStartup;
    6.         engine.Execute("for (i=0; i<1000000; i++) {}");
    7.         t1 = Time.realtimeSinceStartup-st;
    8.         Debug.Log("total time=" + t1);
    9.        
    10.         st = Time.realtimeSinceStartup;
    11.         for (int i=0; i<1000000; i++){}
    12.         t2 = Time.realtimeSinceStartup-st;
    13.         Debug.Log("total time=" + t2);
    14.     }
    15.  
    Of course, this is still slower than UnityScript ;)

    Example with an external script (with the ORIGINAL Dll )
    Code (csharp):
    1.  
    2.     public static void Test()
    3.     {
    4.         var engine = new Jurassic.ScriptEngine();
    5.         float st = Time.realtimeSinceStartup;
    6.         engine.ExecuteFile("test.js");
    7.         t1 = Time.realtimeSinceStartup-st;
    8.         Debug.Log("total time=" + t1);
    9.        
    10.         st = Time.realtimeSinceStartup;
    11.         for (int i=0; i<1000000; i++){}
    12.         t2 = Time.realtimeSinceStartup-st;
    13.         Debug.Log("total time=" + t2);
    14.     }
    15.  
    the external test.js source
    Code (csharp):
    1.  
    2. for (i=0; i<1000000; i++) {}
    3.  
    Call a javascript function from unity
    Code (csharp):
    1.  
    2.     public static void Test()
    3.     {
    4.         var engine = new Jurassic.ScriptEngine();
    5.         engine.Evaluate("function test(a, b) { return a + b }");
    6.         Debug.Log(engine.CallGlobalFunction<int>("test", 5, 6));
    7.     }
    8.  
    A call with an external script
    Code (csharp):
    1.  
    2.     public static void Test()
    3.     {
    4.         var engine = new Jurassic.ScriptEngine();
    5.         engine.ExecuteFile("test.js");
    6.         Debug.Log(engine.CallGlobalFunction<int>("test", 5, 6));
    7.     }
    8.  
    The external script test.js
    Code (csharp):
    1.  
    2. function test(a, b) { return a + b }
    3.  
    @joe0418
    You ask the result of an unknown variable by "engine.Evaluate"" ;)
     
    Last edited: Nov 21, 2011
  7. Kroltan

    Kroltan

    Joined:
    Mar 24, 2012
    Posts:
    8
    I have tried using this parser within UnityScript, and it throws me an error. This is my code:
    Code (csharp):
    1.  
    2. import Jurassic;
    3. var engine : ScriptEngine = new ScriptEngine();
    4.  
    5. function Update () {
    6.     engine.Execute("print \"hi\"");
    7. }
    This is the error:
    Code (csharp):
    1. NotImplementedException: The requested feature is not implemented.
    2. System.Reflection.Emit.DynamicMethod.GetDynamicILInfo ()
    3. Jurassic.Compiler.DynamicILGenerator..ctor (System.Reflection.Emit.DynamicMethod dynamicMethod)
    4. Jurassic.Library.FunctionBinder.CreateSingleMethodBinder (Int32 argumentCount, Jurassic.Library.FunctionBinderMethod binderMethod)
    5. Jurassic.Library.FunctionBinder.CreateBinder (Int32 argumentCount)
    6. Jurassic.Library.FunctionBinder.Call (Jurassic.ScriptEngine engine, System.Object thisObject, System.Object[] arguments)
    7. Jurassic.Library.ClrFunction.ConstructLateBound (System.Object[] argumentValues)
    8. Jurassic.JavaScriptException.CreateError (Jurassic.ScriptEngine engine, System.String name, System.String message)
    9. Jurassic.JavaScriptException..ctor (Jurassic.ScriptEngine engine, System.String name, System.String message, Int32 lineNumber, System.String sourcePath)
    10. Jurassic.Compiler.Parser.ParseExpression (Jurassic.Compiler.Token[] endTokens)
    11. Jurassic.Compiler.Parser.ParseLabelOrExpressionStatement ()
    12. Jurassic.Compiler.Parser.ParseStatementNoNewContext ()
    13. Jurassic.Compiler.Parser.ParseStatement ()
    14. Jurassic.Compiler.Parser.Parse ()
    15. Jurassic.Compiler.GlobalMethodGenerator.Parse ()
    16. Jurassic.ScriptEngine.Execute (Jurassic.ScriptSource source)
    17. Jurassic.ScriptEngine.Execute (System.String code)
    18. ParserTester.Update () (at Assets/ParserTester.js:5)
    19.  
    I'm using Unity 3.5 Free with Android and iOS Basic licenses on a Windows 7.
     
  8. GamePowerNetwork

    GamePowerNetwork

    Joined:
    Sep 23, 2012
    Posts:
    257
    Will this run on iOS without be rejected by Apple? ... I know you can't execute code that isn't built at compile time.
     
  9. StrongGuy

    StrongGuy

    Joined:
    Aug 12, 2014
    Posts:
    13
  10. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    wccrawford likes this.
  11. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Aaaaaand, it's removed. The funny thing is that, despite being annoyed, I still clicked on it out of curiosity. I suppose that's why these tactics will continue to be successful (at least, when they're not removed). ^_~