当前位置: 首页 > 工具软件 > Urho3D > 使用案例 >

urho3D脚本

权韬
2023-12-01

要启用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()函数访问场景中的任何脚本对象。

将检查以下实现组件行为的方法。这些都不是必需的。

  • void Start()
  • void Stop()
  • void DelayedStart()
  • void Update(float)
  • void PostUpdate(float)
  • void FixedUpdate(float)
  • void FixedPostUpdate(float)
  • void Save(Serializer&)
  • void Load(Deserializer&)
  • void WriteNetworkUpdate(Serializer&)
  • void ReadNetworkUpdate(Deserializer&)
  • void ApplyAttributes()
  • void TransformChanged()

    上述更新方法对应于可变时间步长场景更新和后更新,以及固定时间步长物理世界更新和后升级。默认情况下不处理应用程序范围的更新事件。

    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返回类型,并且没有参数,则也可以指定其名称而不是完整声明。

    保存/加载场景时,还将保存和恢复任何挂起的延迟调用。

    脚本API

     大多数Urho3D类都暴露于脚本,但需要低级别访问或高性能(如直接低级别渲染)的内容则不需要。此外,为了方便编写脚本,对C++API进行了一些更改:

  • 模板数组和字符串类公开为array<type>和string。
  • 公开公共成员变量时不附加下划线。例如Vector3中的x、y、z。
  • 只要只需要一个参会被数,setter和getter函数就替换为财产。此类财产以小写字母开头。如果需要索引参数,将对属性进行索引。索引的财产是复数形式。
  • 数组和其他动态结构(如VariantMap和ResourceRefList)的元素计数属性称为“长度”,但相应的C++函数通常为Size()。
  • 子系统作为全局财产存在:时间、文件系统、日志、缓存、网络、输入、ui、音频、引擎、图形、渲染器、脚本、控制台、debugHud。
  • 还有其他全局财产用于访问脚本对象的节点、场景和场景范围组件:节点、场景、八叉树、physicsWorld、debugRenderer。当对象方法未执行时,这些值为空。一个例外:当通过调用SetDefaultScene()设置了立即执行的默认场景时,它始终作为“场景”可用。
  • 当前正在执行的脚本对象的ScriptInstance组件可通过全局属性self获得。
  • 当前正在执行的脚本文件可通过全局属性scriptFile获得。
  • 为节点创建的第一个脚本对象可用作其scriptObject属性。
  • 将原始输出打印到日志简单地称为Print()。通过调用log.Debug()、log.Info()、log.Warning()和log.Error()来访问其余的日志函数。
  • 采用StringHash参数的函数通常采用字符串。例如,发送事件、请求资源和访问组件。
  • 大多数StringUtils都公开为string类的方法。例如String.ToBol()。
  • 不支持按类型获取组件或资源的模板函数。而是根据需要执行自动类型转换。

    检查自动构建的Scripting API文档以获取准确的函数签名。请注意,可以通过调用脚本子系统上的DumpAPI()函数或使用ScriptCompiler工具将API文档重新生成到Urho3D日志文件中。

  将脚本预编译为字节码

    除了在启动期间从源代码动态编译脚本之外,还可以将它们预编译为字节码,然后加载。为此,请使用ScriptCompiler实用程序。

   如果.as文件不存在,脚本子系统将自动将脚本文件资源请求(.as)重定向到编译版本(.asc)。因此,对脚本化应用程序进行最终构建可能需要使用ScriptCompiler编译所有脚本,然后从构建中删除原始的.as文件。

   局限性

脚本系统有一些复杂性需要注意:

  • 在执行脚本对象的构造函数期间,该对象尚未与ScriptInstance关联,因此订阅事件、添加延迟的方法调用或尝试访问节点或场景都将失败。构造函数的使用最好只用于初始化成员变量。
  • 当最初对特定ScriptFile发出资源请求时,脚本文件及其包含的文件被编译为AngelScript脚本模块。每个脚本模块都有自己的类层次结构,除非这些类声明为共享,否则其他脚本模块无法使用该层次结构。有关详细信息,请参阅AngelScript文档。
  • 如果重新加载ScriptFile资源,则将销毁从中创建的所有脚本对象,然后重新创建。它们将丢失任何存储状态,因为它们的构造函数和Start()方法将再次运行。这在运行实际游戏时很少有用,但在开发过程中可能有用。

   所有脚本都可以访问全局VariantMap(globalVars)来存储共享数据或通过脚本文件重新加载来保存数据。

AngelScript修改

对Urho3D中的AngelScript进行了以下更改:

  •     出于性能原因,为了保证立即删除过期对象,已对脚本类和Array类型禁用了AngelScript垃圾收集。这样做的缺点是无法检测到循环引用。因此,每当您在脚本中有对象句柄时,请将它们视为C++共享指针,并避免使用它们创建循环引用。为了安全起见,请考虑使用WeakHandle值类型,它是一个暴露于脚本的WeakPtr<RefCounted>,可用于指向任何引擎对象(但不指向脚本对象)
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");

 类似资料: