要启用AngelScript脚本支持,需要在初始化引擎后创建并注册脚本子系统。这是通过以下代码实现的,如Tools/Urho3DPlayer/Urho3DPlayer.cpp中所示:
context_->RegisterSubsystem(new Script(context_));
有三种方式可以在Urho3D中与AngelScript语言交互:
这需要成功加载ScriptFile资源,将使用其Execute()函数。要识别要调用的函数,需要完整的声明。参数在VariantVector中传递。例如:
ScriptFile* file = GetSubsystem<ResourceCache>()->GetResource<ScriptFile>("Scripts/MyScript.as");
VariantVector parameters;
parameters.Push(Variant(100)); // Add an int parameter
file->Execute("void MyFunction(int)", parameters); // Execute
如果被调用的函数具有void返回类型,并且没有参数,则也可以指定其名称而不是完整声明。
Execute()还有一个重载,它采用函数指针而不是通过声明进行查询。使用指针自然比查询更快,但风险更大:如果卸载或重新加载ScriptFile资源,任何函数指针都将无效。
组件ScriptInstance可用于从脚本文件中实例化特定类。实例化后,脚本对象可以响应场景更新、事件和序列化,就像用C++编写的组件一样,如果它实现了适当的方法。例如:
ScriptInstance* instance = node->CreateComponent<ScriptInstance>();
instance->CreateObject(GetSubsystem<ResourceCache>()->GetResource<ScriptFile>("Scripts/MyClass.as"), "MyClass");
该类必须实现空接口ScriptObject以使其基类静态已知。这允许使用ScriptInstance的GetScriptObject()函数访问场景中的任何脚本对象。
将检查以下实现组件行为的方法。这些都不是必需的。
上述更新方法对应于可变时间步长场景更新和后更新,以及固定时间步长物理世界更新和后升级。默认情况下不处理应用程序范围的更新事件。
Start()和Stop()方法在C++组件中没有直接对应的方法。在创建脚本对象后立即调用Start()。在脚本对象被销毁之前调用Stop()。当ScriptInstance被破坏或脚本类被更改时,就会发生这种情况。
当实例化带有脚本对象的场景节点层次时(例如加载场景时),在执行Start()时可能尚未创建任何子节点,因此无法依赖于该子节点进行初始化。在这种情况下,可以改用DelayedStart()方法:如果定义了该方法,它将在任何Update()调用之前立即调用。
每当场景节点变换发生变化并且节点之前没有变脏时,就会调用TransformChanged(),类似于C++组件的OnMarkedDirty()函数。该函数应读取节点的世界变换(或旋转/位置/缩放),以重置脏状态,并确保也发送下一个脏通知。
根据是从脚本对象的方法还是从过程脚本函数调用SubscribeToEvent(),在脚本中订阅事件的行为不同。如果从实例化的脚本对象调用,ScriptInstance将成为C++端的事件接收器,并在事件到达时调用指定的处理程序方法。如果从函数调用,ScriptFile将是事件接收器,处理程序必须是同一脚本文件中的自由函数。第三种情况是从不属于ScriptInstance的脚本对象订阅事件。在这种情况下,ScriptFile将根据需要创建一个代理C++对象,以便能够将事件转发到脚本对象。
脚本对象的启用状态可以通过SetEnabled()函数控制。禁用时,将不会调用脚本化的更新方法或事件处理程序。这可用于减少大型或人口密集场景中的CPU负载。
在脚本端有一些快捷方法用于创建和访问节点的脚本对象:node.CreateScriptObject()和node.GetScriptObject()。或者,如果节点只有一个ScriptInstance,并且不需要特定的类,也可以使用节点的scriptObject属性。CreateScriptObject()将脚本文件名(或ScriptFile对象句柄)和类名作为参数,并自动创建ScriptInstance组件,然后创建脚本对象。例如:
ScriptObject@ object = node.CreateScriptObject("Scripts/MyClass.as", "MyClass");
注意,这些不是C++端的实际节点成员函数,因为场景类不允许依赖于脚本。
实例化后,脚本对象的公共成员变量(可以转换为Variant,并且不以下划线开头)将作为ScriptInstance的属性自动可用,并将被序列化。节点和组件句柄也会自动转换为节点ID和组件ID属性。注意:这种自动属性机制意味着ScriptInstance的属性列表会根据已实例化的类而动态更改。
如果脚本对象包含更复杂的数据结构,还可以通过实现Load()和Save()方法手动将其序列化和反序列化为二进制缓冲区。
脚本对象变量的网络复制必须通过实现WriteNetworkUpdate()和ReadNetworkUpdate(()方法手动处理,这些方法还可以写入和读取二进制缓冲区。这些方法应写入/读取对象的所有复制变量。此外,每当复制的数据发生更改时,必须通过调用MarkNetworkUpdate()将ScriptInstance标记为网络复制。由于此复制机制不能对每个变量进行同步,但如果数据中有一位发生变化,则始终会发送整个二进制缓冲区,因此也可以考虑使用自动复制的节点用户变量。
延迟的方法调用可以在脚本对象中使用,以实现时间延迟的操作。在脚本对象代码中使用DelayedExecute()函数来添加稍后要执行的方法。参数包括延迟(以秒为单位)、重复标志、函数的完整声明以及可选的参数,这些参数必须放置在Variant数组中。例如:
class Test : ScriptObject
{
void Start()
{
Array<Variant> parameters;
parameters.Push(Variant(100));
DelayedExecute(1.0, false, "void Trigger(int)", parameters);
}
void Trigger(int parameter)
{
Print("Delayed function triggered with parameter " + parameter);
}
}
通过使用ClearDelayedExecute()函数声明,可以删除延迟的方法调用。如果将空声明(默认值)作为参数,则将删除所有延迟的调用。
如果被调用的方法具有void返回类型,并且没有参数,则也可以指定其名称而不是完整声明。
保存/加载场景时,还将保存和恢复任何挂起的延迟调用。
大多数Urho3D类都暴露于脚本,但需要低级别访问或高性能(如直接低级别渲染)的内容则不需要。此外,为了方便编写脚本,对C++API进行了一些更改:
检查自动构建的Scripting API文档以获取准确的函数签名。请注意,可以通过调用脚本子系统上的DumpAPI()函数或使用ScriptCompiler工具将API文档重新生成到Urho3D日志文件中。
除了在启动期间从源代码动态编译脚本之外,还可以将它们预编译为字节码,然后加载。为此,请使用ScriptCompiler实用程序。
如果.as文件不存在,脚本子系统将自动将脚本文件资源请求(.as)重定向到编译版本(.asc)。因此,对脚本化应用程序进行最终构建可能需要使用ScriptCompiler编译所有脚本,然后从构建中删除原始的.as文件。
脚本系统有一些复杂性需要注意:
所有脚本都可以访问全局VariantMap(globalVars)来存储共享数据或通过脚本文件重新加载来保存数据。
对Urho3D中的AngelScript进行了以下更改:
WeakHandle rigidBodyWeak = node.CreateComponent("RigidBody");
RigidBody@ rigidBodyShared = rigidBodyWeak.Get(); // Is null if expired
如果所讨论的对象不支持值赋值,则可以在没有@符号的情况下进行对象句柄赋值。从RefCounted派生的所有公开的Urho3D C++类都不支持值赋值。例如,当指定StaticModel组件的模型和材质时:
object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
在未修改的AngelScript中,这必须写成:
@object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
@object.material = cache.GetResource("Material", "Materials/Mushroom.xml");