Scripted Classes
See the openfl_hscript_class
sample for an example of this in action.
Scripted classes are classes whose behavior is defined in a script, rather than in source code. They are parsed and registered at runtime, can be instantiated, and otherwise behave almost identically to normal classes.
This is an incredibly powerful feature for quickly prototyping and testing new features, as well as for providing robust and intuitive modding support to your application.
Creating a Scripted Class
- Create a class like this in your project. In our example, we will create a class called ScriptedStage which extends Stage; this will allow scripts to create classes which extend
Stage
.
@:hscriptClass
class ScriptedStage extends Stage implements HScriptedClass {}
Note that the body of the class is COMPLETELY EMPTY. This is because the contents are populated at compile time using a macro.
- Initialize Polymod as normal.
- Call
ScriptedStage.listScriptClasses()
to get a list of all the classes that have been registered.- These include any classes defined in script files in your
assets
folder that have the correct file extension (defaulting to.hxc
). - You can add additional classes or override existing classes by including them in Polymod mods.
- These include any classes defined in script files in your
- Create a script that defines a class, and add it to the
assets
folder of your project (or to a mod folder to be loaded by Polymod). - Call
ScriptedStage.init(stageClassName, ...args)
to instantiate an object of the scripted class.- The
stageClassName
is the name of the class you want to instantiate. It should be one of the classes returned byScriptedStage.listScriptClasses()
. - The
init()
function will also require any constructor arguments that the superclass needs to be instantiated, so they can be passed to the scripted class. - The return value of
init()
will be of the typeScriptedStage
. It has full support for compile completion and can be passed as an argument to functions which expect aStage
object.
- The
Example of a Scripted Class
Here is an example of what a user-provided scripted class may look like:
// Make sure to import the target class...
import stage.Stage;
// ...and any modules you want to use.
import openfl.utils.Assets;
import openfl.display.Bitmap;
// Extend the Stage class, not the ScriptedStage class.
class BasicStage extends Stage {
// You can define a constructor which utilizes the same arguments as the superclass,
// or fewer arguments if you provide them yourself..
public function new() {
super('basic');
// You can get and set fields of the stage and the program can access those properties.
stageName = 'Basic Stage';
}
// You can define override functions which replace the superclass's behavior completely.
public override function create():Void {
// You can also call the superclass function.
super.create();
// You can call static functions of modules you import, or instantiate classes from those modules.
var landscapeBg = new Bitmap(Assets.getBitmapData('img/stage/landscape.png'));
// You have full access to any properties and methods of objects you instantiated,
// as though you were writing source code directly!
landscapeBg.x = 0;
landscapeBg.y = 0;
landscapeBg.width = 480;
landscapeBg.height = 360;
this.addChild(landscapeBg);
}
// You can also define new functions which can be called from within the script,
// and variables which those functions can use.
var abc:Int = 123;
function coolStuff() {
abc += 10;
if (abc > 1000) {
abc = 20;
}
trace('Value is: ' + abc);
}
}
Abilities and Limitations of Scripted Classes
There are many things which you can do within a scripted class, including but not limited to:
- Override the constructor (using
public function new() { ... }
) - Define new fields and functions and call them from other functions.
- Modify public or private values of the superclass, and call superclass functions.
- Override existing functions of the class to replace (or extend) functionality.
- Import modules and call static functions or instantiate objects (like
import flixel.FlxG
orimport flixel.FlxSprite
)
There are some things to watch out for though:
- You can’t override
static
functions from a script.- This is because the scripted class needs to be instantiated in order to have something to redirect function calls to.
- You can’t override a function which uses a private class as an argument.
- This cannot be done in Haxe either. This is because the private class cannot be accessed outside the module, even by macros.
- You can’t override inline functions.
- This cannot be done in Haxe either. This is because each call to the function is replaced with the function body at compile time.
- Note that this may apply for functions which are not inline, if a child of the class that defines it overrides that function with an inline one.
- You can’t override functions which use the
@:generic
annotation.- This cannot be done in Haxe either. This is because the
@:generic
annotation is actually syntax sugar that, at compile time, creates a separate function for each type it is used with.
- This cannot be done in Haxe either. This is because the
- You can’t override functions which use (as an argument or return value) a type parameter under more than one layer of nesting.
- Example:
Foo<Bar<T>>
. - Doing so will throw an error while compiling, that looks like
Type not found: T
. - This issue exists because type parameters are not being parsed fully recursively right now. Make a GitHub issue explaining your use case if you need this fixed.
- Example:
- You can’t use string interpolation within a script.
- For example,
trace('Value is ${abc}')
will literally printValue is ${abc}
instead ofValue is 123
. - This is because implementing string interpolation would drastically complicate the script parser.
- Thankfully, you can still use string concatenation, for example,
trace('Value is ' + abc)
.
- For example,
- You can’t define
final
variables within a script.- Just don’t change the variable.