AngelScript进阶用法:在代码中使用脚本类

DinS          Written on 2018/1/17

本文介绍如何在代码中使用脚本类,有些难度,确保阅读过《AngelScript进阶用法:在脚本中使用代码类》和其他基础文章。

回顾一下,我们已经了解了AS的整体框架、调用流程,可以注册函数和自定义类,并且可以做到脚本与代码的交互。
但是目前为止,重心都在代码。代码提供基础的概念,比如一些类和函数,然后由脚本定义一个函数去应用代码提供的内容。我们想要做的更多。我们希望脚本提供自定义的类,然后代码去调用。如果能够做到这一点,则可以说AS是全方位了,可以做到!

AS脚本里声明类的语法与c++很像,同时借鉴了java的一些写法,所以可以做到很好的兼容,而且类有关的功能基本上都可以做到,构造析构、重载运算符甚至是继承和多态。

使用脚本类的主流方式有两种。一个是将脚本类仅仅作为脚本中的一个过程,实际返回给程序的还是通过函数。
另一种方式是把脚本类直接返回给程序,然后程序去调用脚本类的成员函数。
显然第二种比第一种复杂。

一、停留在脚本中的脚本类

让我们先来试试简单的,感受一下脚本类的氛围。当然咱们增加一点难度,使用SDK提供的数组来实现某些功能,下面看一个脚本。

大图点这里

声明了一个类,大体上跟c++风格一致,不过有几点需要注意。
一个是private的声明方式,这个跟java更接近,每一个变量或函数前面都要加private或者protected,如果不加默认为public。当然其含义跟c++一致。
另一个是我们这里使用了array<T>,这是脚本提供的一个动态数组,用法跟vector一样。我也好奇为什么不直接叫vector?
这里出现了构造函数,跟代码写法一致。
脚本里没有unsigned类型,而是用uint表示unsigned int。

这里面出现了一个函数Generate…,实现在代码里:

脚本里不好直接用随机数,所以借助代码实现。脚本与代码,二者沟通是不成问题的。

我们在脚本里声明、定义了一个类,但是本小节的目的是只把类局限在脚本里,于是我们再提供一个函数用于交互。

大图点这里

真正在代码中调用的是这个函数,函数中用到了这个类。
写法与c++一致,与其说是脚本语言不如说是c++代码。

脚本就是这样了,看代码:

大图点这里

因为类被隐藏在脚本里,所以代码上看起来跟调脚本函数一致。

注意这里一个调试用的技巧。每一个返回值后面都跟一个assert这样方便排查bug。运行看结果:

试了几次,居然有猜中的,运气不错。

二、与代码交互的脚本类

让我们试试更加困难的任务,依然使用刚才的脚本,我们希望把Gamble类直接放到代码中并调用。

在这之前要说一个普遍的问题:代码如何知道脚本类的名字?
一般来说有几种方式:硬编码写到代码里,要求脚本类必须是这个名字;通过配置文件沟通代码和脚本类;SDK里的Builder有一个功能,可以获取脚本类的metadata。

当然现在我们知道脚本类叫Gamble,所以不存在这个问题。另外成员函数也存在这个问题,解决方法类似。

前面两个环节一样,代码也一样,区别在第三阶段:

点击看大图

整体流程就是context指定类和构造函数,然后执行,实例化脚本类,得到asIScriptObject,所有的脚本类在代码中都是这个。
有了这个对象后,调类不同的成员函数,在context中设置对象,这样引擎就知道调用的是哪个函数了。

获取脚本类是固定写法,照着写即可。

这里最难理解的可能是那个工厂函数。
首先明确一点,虽然在代码中出现了“Gamble@ Gamble(int n)”,但是脚本中并没有这样一个函数声明。这里的工厂函数,实质上就是告诉引擎用类的哪个构造函数来实例化。因此我们仿照这个格式,指定一个构造函数,就可以了。
但是还需要深究,工厂函数毕竟不是构造函数。引擎拿到了构造函数后,背后做了更多的工作,包括引用计数等。这就涉及到了前面那个返回值“Gamble@”。Gamble就是我们的脚本类,那个@是什么?我一直规避了这个问题,现在来说一说。

AS的脚本里是没有指针这个概念的,这很自然,脚本本来就要简单,搞出个指针没有太大意义。不过有些时候我们不希望拷贝大对象,在c++中通常的做法是const type &para,这里的@跟&的意思很接近。
AS的术语中,@称为object handle。An object handle is a type that can hold a reference to an object。当然@的作用不局限于避免拷贝,还有继承与虚函数相关。上一章注册类时,我们只演示了value type,另一个叫reference type,就跟这个@有关系。
研究这个东西超出了本专题的范围,感兴趣或者有需求的朋友可以自己阅读AS的手册,或者见《AngelScript高级概念:object handle与OOP》。

言归正传,从语义上来看,工厂函数的作用是调用类的某个构造函数,实例化该类的object handle并返回给程序。接下来我们获取这个handle并变成值,于是有了那么多个*,固定写法照着写即可。接下来是增加计数,因为AS有自己的内存管理,对于handle类型是靠计数来完成内存管理的,我们要用这个handle,就要增加计数。接下来调用成员函数就熟悉了,不细讲了,记得每次设置对象即可。

试试看运行结果:

第4位的元素确实随机,并且我们获取到了这个元素,成功!

三、脚本的robust性

顺便我们看看脚本提供的add-on的robust性如何,比如这样:

访问一个越界的元素,运行看效果:

还是很给力的,之所以能够输出这么具体,是因为我们使用了官网提供的一个简单的函数:

在代码里这么调用即可:

示意,具体的后续工作当然还要亲自处理。这个只是告知哪里出错了。

四、其他注意事项

说说AS还有哪些使用方式,纯指路,对于更加具体的内容遇到问题可以直接看官网的手册。

AS可以#include,引擎会自动处理,这样一来就跟写c++代码一模一样了。
可以将.as预先编译成byte code,一方面可以加快运行速度,另一方面可以起到加密的作用。
关于脚本语言本身的说明可见官网API:http://www.angelcode.com/angelscript/sdk/docs/manual/doc_script.html
add-on里有许多现成的东西可以拿来使用,减少工作量。

回顾一下AS的要点。
为了使用AS,我们需要在引擎(即解释器)中喂入各种符号,比如函数和类
然后就可以在AS的脚本中使用这些符号。另一方面,我们可以在AS脚本中定义类,然后在代码中间接或者直接使用。
理解了AS的这个框架,剩下的都是具体的细节问题:喂入符号的方式、传递参数的方式、获取返回值方式、调用脚本类成员函数方式等等。
使用AS时注意异常处理,脚本难免会发生错误。
SDK提供了许多add-on简化使用

综合而言,AS是针对c++的脚本,很好地完成了他的使命,虽然小众但是好用即可。

接下来将进入AS的高级特性,见《AngelScript高级概念:object handle与OOP》。