In a nutshell
A more concise explanation of Freedom Script for programmers.
In the previous quick start guides I endeavored to take the point of view of a novice programmer. I tried to explain basic terms and functionality. If you are an intermediate or more advanced programmer it was a little drawn out. In this guide I will assume you can create basic c# scripts and have a basic understanding of .net language. I do still recommend reading The BASICS first but then jumping right into this guide.
Freedom Script (FS) is a macro language. It’s functionality is folded into three main objects. The Interpreter Object loads, parses and executes scripts that exist external to your unity project. These script files are plain text. The Interpreter object provides the structure for a basic programming language. Inside a FS script there is support for variables (local and global), basic math and string handling and decision and looping constructs. FS even supports user-defined script subroutines that return values. In unity you can run your FS scripts line by line or all at once. Examples would be from the Awake() function to fire once or from its Update() firing repeatedly. These are all common modern language elements. FS scripts could be created and run in a separate virtual space right out of the box, I am sure you are reading this because you want to connect to your unity project. Here is where the true power of Freedom Script lays.
Through FS’s Subroutine object you create c# objects that will run in unity when a keyword or function call is made from your FS script. You define the code that will execute and the keyword to trigger the code in your FS script. You are effectively extending the Freedom Script language. Each Interpreter object can have its own language definition or one common language depending on your needs. Each unity object can have a distinct Interpreter Object and thus run it’s own scripts and language or a common “gamemanager” Interpreter that fires off commands to other unity game objects. Through the Subroutine Object it is even possible to extend FS’s basic data types. FS does not override anything in unity but runs a separate scripting environment and allows communication between the two environments. Perfect for game programming!
Internally FS script is reduced to an intermediate code and then executed. All script data values are stored internally as enhanced string objects and inserted into a collection. The Value object is the wrapper for all variables in a FS script. Inside your unity c# script you can access these Value objects and create or manipulate them. You use standard C# code to interact with the collection. It would appear from the FS script’s perspective that the variables were updated.
That’s it ! It is simple and straightforward.
Let us quickly go over some code to demonstrate. I will use our move block example. In this example we will run a script from a unity cube’s FixedUpdate() function. In unity this gets called repeatedly and forever.
We want to move a cube incrementally down the X axis until it reaches a X value that we define in script, then reverse the direction and do it again. In FS script will a define a function called moverblockX() and pass it a value to modify the cubes transform thus “moving” it. we will add some IF THEN statements to our FS script to (in)reverse the X value thus the X direction so our poor cube never reaches its destination.
Here is our FS script file: (FS is not case sensitive)
targetX = 100
currentX = 0
dir = 2
WHILE currentX < targetX DO
currentX = moveblockX(dir)
print(currentX )
IF currentX = 99 THEN
dir = -2
ENDIF
IF currentX = -99 THEN
dir = 2
ENDIF
ENDWHILE
print("all done! : FS")
You can see standard programming structure like While/Do, If/then and variable declarations. These are standard out of the box functionality in FS. I have also used a function called moveblockX(n) and print(), these do not exist in FS and we will define and code their implementation in our unity C# script. (Extending the FS language)
Copy and save that FS script into a project folder. I created a unity project and folders: Assets/Freedom Demo/Freedom Demo Script. You can use anything you like as long as you change the c# code that constructs the path to open the file.
In unity: ( I assume you have basic unity skills)
- Create a unity project/scene
- Create a scripts folders for unity C# scripts and a folder for your FS scripts described above
- Copy the FS script DLL into your unity scripts folder ( this includes it in your unity project)
- Create a cube and create and attach a c# script, name the script “Blockmove”
Now we are ready to do some coding, open the Blockmove c# script,
We need to add two using statements, one for FS and one to access file IO.
using System.IO;
using Interp;
we need to declare a variable of type Interpreter to hold our reference when we create the object. We do this at the class level so it won’t go out of scope until the cube is destroyed.
Create an Awake() function. Because Awake() only fires once it is a good place to create our interpreter object. We will also add some code to construct a full path name to our FS script file.
void Awake()
{
interp = new Interpreter();
string _filename = null;
_filename = UnityEngine.Application.dataPath + "/Freedom Demo/Freedom Script Demo/";
_filename += "BlockmoveDemo.txt";
Now we can load our FS script but do not run it yet, we do this through the Interpreter object. I use a Try/Catch block when I do it. If we load the FS script file and it finds a FS script syntax error it will throw an exception.
void Awake()
{
interp = new Interpreter();
string _filename = null;
_filename = UnityEngine.Application.dataPath + "/Freedom Demo/Freedom Script Demo/";
_filename += "BlockmoveDemo.txt";
try
{
interp.Load(new StreamReader(_filename));
print("File Loaded");
}
catch (SyntaxError ex)
{
print(ex.Message);
}
catch(InternalError ex)
{
print(ex.Message);
}
catch(System.Exception ex)
{
print(ex.Message);
}
}
void fsPrint(string what)
{
print(what);
}
As you can see I have also defined a function fsprint() function. All it does is print to the unity console. Recall we used a print() in our FS script code. We will connect the two shortly. I have not shown you the FixedUpdate() yet either, we will get to that in a moment.
Recall in our FS script file we used two functions that were not part of the builtin FS language. I called them language extensions. They are print() and moveblockX(). Each new keyword or function you add to your FS language needs to have a corresponding Subroutine object. Take a look at the print() subroutine.
public class printSubroutine : Subroutine
{
private Blockmove _parent = null;
public printSubroutine(Blockmove vparent)
{
_parent = vparent;
num_parameters = 1;
}
public override Value Execute(Value[] args, VarCollection vars)
{
_parent.fsPrint(args[0].content.ToString());
return null;
}
}
There is simply a constructor and an Execute(). Notice it is derived from the Subroutine class; you cannot use the Subroutine class directly. The class name is not important. Subroutine has a base class variable (built in) called num_parameters. This indicates how many will be passed in from your FS script. In our FS print function it is only one. (what we want to print). The design pattern I like to use also stores a reference to the c# unity script that will create this object. We write the constructor to accept and store this reference. I call it _parent. We will Instantiate this Subroutine Object from our Awake() in our Blockmove script in a moment. Remember the unity c# Blockmove script is derived from unity’s MonoBehaviour, therefore with our parent reference our Subroutine will have access to Blockmove which has access to MonoBehavior. Simply by using c# “.” Dot notation.
There will be many times this is not necessary and you won’t code this design pattern. An example would be if you are just preforming calculations or changing FS script variables.
Let us move on…
The next function is the Execute(). This is the meat and potatoes. This function gets called when the FS script encounters the function name. In this case it will be “print” *we have not set this keyword yet so don’t worry you did not miss it.
In this function you will first get passed an array of Value objects. As described earlier, Value objects are just wrappers for FS script variables. In this case the parameters that the FS script print() passed in. (what you want to print). First in arg[0],second in arg[1] and so on. Use its’s “content” property to cast it into a string and you are ready to do something with it in unity. In this case, we use our parent reference to call the Fsprint() we defined in the unity c# script Blockmove. It simply prints to the unity console. We call it and pass in that parameter.
The second item passed to the Execute() is a reference to a collection holding ALL of the FS script variables. You can create and insert Value objects into the collection, thereby creating new variables for your FS script or search the collection and modify them.
One item of note, I have included the Subroutine class definition within or “inline” with the Blockmove c# unity script. Only the Blockmove class can see the class we derived from Subroutine. Defining your class outside your unity c# script definition causes unity to treat it as global. This allows other scripts or other Interpreter objects to use it. I use this method to share Subroutines, therefore language extensions, between different instances of Interpreters. If two have the same class name unity will throw an error.
Here is the listing for the Subroutine class that will handle the moveblockX() FS script function.
public class moveblocktoXSubroutine : Subroutine
{
private Blockmove _parent = null;
public moveblocktoXSubroutine(Blockmove vparent)
_parent = vparent;
num_parameters = 1;
}
public override Value Execute(Value[] args, VarCollection vars)
{
Vector3 currentpos = new Vector3(_parent.transform.position.x,_parent.transform.position.y,_parent.transform.position.z);
int temp = int.Parse( args[0].content);
currentpos.x += temp;
_parent.transform.position = currentpos;
return new Value(Interp.Types.DOUBLE, currentpos.x.ToString());
}
}
It has the same design pattern as printsubroutine. The Execute() manipulates the cubes transform by adding the parameter that was passed in. (how much to move) Then we create a Value object, inserting the new X position and returning it to the FS script. The FS script returns this value from the moveblockX(). Any value you return from a Subrountine Execute() will be returned to the function call in your FS script.
Awesome we are almost done! So how do we associate the function keyword that the Subroutine Class will handle in Execute()? Example: print or moveblockX. A simple one liner. Really!
Return back to the Awake() where we created the Interpreter object. After you create the Interpreter just “Register” your subroutine class.
interp.RegisterSubroutine("moveblockX",new moveblocktoXSubroutine(this));
interp.RegisterSubroutine("print", new printSubroutine(this));
Use the Interpreter Object’s RegisterSubroutine(). Pass in first the keyword you want to use in your FS script, in this example “print” and “moveblockX”. Secondly, create an instance of your custom subroutine class in this example “moveblockXSubroutine” and “printsubroutine”.
Insure you pass the Subroutines constructor any parameters you defined. In our example a reference to our blockmove unity class is needed. We can simply use “this”.
We created our Interpreter, custom subroutines and registered them. We are ready to run our script with the unity scene. To do that we will start the interpreter in our unity FixedUpdate()
void FixedUpdate()
{
if (!interp.AtEndOfCode())
{
try
{
interp.Run(StepByStep.STEP_OVER);
print(interp.LastExecutedLine().ToString());
}
catch (RuntimeError ex)
{
print(ex.Message);
}
catch (InternalError ex)
{
print(ex.Message);
}
}
}
We use an If statement to detect the end of our script and a try/catch block to trap any error we encounter when running our FS script.
Our last item to highlight is the Run() parameters. It takes a StepbyStep enumerator. Passing in a Step_Over and FS will run a block or line of code and stop and wait for the next Run() call. That is what we did in our example.
You can also pass in Step_NO and FS will run the code to its completion before returning. This would not work with our example, but works well in the unity Awake() as a setup script.
Click back to unity to compile your code and check for errors and run the scene. If all went well you should see a cube moving along it’s X axis in one direction then reversing back in the other direction. It never gets to its destination. Try changing your FS script code and change the amount the block is moving by 4 or 10. Restart the scene and observe the change. Try adding a delay in the FS script code to see the effect.
In unity convert your cube into a prefab, then drop a few in the scene. Change the starting X position of a few of the prefabs and run the scene.
There are many ways to implement scripting into your project. The possibilities are many. Try a project that attaches a FS script onto an empty gameobject to manage or direct the setup of your scene. You should run it from the Awake() use the STEP_NO flag when you call Run(). This is an excellent way to allow modding in your project.
Whatever your needs are, the design of Freedom Script affords you the tool to create a solution.
Enjoy and Thank You for your support,
Bill R