Skip to content

Latest commit

 

History

History
1687 lines (1206 loc) · 61.9 KB

脚本开发教程.md

File metadata and controls

1687 lines (1206 loc) · 61.9 KB

该页面 Wiki 仍处于构建状态

内容贡献者: 寂静的羽夏

**AngelScript脚本语言文档官网:**https://www.angelcode.com/angelscript/sdk/docs/manual/index.html

简介

  该软件使用的脚本引擎是 AngelScript 。AngelScript 是一种高性能的脚本语言,主要用于游戏开发和嵌入式应用程序。它的设计目标是易于集成、轻量级,并且具有类似于 C/C++ 的语法。AngelScript 允许开发者在应用程序中嵌入脚本,从而使得游戏逻辑、行为和其他动态元素可以在不重新编译整个程序的情况下进行修改。

  AngelScript 是一种专为嵌入式应用程序设计的脚本语言,具有以下特点:

  1. 易于集成:AngelScript 允许开发者将其嵌入到 C++ 应用程序中,提供灵活的脚本执行环境,支持对 C++ 类型的直接操作。
  2. 语法简洁:它的语法类似于 C/C++,对于熟悉这些语言的开发者来说,学习成本低,易于上手。
  3. 类型系统:AngelScript 是强类型语言,支持静态类型检查,减少了运行时错误,同时也支持类型推断。
  4. 面向对象编程:支持类、继承和多态,可以创建复杂的数据结构和功能。
  5. 模块化:支持模块化设计,开发者可以将代码组织成不同的模块,提高代码的可重用性和维护性。
  6. 内存管理:提供自动内存管理机制,减少内存泄漏的风险。
  7. 性能优化:AngelScript 经过优化,能够在大多数情况下提供接近 C++ 的执行性能,适合需要实时反馈的应用场景。
  8. 丰富的扩展性:可以通过 C++ 代码扩展其功能,开发者可以自定义数据类型和操作符,甚至可以创建新的脚本库。

  AngelScript 通常用于游戏引擎中,帮助开发者实现动态内容和扩展功能。

官方概览 AngelScript 构建在一个引擎周围,应用程序需要在这个引擎中注册脚本能够使用的函数、属性甚至类型。然后,脚本被编译成模块,应用程序可能根据需要有一个或多个模块。应用程序还可以通过使用访问配置文件向每个模块暴露不同的接口。当应用程序处理多种类型的脚本时,例如 GUI、AI 控制等,这一点尤其有用。

由于脚本被编译成字节码,AngelScript 还提供了一个虚拟机,也称为脚本上下文,用于执行这些字节码。应用程序可以同时拥有任意数量的脚本上下文,尽管大多数应用程序可能只需要一个。这些上下文支持暂停执行然后恢复,因此应用程序可以轻松实现并发脚本和协程等功能。脚本上下文还提供了一个接口,用于提取运行时信息,这对于调试脚本非常有用。

脚本语言基于众所周知的 C++ 语法,以及更现代的语言,如 Java、C# 和 D。任何对这些语言或具有类似语法的其他脚本语言(如 Javascript 和 ActionScript)有所了解的人,都应该能够轻松上手 AngelScript。与大多数脚本语言不同,AngelScript 是一种强类型语言,这允许代码执行更快,与宿主应用程序的交互更平滑,因为需要在运行时评估值的真实类型的频率会降低。

AngelScript 的内存管理基于引用计数,并使用增量垃圾收集器来检测和释放具有循环引用的对象。这提供了一个受控的环境,避免了应用程序冻结,因为垃圾收集器会介入释放内存。

入门

  该软件内置了一个简易但五脏俱全的小IDE,支持脚本的编写和调试功能。

在羽云十六进制编辑器中编写并运行脚本

点击上方的脚本工具栏-脚本编辑器

image-20241009160831085

点击脚本编辑器中的新建,并选择保存位置。

image-20241009160908102

然后就可以写AngelScript代码了。

我们输入如下代码

void main()
{
	print("Hello World");
}

点击调试器选项卡中的运行

image-20241009161059743

我们可以看到下方输出框输出了

Hello World

数据类型

基础数据类型

AngelScript中的基本数据类型:

  • void空类型:它只能用来告诉编译器函数不返回任何数据。

  • bool布尔类型:一种布尔类型,只有两个可能的值:truefalse

  • Integer numbers整型

    类型 最小值 最大值
    int8 -128 127
    int16 -32,768 32,767
    int -2,147,483,648 2,147,483,647
    int64 -9,223,372,036,854,775,808 9,223,372,036,854,775,807
    uint8 0 255
    uint16 0 65,535
    uint 0 4,294,967,295
    uint64 0 18,446,744,073,709,551,615
  • Real numbers实数型

    类型 值范围 最小正数 最大有效数字
    float +/- 3.402823466e+38 1.175494351e-38 6
    double +/- 1.79769313486231e+308 2.22507385850720e-308 15
  • array<类型> 数组, 有且只有一维数组

  • string字符串

  • obj object,带@指handle

  • dictionary 词典

  • enum 非变量,枚举

  • ref 需手动registe,如同object handle

  • weakref 与ref类似,会尽可能保持handle存活

扩展类型

语法

这是巴科斯-诺尔范式(BNF)中的语言语法。

使用以下约定:

  • ( ) - 用于分组
  • { } - 0次或多次重复
  • - 可选
  • | - 或
  • ’ ’ - 标记

声明变量

//声明变量
int num;

//可以为变量赋上同类型的初始值
int myvar = 2;

//加上关键词const声明变量为只读,该情况下变量将会被视作静态
const float pi = 3.14159f;

//使用@声明句柄对象,详见句柄一章
object@ handle = null;

//使用了未声明的变量,将会报错
strangObj = 2;//因未声明

当程序结束执行声明变量的代码块时,该变量将不再有效。

// 定义了一个全局变量`globalVar`,它将在整个程序运行期间持续存在。
int globalVar;

// 定义了一个局部变量`localText`,它将在函数执行结束时失去作用域。
void performAction()
{
    string localText;
}

// 定义了一个类成员`classPos`,它将在类实例被销毁时失效。
class Entity
{
    Vector classPos;
}

如果变量在声明时没有被初始化,将会出现以下情况:

// 基本数据类型的变量将具有不确定的值,这取决于它们被分配的内存区域。
// 对于新分配的内存区域,这些值通常为0或空字符串。
int uninitializedNumber; // 默认为0
string uninitializedText; // 默认为空字符串""

// 对象将使用其默认构造函数来初始化。
// 如果对象类型没有默认构造函数,则必须显式初始化。
// 有关更多信息,请参考对象相关章节。
Entity entity; // 使用Entity()构造函数初始化

// 引用将默认指向一个空对象。
Entity@ entityRef; // 默认为null

赋值语句

// 变量赋值
x = y;
// 函数调用
callFunction();

任何赋值函数调用都可以作为独立的语句写在一行

通常,这些语句不返回任何值(或者返回的值被忽略)。

赋值或函数调用必须以分号;结束,这是编译器识别语句结束的标志。

更多细节,请参考AngelScript的表达式处理规则。

赋值

赋值操作 使用等号=可以将右侧表达式的值赋给左侧的变量,这本质上是调用了左侧变量类型的赋值操作符。

leftExpr = rightExpr;
// 相当于调用了
leftExpr.opAdd(rightExpr);

在执行赋值时,会先计算右侧的表达式。

// 首先计算rValue2(),然后计算rValue1()
leftExpr = rValue1() + rValue2();

这意味着在赋值操作中,右侧的表达式会先于左侧被计算,确保在赋值时右侧的值是最新的。

表达式

条件判断结构 条件判断结构通常由if-else语句和代码块组成。

根据条件是否成立来决定执行哪个代码块。

可以串联多个if-else语句,以实现多条件的顺序选择。

if (checkCondition) {
    // 如果条件成立,则执行此代码块
} else if (anotherCondition) {
    // 否则,如果anotherCondition为真,则执行此代码块
} else {
    // 如果所有条件都不成立,则执行此代码块
}

选择结构 当条件由一个整数(无论是有符号还是无符号)控制时,使用选择结构比使用多个条件判断结构更为高效。

选择结构由switch-case和代码块组成,通过匹配整数值直接执行相应的代码块,这比顺序判断的if-else结构效率更高。

switch (value) {
    case 0:
        // 如果value等于0,则执行此代码块
        break;
    // case 1后面没有break,所以会执行下一个case的代码
    case 1:
    case specificValue:
        // 如果value等于1或specificValue,则执行此代码块
        break;
    default:
        // 如果value不匹配上述任何值,则执行此代码块
        break;
}

case后的值必须是常量,变量不能用作case的值。

除非你希望代码继续执行下一个case的代码,否则每个case代码块都应该以break语句结束。

循环

AngelScript循环有while do-while for三种不同的类型

while循环

while循环是在执行循环之前进行条件检查的循环

//循环会在执行之前检查是否满足条件
int i = 0;
while(i =< 5)
{
    i++;
}

do-while循环

do-while循环是一种先执行循环体,然后检查条件是否满足的循环结构。

int i = 0;
do {
    i++;
} while (i <= 1); // 注意:这里的条件应该是 i <= 1,而不是 i =< 1

for循环

for循环本质上是while循环的紧凑形式,它在执行循环体之前检查条件。

array<array<int>> list;
// for循环可以在循环结构中声明仅在循环体内有效的变量,并声明每次循环结束后执行的增量表达式
for (int i = 0; i <= 1; i++) {
    list[i] = {};
}
// 循环外变量将失效
int i = 2;

// for循环中可以声明多个变量,以逗号分隔
// 同样地,可以以逗号分隔多个增量表达式
for (int i = 0, j = 0; i < 10; i++, j++) {
    list[i][j] = 0;
}

循环控制

当循环条件过于复杂,或者需要外部灵活控制循环时,就需要使用循环控制语句。

中断(Break)

使用关键词break来中断循环。

// 无限循环
for (;;) {
    if (condition) {
        break; // 中断循环
    }
}

继续(Continue)

使用关键词continue来跳过当前循环的剩余部分,直接进入下一轮循环。

int i = 0;
while (i < 10) {
    i++;
    if (i == 5) {
        continue; // 跳过当前循环的剩余部分
    }
    print(i); // 打印i的值
}
// 输出结果为:1 2 3 4 6 7 8 9

返回语句

在编程中,返回语句return用于从函数中返回一个值,或者提前退出函数的执行。

返回值

对于非void类型的函数,必须使用return语句返回一个与函数声明类型相匹配的值。

// 返回浮点数值的函数`pi()`
float pi() {
    return 3.1415926f; // 返回圆周率的近似值
}

提前结束函数

对于void类型的函数,可以使用return语句不带任何返回值来提前结束函数的执行。

void Func() {
    // 满足条件时提前结束函数
    if (condition) {
        return; // 不返回任何值,直接退出函数
    }
    // 其他代码...
}

使用return提前退出函数是一种常见的控制流程手段,它允许函数在完成必要的操作后立即终止,而不是继续执行后续的代码。这种方式可以提高代码的效率和可读性。

代码块

代码块是一系列语句的集合。

每个代码块都有自己的作用域,这意味着在代码块内声明的变量在代码块外是不可见的。

{
    int x;
    float y;
    {
        // 在这个代码块内,变量x被重新声明为浮点数,覆盖了外部代码块的声明
        float x;
        // 在外部代码块中未被覆盖的变量仍然可见
        y = x;
        // 在这个代码块内声明的变量只在当前代码块内可见,一旦代码块结束,这些变量就会被销毁
        string z;
    }
    // 此时x将重新指向原先的int类型变量
    typeof(x);
    // z在当前作用域不可见,尝试访问将导致错误
    typeof(z);
}

异常处理块

当执行可能抛出异常的代码时,如果希望捕获这些异常并继续执行脚本而不是终止,应使用try-catch结构的代码块。

{
    try
    {
        PerformActionThatMightFail();
        // 如果没有异常发生,则跳过catch块
    }
    catch
    {
        // 如果发生异常,则执行这个代码块
    }
}

异常可能由多种原因引起,例如使用指向空值的引用、除以零或无效的注册等。

在某些情况下,脚本可能会故意抛出异常来中断某些操作。

函数

函数调用

函数可以被调用来执行一个动作

func();

函数可能带有一个返回值

int func()
{
    //这个函数返回整数1
    return 1;
}

如果函数使用了多个参数 那么优先从右侧参数开始计算

void func (int arg1, int arg2, int arg3)
{
    //魔法代码
}
//优先计算3+3,其次2+2,最后1+1
func(1+1, 2+2, 3+3);

out参数声明的一些函数以返回多个值。调用这些函数时,必须将输出参数作为可返回值赋值的表达式。 如果不需要输出值,则使用参数void来显式放弃其值

实际上,这并不是传址,只是在函数结束时对表达式再次赋值,AS不存在传址的概念

//这个函数在输出参数中返回一个值
void func(int &out outputValue)
{
    //在函数结束之前,必须对所有有out标记的变量赋值
    outputValue = 42;
}
//调用函数时使用有效的左值表达式来接收函数值
//此时value初始化为0
int value = 0;
//此时value被赋值为42
func(value);
//使用关键词void来显式放弃返回值
func(void);

还可以将函数赋值给变量

lvalue = func();

参数也可以被命名,并且直接传递到特定参数,而不是按照参数的命名顺序传递

位置参数不可以跟随在命名参数后

void function(bool flagA = false; bool flagB = false; bool flagC = false)
{
    //优雅
}
//调用函数时可以通过命名直接传递
function(flagC: true);

运算符

运算符指对变量进行相应操作并返回新值的符号

数学运算符

数学运算符是用来表示数学运算的符号

运算符 描述 左侧 右侧 结果 举例 举例结果
+ 一元正 数字型 数字型 +(-1) 1
一元负 数字型 数字型 -(+1) -1
+ 加法 数字型 数字型 数字型 1+1 2
减法 数字型 数字型 数字型 1-1 0
* 乘法 数字型 数字型 数字型 3*3 9
/ 除法 数字型 数字型 数字型 9/3 3
% 模数 数字型 数字型 数字型 3%2 1
** 指数 数字型 数字型 数字型 3**2 9

加和减也可以用作一元运算符,实际上,他们的符号是完全一样的

数字型可代表任何数字类型,列如intfloat

双操作的左右侧两个变量将会被隐式转化成相同的类型,最终结果与被赋值的变量相同

int imCool;
//2.0f将会被隐式转化为2,最终返回结果为int(1+2) = 3
imCool = 1 + 2.0f;
float imSuperCool;
//1将会被隐式转化为1.0f,最终返回结果为float(1.0f + 2.0f) = 3.0f
imSuperCool = 1 + 2.0f

一个例外是一元负运算符,不适应于任何无符号类型

//在进行一元运算前变量必须拥有值
uint noSign = 3;
//报错,无符号数不能进行一元负运算
-noSign;

位运算符

位运算符是表示对数字进行位运算的符号

运算符 描述 左侧 右侧 结果 举例 举例结果
~ 按位补 数字型 数字型 ~2 1
& 按位和 数字型 数字型 数字型 2&5 0
| 按位或 数字型 数字型 数字型 2|5 7
^ 按位异或 数字型 数字型 数字型 4^6 2
<< 逻辑左移 数字型 数字型 数字型 2<<5 64
>> 逻辑右移 数字型 数字型 数字型 5>>2 1
>>> 算术右移 数字型 数字型 数字型 8>>>2 2
<<< 算术左移 数字型 数字型 数字型 2<<<8 512

除按位补外均为双操纵符

在进行计算之前,左右侧两个数字都将转化为整数,同时保留原始符号

结果将与左侧数字相同

uint8 leftNum = 4;
float rightNum = 2.0f;
//4将保留无符号整数,2.0f将被隐式转化为有符号整数2进行计算,计算结果将隐式转化为无符号短整数(uint8)类型;
leftNum | rightNum;
//同上,但计算结果将转化为浮点数(float)
rightNum & leftNum;

复合运算符

复合运算符指上述任意运算符与=的组合

//此式首先将leftVal赋值给寄存器,再将1赋值给寄存器,最后将寄存器内数值相加赋值回leftVal
leftVal = leftVal + 1;
//此式将1赋值给寄存器,再将leftVal数值直接与寄存器相加
leftVal += 1;

上述两个式子结果完全一致,而第二个式子将会有更好的运行效率(如果左侧式子本身是复杂的表达式,那么可以有所不同)

可用的复合运算符: += -= *= /= %= **= &= |= ^= <<= >>= >>>=

逻辑运算符

逻辑运算符指将两个布尔型量进行运算并放回新布尔值的运算符号

运算符 等同符号 描述 左侧 右侧 结果 举例 举例结果
not ! 逻辑不 布尔型 布尔型 not true false
and && 逻辑与 布尔型 布尔型 布尔型 true and false false
or || 逻辑或 布尔型 布尔型 布尔型 true or false true
xor ^^ 逻辑异或 布尔型 布尔型 布尔型 true xor false true

逻辑运算符多用于ifwhile结构内

if (a and b)
{
    //吃饭
}
else if (b || c)
{
    //睡觉
}
else
{
    //打豆豆
}

比较运算符

等式比较运算符

通过比较两个值数值是否相等从而返回布尔值的运算符

运算符 描述 左侧 右侧 结果 举例 举例结果
== 相等 数字型 数字型 数字型 1 == 2 false
!= 不等 数字型 数字型 数字型 1 != 2 true

关系比较运算符

通过比较两个值数值是否符合关系从而返回布尔值的运算符

运算符 描述 左侧 右侧 结果 举例 举例结果
> 大于 数字型 数字型 数字型 1 > 2 false
< 小于 数字型 数字型 数字型 1 < 2 true
>= 大于或等于 数字型 数字型 数字型 1 >= 1 true
<= 小于或等于 数字型 数字型 数字型 1 <= 2 true

身份比较运算符

引用类型专用运算符(如句柄),比较两个引用对象是否相同

运算符 描述 左侧 右侧 结果 举例 举例结果
is 相等 数字型 数字型 数字型 @a is @a true
!is 不等 数字型 数字型 数字型 @a !is @b true
if( a is null )
  {
    // ... 执行代码
  }
  else if( a is b )
  {
    // ... 执行代码
  }

增量运算符

表达式中使用该值之前或之后以1递增/递减其值的运算符

运算符 描述 左侧 右侧 结果 举例 举例结果
++ 之后增量 数字型 数字型 1++ 2
之后减量 数字型 数字型 1– 0
++ 之前增量 数字型 数字型 ++1 2
之前减量 数字型 数字型 –1 0
//使用增量运算符之前变量必须拥有初始值
int8 value = 0;
//之后增量,首先将value赋值给寄存器,接着在将寄存器数值加一,最后将寄存器赋值回value
value++;//此时value为1
//之前减量,直接在value地址上减一
--value;//此时value为0

索引运算符

用于访问对象内包含的元素的运算符 符号为变量后添加[无符号整数]

array<int> arr = {5,6,7,8};
arr[0]; //5
arr[2]; //7
arr[1337]; //报错,超出数组最大长度
arr[4.2f]; //报错,索引必须为整数
arr[-13]; //报错,索引必须无符号
int i = 2;
arr[i]; //报错,索引必须显式转化为无符号

条件表达式

choose ? a : b;
//choose的条件为真则返回a, 条件为假则返回b

如果ab不是统一类型,将会进行隐式转换

如果a0null,将会试图将a转化为b类型,否则将b转换为a类型

如果a b之间没有隐式转化方法,将会报错

int a = 0;
float b = 1.0f;
//隐式转换0为0.0f
true ? a : b;

同样,如果a b类型相同,条件表达式还可以用作左值

int a,b;
//将会为b赋值1337
(false ? a : b) = 1337;

成员访问

如果某个对象是一个类型的实例化,那么可通过.符号范围其成员

class CExample
{
    int property;
    void method()
    {
        //做点什么....
    }
}

CExample object;
//property为可get或set的成员
object.property = 1;
//method为可在对象上调用的成员方法
object.method();

句柄

句柄是对对象的引用,多个句柄可以应用同一个对象

使用@符号标识一个句柄对象

哪些对象有句柄

基本数据类型(boolintfloat等)都不能具有句柄 注册类型时标记为不允许拥有句柄的类型也不能有句柄

声明句柄

通过在变量类型后加@符号来声明一个句柄

object@ jubing;

这样就可以声明一个句柄 句柄在声明时默认赋值为null,即这个句柄没有引用任何对象

使用句柄

使用句柄和使用物理变量的方法一致,但是句柄无法保证已经引用了变量(句柄可能为null),从而引起程序抛出异常,所以在使用句柄之前切记进行判断,或者你可以保证这个句柄永远不为空

//这将声明一个物理对象
object obj;
//这将声明一个句柄
object@ objHandle;
//两个方法作用完全相同
//均调用obj.Add()
obj.Add();
objHandle.Add();
//但是objHandle可能为空,因此最好进行判断
if(objHandle is null)
{
    //balbalba
}
else
{
    objHandle.Add();
}

句柄赋值

当句柄对象直接赋值时,他将指向所引用的对象

CApple apple;
//要操作实际的句柄对象,应该在表达式前加上@
CApple@ handle = @apple;
handle = apple;
//该式完全等同于
apple = apple;
//当句柄引用对象为空,将抛出异常
CApple@ badHandle;
badHandle = apple;

常量句柄

有时需要使用一直保持引用而不被修改的句柄,在句柄前加上const前缀即可

CApple@ rewritalbeApple;
const CApple@ stableApple;

Copy

不可修改对象的句柄既可以引用可修改对象,也可以引用不可修改对象 但是脚本不允许通过该句柄修改对象 也不允许将该句柄传递给另一个允许修改的句柄

另外可以在类型后添加后缀const声明只读句柄

//这是一个可修改对象的只读句柄
CApple@ const readonlyApple;
//这是一个不可修改对象的只读句柄
const CApple@ const ironApple;

部分情况下,解释器可以隐式确定需要的是对象句柄,而不是句柄的本身,此时可以不在表达式前添加@

当没有任何句柄引用对象时,对象才会销毁(详见AngelScripts 对象句柄文章

通过句柄访问引用对象的方式与直接访问引用对象的方式相同

什么时候使用句柄: 对象本身过大或过于复杂,直接复制对象值会导致寄存器占用增加时 需要对象数据双向流通或单向流通时

//TODO LIST 待更新....

括号

括号用于标识表达式的运算优先值,括号内外部分将会分别以运算优先表运算(见附录)

//优先计算1+b,再将结果与b相乘
a = b * (1 + b);
//优先计算b或c,再将结果与a相和
if(a && (b || c))
{
    //出事了
}

范围解析

范围解析运算符::,当本地变量或函数与全局变量或函数重载时,指定使用的变量和函数的命名空间的运算符

//命名空间为空时,指全局命名空间
命名空间::变量;

举例

int value; //标记为[0]

namespace NSecret
{
    int value; //标记为[1]
}

void function()
{
    int value; //标记为[2]

    //指[2]被赋值1
    value = 1;
    //指[0]被赋值2
    ::value = 2;
    //指[1]被赋值3
    NSecret::value = 3;
}

类型转换

基础类型转换

不支持对象句柄的基础类型可用强制转换,此时将构造一个新值或创建一个新实例 显式强制转换方法

类型(被转换变量名);
//隐式强制转换
int a = 1.0f;
//显式强制转换
float b = float(a) / 2;

句柄类型转换

可以使用强制转换运算符将对象句柄转换为其他对象句柄。如果强制转换有效,则返回目标类型对象句柄,否则返回空句柄(null) 显式转换方法

cast<目标类型>(句柄对象);
//隐式转换CAmmo句柄为CWeapon句柄
CWeapon @a = @CAmmo();
//显式转换
CAmmo @b;
CWeapon @a = cast<CWeapon@>(b);

通过上述方法转换句柄时,句柄的引用对象和指向的对象并没有变化,只是通过不同的类型公开不同接口

详见AngelScripts 对象句柄文章

匿名对象

匿名对象即未声明为变量而创建的对象

匿名对象可以在表达式中实例化,方法是调用该对象的构造函数,就好像它是一个函数一样

//"Hello World"即是一个匿名字符串对象
g_Game.AlertMessage( at_console, "Hello World" );
//通过构造函数创建一个匿名对象
typeof(CExample());
//字典类显式初始化,此时该字典是一个匿名对象
function(dictionary = {
    {"banana", 1},
    {"orange", 2},
    {"apple", 3}
});
//当只有一种可能类型的初始化列表时,可以省略其类型标记,编译器可以隐式的确定其类型
resortArray({5, 6, 7, 8});

匿名对象使用后即释放,除非有句柄保持了对其的引用

注释

注释与其他语言一样,你可以使用//进行单行注释,也可以使用/**进行多行注释

void NB()
{
    //这是单行注释
    /***
        这是多行注释
    ***/
}

共享实例

//TODO LIST 待更新....

索引

标准库相关

**官网地址:**https://www.angelcode.com/angelscript/sdk/docs/manual/doc_script_stdlib.html

这里描述了AngelScript SDK提供的标准库。使用AngelScript的应用程序可能会也可能不会向脚本公开标准库。有关它公开的API的信息,请始终查阅应用程序手册。

异常

异常是AngelScript中一个特殊的数据类型,很明显你无法显式操作一个异常

try-catch代码块中,catch部分默认隐式传递了一个异常参数

注意

throwgetExceptionInfo只有程序注册了他们才能使用

函数

void throw(const string &in exception)

显式抛出一个异常,输入的字符串应该位能标识异常类型的身份,用于记录或异常捕获

string getExcetionInfo()

获得上一个抛出的异常的字符串信息

列如

void BadCode()
{
    CApple@ obj = null;
    try
    {
        //obj为空,将会抛出一个异常
        obj.Eat();
    }
    catch
    {
        //默认已经传递了一个隐式的异常信息

        //将会获得 “Null Pointer Access”
        getExcetionInfo();
    }
}

void WeridCode()
{
    try
    {
        throw("人为抛出的异常");
    }
    catch
    {
        //将会获得 “人为抛出的异常”
        getExcetionInfo();
    }
}

异常可能由于各种原因发生,例如,访问空句柄,0作为除数,或应用程序注册函数引发的异常

在某些情况下,脚本故意引发异常以中断某些执行


字符串

注意

只有注册了字符串类型的程序才能在脚本中使用字符串,由于注册方法或注册内容的不同,不同程序使用字符串的方法可能会有所区别

标准库中的字符串(string)是基础类型中的字符串的补充,主要为字符串类型添加了一些列的类方法

关于字符串的定义,请查看数据类型-字符串

运算符

名称 符号 描述 短例
赋值 = 赋值运算符将右侧字符串的内容复制到左侧字符串中, 右值可以是基础数据类型中的任意一种,赋值时将会对其进行隐式转换 string1 = string2 string1 = int2
连接 + += 连接运算符将右侧字符串的内容追加到左侧字符串的末尾,如同赋值,右值可以是任意基础数据类型 string1 = string 2 + string3 string1 += int2
相等 == != 相等运算符用于比较两个字符串是否相等 string1 == string2
比较 < > <= >= 比较运算符用于比较两个字符串内容,注意,比较运算符是对字符串中字符的字节值进行比较,不是对字符串中的字母顺序进行比较 "A" > "B" 等同于 65 > 66
索引 [] 索引运算符允许访问字符串中的单个字符 "Hello World"[2] == ‘l’

方法

方法 描述 短例 结果
uint length() const 返回字符串的长度 Hello World.length() 11
void resize(uint) 重设字符串的长度 "Hello World".resize(5) "Hello"
bool isEmpty() const 返回字符串是否为空 "Hello World".isEmpty() false
string substr(uint start = 0, int count = -1) const 返回起始位置向后指定长度的子字符串 "Hello World".substr(6,4) "World"
void insert(uint pos, const string &in other) 将另外一个字符串插入字符串指定位置 "Hello World".insert(5, "This ") "Hello This World"
void erase(uint pos, int count = -1) 从指定位置删去指定数目的字符 "Hello World".erase(5,4) "Hello"
int findFirst(const string &in str, uint start = 0) const 从指定位置左往右找到第一个和指定字符串对应的子字符串位置, 找不到将返回-1 "Hello World".findFirst("l",0) 2
int findLast(const string &in str, int start = -1) const 从指定位置右往左找到第一个和指定字符串对应的子字符串位置, 找不到将返回-1 "Hello World".findFirst("l",-1) 8
int findFirstOf(const string &in chars, int start = 0) const 从指定位置左往右找到第一个和指定字符串对应的子字符串或字符的位置, 找不到将返回-1 "Hello World".findFirstOf("l",0) 2
int findLastOf(const string &in str, int start = -1) const 从指定位置右往左找到第一个和指定字符串对应的子字符串或字符的位置, 找不到将返回-1 "Hello World".findLastOf("l",-1) 8
int findFirstNotOf(const string &in chars, int start = 0) const 从指定位置左往右找到第一个和指定字符串对应的子字符串或字符的位置, 找不到将返回-1 "Hello World".findFirstNotOf("H",0) 1
int findLastNotOf(const string &in chars, int start = -1) const 从指定位置右往左找到第一个和指定字符串对应的子字符串或字符的位置, 找不到将返回-1 "Hello World".findLastNotOf("d",-1) 9
array@ split(const string &in delimiter) const 根据指定的子字符串将字符串拆分为字符串数组 "Hello World".split(" ") {"Hello", "World"}

函数

函数 描述 短例 结果
string join(const array &in arr, const string &in delimiter) 将数组中的字符串连接成由分隔符分隔的大字符串 join({"Hello", "World"}, " ") "Hello World"
int64 parseInt(const string &in str, uint base = 10, uint &out byteCount = 0) 以基值(10或16)对字符串进行分析获取整数值,最后可将字符串长度输出 parseInt("0x1", 16) 1
uint64 parseUInt(const string &in str, uint base = 10, uint &out byteCount = 0) 以基值(10或16)对字符串进行分析获取整数值,最后可将字符串长度输出 parseUInt("1337", 10) 1337
double parseFloat(const string &in, uint &out byteCount = 0) 对字符串进行分析获取浮点值,最后可将字符串长度输出 parseFloat("13.21F") 13.21f
string formatInt(int64 val, const string &in options = ”, uint width = 0) format函数接受一个字符串,该字符串定义数字的格式 formatInt(1) "1"
string formatUInt(int64 val, const string &in options = ”, uint width = 0) format函数接受一个字符串,该字符串定义数字的格式 formatUInt(1,"0", 4) "1000"
string formatFloat(double val, const string &in options = ”, uint width = 0, uint precision = 0) format函数接受一个字符串,该字符串定义数字的格式 formatFloat(2.2f,"+","4") "+2.2000"

format函数可接受的option

字符 描述 短例 结果
| 左对齐 formatInt(1, ‘l’, 10) " 1"
0 以0填充 formatInt(1, ‘0l’, 10) "0000000001"
+ 正数前添加+号 formatInt(1, ‘+’) "+1"
space 正数添加空格 formatInt(1, ‘space’) "1 "
h 以小写输出为16位数(不支持float) formatInt(10, ‘h’) "0xa"
H 以大写输出位16位数(不支持float) formatInt(10, ‘H’) "0xA"
e 以科学计数法小写输出数字(仅支持float) formatFloat(10.1f, ‘e’) "1.01e1"
E 以科学计数法大写输出数字(仅支持float) formatFloat(10.1f, ‘E’) "1.01E1"

数组

注意

只有注册了数组类型的程序才能在脚本中使用字符串,由于注册方法或注册内容的不同,不同程序使用数组的方法可能会有所区别

声明数组(array)类型可如同普通基础数据类型一样,不同的是尖括号内应该包含数组元素的类型

//声明整数数组
array<int> a, b,c;
//数组元素可为句柄
array<CApple@> d;

除此之外,还有多种方法声明数组

//声明一个元素为0的整数数组
array<int> a;
//声明一个有三个默认初始值的整数数组
//该数组等同于{0, 0, 0}
array<int> b(3);
//声明一个有三个以1为初始值的整数数组
//该数组等同于{1, 1, 1}
array<int> c(3,1);
//声明一个拥有确切元素的数组
array<int> d = {1, 2, 3};

同样的,数组也可以成为数组的元素,可以此来声明交错数组

AngelScript 不存在多维数组,只有交错数组

//一个元素为0的空整数数组的数组
array<array<int>> e;
//一个有两个确切元素的数组数组,元素分别为{1,2}和{3,4}
array<array<int>> f = {{1, 2}, {3, 4}};
//一个有十个数组元素的数组,该数组共包含10 x 10个0
array<array<int>> g(10, array<int>(10));
//一个有十个以整数数组为元素的数组为元素的数组,该数组共包含10 x 10 x 10个0
array<array<array<int>>> h(10, array<array<int>>(10, array<int>(10)));
//你也可以继续套娃下去实现更高维的数组.....

访问数组中的元素可以使用索引运算符,索引的范围为0到数组长度 – 1 以上数组为例

//访问数组元素
b[0] = c[1];
//访问交错数组
f[0][1] == g[1][5];
//访问更高维交错数组
h[1][2][3];

同样的,当数组元素为句柄时,可以通过数组访问对象成员

array<CApple@> apples(1);
//将第一个数组元素指向一个新实例化的对象
@apples[0] = CApple();
//通过数组访问对象
apples[0].Eat();

数组可以通过使用花括号的形式初始化为匿名对象

Function({-1, -2, -3, -4, -5});
//当匿名对象有多种类型可以对应时,你需要显式描述匿名对象的类型
//此时将会抛出错误,因为这个匿名对象可以对应array<uint>和array<int>两种类型
Function({1, 2, 3, 4});
//你需要如此显式描述类型
Function(array<int> = {1, 2, 3, 4});

运算符

名称 符号 描述 短例 结果
赋值 = 将右侧数组值复制给左侧数组 a = {5, 6, 7} {5, 6, 7}
索引 [] 通过索引访问数组元素 a[1] 6
相等 == != 比较两个数组元素是否一致 {1, 2, 3} == {5, 6, 7} false
句柄 @ 获取数组的对象句柄 @a = @{1,2,3} a[1] = 2

方法

方法 描述 短例 结果
uint length() const 返回数组元素数 {1,2,3}.length() 3
void resize(uint) 重设数组长度 {1,2,3}.resize(2) {1,2}
void reverse() 反转数组元素顺序 {1,2,3} {3,2,1}
void insertAt(uint index, const T& in value) 在指定位置插入一个元素 {1,2,3}.insertAt(1,1) {1,2,1,3}
void insertAt(uint index, const array<\T>& arr) 在指定位置插入一个数组 {1,2,3}.insertAt(1,{1,2,3}) {1,2,1,2,3,3}
void insertLast(const T& in) 在数组最后添加一个元素 {1,2,3}.insertLast(4) {1,2,3,4}
void removeAt(uint index) 移除数组指定位置的元素 {1,2,3}.removeAt(0) {2,3}
void removeLast() 移除数组最后一个元素 {1,2,3}.removeLast() {1,2}
void removeRange(uint start, uint count) 移除数组指定范围的元素 {1,2,3}.removeRange(1,2) {1}
void sortAsc() 升序排序数组, 需要数组元素类型拥有opCmp方法 {2,1,3}.sortAsc() {1,2,3}
void sortAsc(uint startAt, uint count) 升序排序范围内的数组, 需要数组元素类型拥有opCmp方法 {2,1,4,3}.sortAsc(1,3) {2,1,3,4}
void sortDesc() 降序排序数组, 需要数组元素类型拥有opCmp方法 {2,1,3}.sortAsc() {3,2,1}
void sortDesc(uint startAt, uint count) 降序排序范围内的数组, 需要数组元素类型拥有opCmp方法 {2,1,4,3}.sortAsc(1,3) {2,4,3,1}
void sort(const less &in compareFunc, uint startAt = 0, uint count = uint(-1)) 此方法接受一个回调函数作为输入,用于在排序数组时比较两个元素 表后附 表后附
int find(const T& in) 获得第一个与传入元素相等的元素索引, 不存在时返回-1, 要求元素类型拥有opEquals或opCmp方法 {1,2,3}.find(2) 1
int find(uint startAt, const T& in) 在指定范围内获得第一个与传入元素相等的元素索引, 不存在时返回-1, 要求元素类型拥有opEquals或opCmp方法 {1,2,3,}.find(1,1) -1
int findByRef(const T& in) 将通过比较地址而不是值来搜索数组中的元素, 不存在时返回-1 {a = 1, b = 2, c = 3}.findByRef(2) -1
int findByRef(uint startAt, const T& in) 将通过比较地址而不是值来搜索数组指定范围中的元素, 不存在时返回-1 {a = 1, b = 2, c = 3}.findByRef(1,b) 1

附 sort方法的使用

回调函数应将同一类型数组元素的两个引用作为参数,并应返回bool。如果第一个参数应放在第二个参数之前,则返回值应为true

array<int> arr = {3,2,1};
arr.sort(function(a,b) { return a < b; });

如要在匿名对象中使用sort

bool lessForInt(const int &in a, const int &in b)
{
    return a < b;
}
bool lessForHandle(const obj @&in a, const obj @&in b)
{
    return a < b;
}
void sortArrayOfInts(array<int> @arr) { arr.sort(lessForInt); }
void sortArrayOfHandles(array<obj@> @arr) { arr.sort(lessForHandle); }

字典

注意

只有注册了字典类型的程序才能在脚本中使用字符串,由于注册方法或注册内容的不同,不同程序使用字典的方法可能会有所区别

字典(dictionary)存储键值对,其中键是字符串,值可以是任何类型

dictionary a = {{"int", 1}, {"boolean", true}, {"float", 2.2f}};

可以动态地添加或删除字典中的键值对

a.set("string", "Hello World");
a.delete("int");

可以通过索引运算符访问字典中的元素

a["boolean"] = false;
//获取字典对象值时需要显式转换
int val = int(a["int"]);
CApple@ apple = cast<CApple>(a["apple"]);

字典可被作为匿名对象

//初始化匿名对象字典时,必须显式说明类型,因为字典的初始化方法和二维交错数组是一样的
//报错报错
foo({{"a",1},{"b",2}});
//正确
foo(dictionary = {{"a", 1}, {"b", 2}});

同样的,可以通过匿名对象初始化一个以字典作为键值对中的值的字典

dictionary d2 = {{'a', dictionary = {{'aa', 1}, {'ab', 2}}}, 
                {'b', dictionary = {{'ba', 1}, {'bb', 2}}}};

方法

方法 描述 短例 结果
void set(const string &in key, ? &in value) 为字典设定一个键值对,如果已存在将覆盖已有的,否则将新建一个键值对 a.set("boolean", true) N/A
bool get(const string &in key, ? &out value) const 从字典中获取一个值到指定变量中,键值不存在时将返回false,并保持指定变量的初始值 a.get("float", b) b = 2.2f
array @getKeys() const 将返回字典中的所有键为一个字符串数组,该数组的顺序不定 a.getKeys() {"int", "boolean", "apple", "float"}
bool exists(const string &in key) const 将返回键值是否存在 a.exists("array") false
bool delete(const string &in key) 删除指定的键值对,键值不存在将返回false a.delete("int") true
void deleteAll() 情况字典所有键值对 a.deleteAll() {}
bool isEmpty() const 返回该字典是否为空 a.isEmpty() false
uint getSize() const 返回字典中键值对的数目 a.getSize() 4

字典值对象

字典值对象(dictionaryValue)是字典储存值的类型,当通过索引访问字典时返回的都是字典值对象

字典值对象类型不可以拥有句柄,但是他可以储存句柄

运算符

名称 符号 描述 短例
赋值 = 将右值复制给左值 dict[0] = dict[1]
句柄赋值 将右值储存对象的句柄复制给左句柄 @apple = @dict[2]
转换 cast<\T> 将字典值对象储存的句柄转换为需要的句柄 cast(dict[2])
转换 ?any() 将字典值对象储存的值转换为需要的类型 int(dict[0])

引用

注意

只有注册了引用类型的程序才能在脚本中使用字符串,由于注册方法或注册内容的不同,不同程序使用引用的方法可能会有所区别

引用类型(ref)被用于处理任何句柄,通常情况下我们储存句柄时必须要知道确切的句柄类型,这让程序编写时增添了非常多的困难,因此有了引用类型来处理所有的句柄引用

例如

//有两个完全不相干的类
class CFood() {};
class CCar() {};

//我想在某个函数中处理这两个类
//传统做法需要为其新建两个不同的重载
//费事费力,浪费空间影响版面
void Cut(CFood@ food) { }
void Cut(CCar@ car) {}
//使用引用类型用一个函数处理两种类
void Cut(ref@ handle)
{
    //首先需显式转换为目标类型
    CFood@ f = cast<CFoold>(handle);
    //当引用不是所需的类型时,将返回空,因此需要空句柄判断
    if(fis null)
    {
        //?
    }
    else
    {
        CCar c = cast<CCar>(handle);
        if(c !is null)
            //?
        else
            //?
    }
}

声明引用时可以同对象句柄一样声明

ref@ r  = CCar();

运算符

名称 符号 描述 短例
句柄赋值 @= 将右值引用复制给左引用 @ref1 = @ref2
身份判断 is !is 判断两个引用是否指向同一对象 ref1 is ref2
转换 cast<\T> 将引用转换为需要的句柄 cast(ref)

弱引用

注意

只有注册了弱引用类型的程序才能在脚本中使用字符串,由于注册方法或注册内容的不同,不同程序使用弱引用的方法可能会有所区别

弱引用(weakref)是用于那些句柄可能会随时被移除的对象的引用

强引用将会保持对象在堆栈中的存在,而使用弱引用在对象生命周期结束后将自动指向null

弱引用可以使用weakrefconst_weakref来声明

class CApple {}
//声明一个对对象的强引用
CApple @obj1 = CApple();

//保持对对象的弱引用
weakref<CApple> r1(obj1);

//保持对只读对象的弱引用
const_weakref<CApple> r2(obj1);

//只要还有对该对象的强引用,弱引用就能返回该对象
CApple @obj2 = r1.get();

//所有对对象的强引用都被移除,弱引用将会指向null
@obj1 = null;
@obj2 = null;
const CApple @obj3 = r2.get();
//obj3为空

运算符

名称 符号 描述 短例
句柄赋值 @= 将右值引用复制给左引用 @weak1 = @weak2
值赋值 = 将右侧弱引用对象的值赋值给左侧引用对象 weak1 = weak2
身份判断 is !is 判断两个引用是否指向同一对象 weak1 is weak2
转换 cast<\T> 将引用转换为需要的句柄 cast(weak1)

方法

方法 描述
T@ get() const 这与cast运算符的作用完全相同,只是一种更明确的写作方式

日期

注意

只有注册了日期类型的程序才能在脚本中使用字符串,由于注册方法或注册内容的不同,不同程序使用日期的方法可能会有所区别

日期(datetime)类型表示日历日期和时间。它可以用于对日期进行数学运算,例如比较两个日期、确定日期之间的差异以及增加/减去日期以形成新日期。

它还可以用于获取当前系统时间,从而允许测量任务的时间,精度仅为秒

构造函数

//初始化对象为当前系统日期
datetime();
//初始化对象为另一日期对象日期
datetime(const datetime &in other);
//初始化日期对象为指定的日期
datetime(uint y, uint m, uint d, uint h = 0, uint mi = 0, uint s = 0);

方法

方法 描述
uint get_year() const property 返回年
uint get_month() const property 返回月
uint get_day() const property 返回日
uint get_hour() const property 返回时
uint get_minute() const property 返回分
uint get_second() const property 返回秒
bool setDate(uint year, uint month, uint day) 设定日期,输入数字无效时返回false
bool setTime(uint hour, uint minute, uint second) 设定时间,输入数字无效时返回false

运算符

名称 符号 描述
赋值 = 将右值复制给左值
差异 从左值中减去右值,返回秒数
+ +=
– -=
相等 == != 比较两个日期是否相同
比较 < > <= >= 比较两个日期

协程

注意

只有注册了协程类型的程序才能在脚本中使用字符串,由于注册方法或注册内容的不同,不同程序使用协程的方法可能会有所区别

协程用于多线程处理多个任务

函数

funcdef void coroutine(dictionary@)
void createCoRoutine(coroutine @, dictionary @)

此函数用于创建协程。协程将在产生状态下启动,也就是说,它只在当前线程将控制权交给它时才开始执行

可以创建多个协程,它们将轮流以循环方式执行

void yield()

为队列中的下一个协程生成执行控制。

当一个共协程收到控制时,它将从最后一个调用恢复执行,如果这是第一次允许协程执行,则返回入口点


文件

注意

只有注册了文件类型的程序才能在脚本中使用字符串,由于注册方法或注册内容的不同,不同程序使用文件的方法可能会有所区别

文件(file)类型用于处理通过IO的文件读取或保存

例如

file f;
// 以读取模式读取文件
if( f.open("file.txt", "r") >= 0 ) 
{
    //读取整个文件进入一个字符串中
    string str = f.readString(f.getSize()); 
    f.close();
}

方法

方法 描述
int open(const string &in filename, const string &in mode) 打开文件,模式r表示读取,w表示写入,或a表示追加, 如果无法打开文件,则返回负值
int close() 关闭文件,文件未打开则返回-1
int getSize() const 返回文件大小, 文件未打开则返回-1
bool isEndOfFile() const 是否到达文件最尾
string readString(uint length) 从文件中读取指定长度的字符串
string readLine() 以\n分开读取一行字符串
int64 readInt(uint bytes) 从指定长度字节中读取整数
uint64 readUInt(uint bytes) 从指定长度字节中读取整数
float readFloat() 读取4字节长度单精度数
double readDouble() 读取8字节长度的双精度数
int writeString(const string &in str) 写出一个字符串,出错返回-1,否则是字节长度
int writeInt(int64 value, uint bytes) 写出指定长度的整数,出错返回-1,否则是字节长度
int writeUInt(uint64 value, uint bytes) 写出指定长度的整数,出错返回-1,否则是字节长度
int writeFloat(float value) 写出4长度的单精度数,出错返回-1,否则是字节长度
int writeDouble(double value) 写出8长度的双精度数,出错返回-1,否则是字节长度
int getPos() const 获得目前文件指针的位置
int setPos(int pos) 设置目前文件指针的位置
int movePos(int delta) 移动文件指针到指定位置

成员属性

bool mostSignificantByteFirst

如果在读/写数字的方法中首先读取或写入最重要的位,则应将此属性设置为true

它默认设置为false,这是大多数平台上的标准


文件系统

注意

只有注册了文件系统类型的程序才能在脚本中使用字符串,由于注册方法或注册内容的不同,不同程序使用文件系统的方法可能会有所区别

文件系统(filesystem)用于访问系统路径

方法 描述
bool changeCurrentPath(const string &in path) 改变目前的路径,不存在返回false
string getCurrentPath() const 获取目前所在的路径
array @getDirs() 获取目前所在路径的所有文件夹
array @getFiles() 获取目前所在路径的所有文件
bool isDir(const string &in path) 判断路径是否为文件夹
bool isLink(const string &in path) 判断路径是否为链接
int64 getSize(const string &in) const 获得文件的大小
int makeDir(const string &in) 在指定路径创建一个文件夹, 成功返回0
int removeDir(const string &in) 删除一个空文件夹, 成功返回0
int deleteFile(const string &in) 删除一个文件, 成功返回0
int copyFile(const string &in, const string &in) 复制一个文件, 成功返回0
int move(const string &in, const string &in) 移动一个文件, 成功返回0

系统函数

注意 只有当应用程序注册了对系统功能的支持,这些系统功能才在脚本中可用。

函数

void print(const string &in line)

将一行文本打印到标准输出。


string getInput()

从标准输入获取一行文本。

array<string> @getCommandLineArgs()

将命令行参数作为数组获取。

int exec(const string &in cmd)
int exec(const string &in cmd, string &out output)

执行一个系统命令。

如果出错则返回-1或抛出异常。成功时返回系统命令的退出代码。

第二个选项允许将标准输出捕获到一个字符串中,以便进一步处理。

脚本调试


知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。