U3D
Open-source, cross-platform 2D and 3D game engine built in C++
|
To enable AngelScript scripting support, the Script subsystem needs to be created and registered after initializing the Engine. This is accomplished by the following code, seen eg. in Tools/Urho3DPlayer/Urho3DPlayer.cpp:
There are three ways the AngelScript language can be interacted with in Urho3D:
Immediate execution takes one line of AngelScript, compiles it, and executes. This is not recommended for anything that needs high performance, but can be used for example to implement a developer console. Call the Script subsystem's Execute() function to use. For example:
It may be useful to be able to access a specific scene or a script file while executing immediate script code. These can be set on the Script subsystem by calling SetDefaultScene() and SetDefaultScriptFile().
This requires a successfully loaded ScriptFile resource, whose Execute() function will be used. To identify the function to be called, its full declaration is needed. Parameters are passed in a VariantVector. For example:
If the function being called has void return type and no parameters, its name can alternatively be given instead of the full declaration.
Execute() also has an overload which takes a function pointer instead of querying by declaration. Using a pointer is naturally faster than a query, but also more risky: in case the ScriptFile resource is unloaded or reloaded, any function pointers will be invalidated.
The component ScriptInstance can be used to instantiate a specific class from within a script file. After instantiation, the the script object can respond to scene updates, events and serialization much like a component written in C++ would do, if it has the appropriate methods implemented. For example:
The class must implement the empty interface ScriptObject to make its base class statically known. This enables accessing any script object in the scene using ScriptInstance's GetScriptObject() function.
The following methods that implement the component behaviour will be checked for. None of them are required.
The update methods above correspond to the variable timestep scene update and post-update, and the fixed timestep physics world update and post-update. The application-wide update events are not handled by default.
The Start() and Stop() methods do not have direct counterparts in C++ components. Start() is called just after the script object has been created. Stop() is called just before the script object is destroyed. This happens when the ScriptInstance is destroyed, or if the script class is changed.
When a scene node hierarchy with script objects is instantiated (such as when loading a scene) any child nodes may not have been created yet when Start() is executed, and can thus not be relied upon for initialization. The DelayedStart() method can be used in this case instead: if defined, it is called immediately before any of the Update() calls.
TransformChanged() is called whenever the scene node transform changes and the node was not dirty before, similar to C++ components' OnMarkedDirty() function. The function should read the node's world transform (or rotation / position / scale) to reset the dirty status and ensure the next dirty notification is also sent.
Subscribing to events in script behaves differently depending on whether SubscribeToEvent() is called from a script object's method, or from a procedural script function. If called from an instantiated script object, the ScriptInstance becomes the event receiver on the C++ side, and calls the specified handler method when the event arrives. If called from a function, the ScriptFile will be the event receiver and the handler must be a free function in the same script file. The third case is if the event is subscribed to from a script object that does not belong to a ScriptInstance. In that case the ScriptFile will create a proxy C++ object on demand to be able to forward the event to the script object.
The script object's enabled state can be controlled through the SetEnabled() function. When disabled, the scripted update methods or event handlers will not be called. This can be used to reduce CPU load in a large or densely populated scene.
There are shortcut methods on the script side for creating and accessing a node's script object: node.CreateScriptObject() and node.GetScriptObject(). Alternatively, if the node has only one ScriptInstance, and a specific class is not needed, the node's scriptObject property can also be used. CreateScriptObject() takes the script file name (or alternatively, a ScriptFile object handle) and class name as parameters and creates a ScriptInstance component automatically, then creates the script object. For example:
Note that these are not actual Node member functions on the C++ side, as the Scene classes are not allowed to depend on scripting.
After instantiation, the script object's public member variables that can be converted into Variant, and that don't begin with an underscore are automatically available as attributes of the ScriptInstance, and will be serialized. Node and Component handles are also converted into nodeID and componentID attributes automatically. Note: this automatic attribute mechanism means that a ScriptInstance's attribute list changes dynamically depending on the class that has been instantiated.
If the script object contains more complex data structures, you can also serialize and deserialize into a binary buffer manually by implementing the Load() and Save() methods.
Network replication of the script object variables must be handled manually by implementing WriteNetworkUpdate() and ReadNetworkUpdate() methods, that also write and read a binary buffer. These methods should write/read all replicated of variables of the object. Additionally, the ScriptInstance must be marked for network replication by calling MarkNetworkUpdate() whenever the replicated data changes. Because this replication mechanism can not sync per variable, but always sends the whole binary buffer if even one bit of the data changes, also consider using the automatically replicated node user variables.
Delayed method calls can be used in script objects to implement time-delayed actions. Use the DelayedExecute() function in script object code to add a method to be executed later. The parameters are the delay in seconds, repeat flag, the full declaration of the function, and optionally parameters, which must be placed in a Variant array. For example:
Delayed method calls can be removed by declaration using the ClearDelayedExecute() function. If an empty declaration (default) is given as parameter, all delayed calls are removed.
If the method being called has void return type and no parameters, its name can alternatively be given instead of the full declaration.
When a scene is saved/loaded, any pending delayed calls are also saved and restored.
Much of the Urho3D classes are exposed to scripts, however things that require low-level access or high performance (like direct low level rendering) are not. Also for scripting convenience some things have been changed from the C++ API:
Check the automatically built Scripting API documentation for the exact function signatures. Note that the API documentation can be regenerated to the Urho3D log file by calling DumpAPI() function on the Script subsystem or by using ScriptCompiler tool.
Instead of compiling scripts from source on-the-fly during startup, they can also be precompiled to bytecode, then loaded. Use the ScriptCompiler utility for this.
The Script subsystem will automatically redirect script file resource requests (.as) to the compiled versions (.asc) if the .as file does not exist. Making a final build of a scripted application could therefore involve compiling all the scripts with ScriptCompiler, then deleting the original .as files from the build.
There are some complexities of the scripting system one has to watch out for:
A global VariantMap, globalVars, can be accessed by all scripts to store shared data or to preserve data through script file reloads.
The following changes have been made to AngelScript in Urho3D:
In unmodified AngelScript, this would have to be written as: