# 认识Molang

Molang是一种基于表达式的类脚本语言,旨在使用简单的类脚本语言在较低层次的系统中不脱离数据驱动实现复杂的行为。当然,在脚本中我们依旧可以使用Molang,这有助于我们轻松获取一些内部成员或旗标的值,也可以实现和数据驱动的更复杂联动。Molang常用于实体的资源控制或世界生成器相关的操作中,支持单一的简单表达式和多行的复杂表达式的运算。

# 基本概念

Molang在数据驱动的JSON文件中经常作为一个字段的值而出现,这往往是一个字符串。字符串中的表达式便是Molang表达式。比如:

"some_field": "math.sin(query.anim_time * 1.23)"

其中math.sin(query.anim_time * 1.23)便是一个Molang表达式。根据国际版最新的概念标准,一个Molang表达式Expression)指一个被引号包裹住的全部Molang语句的总和。如果一个表达式中存在多个语句,那么每个语句都被称为一个子表达式Sub-expression)。比如:

temp.moo = math.sin(query.anim_time * 1.23);
temp.baa = math.cos(query.life_time + 2.0);
return temp.moo * temp.moo + temp.baa;

其中有三个语句,两个赋值语句和一个返回语句。这三条语句的每条语句都被称为一个Molang子表达式。在最新的标准中,我们不用“脚本”一词来称呼Molang。

通过上面的例子,我们可以看出,Molang表达式存在两种形态,一种是只包含一个语句的表达式,并且结尾不存在;作为“结束标识“,这种表达式称为简单表达式Simple Expression)。另一种是包含多个子表达式(即多个语句)的表达式,每个子表达式的结尾必须存在一个;作为“结束标识“,这种表达式称为复杂表达式Complex Expression)。两种表达式均可作为Molang参数写在JSON字段的值中。复杂表达式在作为字符串值时,所有的语句可以写在同一行中,也可以换行且保留缩进,类似于:

{
    "some_field": "
        temp.moo = math.sin(query.anim_time * 1.23);
        temp.baa = math.cos(query.life_time + 2.0);
        return temp.moo * temp.moo + temp.baa;
    ",
    "some_other_field": "something"
    // ...
}

我的世界可以解析这种换行的Molang字段,也仅Molang字段可以如此这般地解析。如果某个字段只接受普通的字符串,则引擎无法像上面的例子一样换行且保留缩进地解析。

每个Molang表达式都必须返回一个值。简单表达式将返回该语句本身计算的值。如果该语句是一个布尔校验,则返回布尔校验的结果值,true等价于1.0false等价于0.0。如果该语句没有产生值,将返回0.0

如果是复杂表达式,则需要使用return的子表达式来返回值。如果不存在这种返回语句,则不论中间计算多么“激烈”,最后都将只返回一个0.0

# 运算符和关键字

Molang中有多种运算符和关键字,下面列出了全部的运算符和关键字。值得注意的是,除了字符串内的内容外,其他地方Molang中的字母都是大小写不敏感的,也就是说,同一个字母的大写和小写没有任何分别。

运算符、关键字或字面量 描述
1.23 一个数值常量值。
! && || < <= >= > == != 逻辑运算符,分别是非、与、或、小于、小于或等于、大于或等于、大于、等于、不等于运算符。
* / + - 基本数学运算符,分别是乘、除、加、减运算符。
( ) 用于控制表达式中的项求值用的圆括号,也用于带参查询函数的传参。
[ ] 用于访问数组的方括号。
{ } 用于控制执行作用域的花括号。
?? 空合并运算符,用于处理丢失的变量和过时的活动对象引用。
<test> ? <if true> 二元条件运算符。
<test> ? <if true> : <if false> 三元条件运算符。
-> 箭头运算符,用于访问来自另一个不同实体的数据。
geometry.geometry_name 一个在实体定义文件中命名的几何的短名称引用。
material.material_name 一个在实体定义文件中命名的材质的短名称引用。
texture.texture_name 一个在实体定义文件中命名的纹理的短名称引用。
math.function_name 用于访问各种数学函数。
query.function_name 用于访问一个实体的属性。
variable.variable_name 读写一个活动对象的存储器。
temp.variable_name 读写暂时存储器。
context.variable_name 访问在某些场景下游戏提供的只读存储。
this 该表达式(在指定上下文的情况下)最终写入值的当前值。
return 为复杂表达式设计,这将计算紧跟着该关键词的语句并停止整个表达式的执行,返回计算出的值。
loop 用于重复执行一个或多个命令。
for_each 用于遍历迭代一个实体数组。
break 用于提前退出loopfor_each的作用域。
continue 用于跳过loopfor_each迭代的语句集的其余部分,并移动到下一次迭代。

上述运算符或关键字的详细用法可以参考bedrock.dev上托管的Molang文档 (opens new window)

# 查询函数

在上面列出的关键字中,存在一种比较特殊的变量类型,被称为查询函数Query Function),它的语法是query.function_name,其中function_name为某个查询函数名。顾名思义,查询函数是用于查询一个属性的值的函数。包括全局参数Global Parameter)、实体成员Entity Member)、实体旗标Entity Flag)在内的各种各样的值都可以被查询函数所查询。

查询函数分为无参查询函数带参查询函数。无参查询函数就是不具有参数表的查询函数。对于这种查询函数,直接写出其函数名即可获得对应属性的返回。比如本节最开头例子中的query.anim_time,便是用来查询一个全局参数anim_time的无参查询函数。

带参查询函数指的是比如先传入一些参数作为基础来查询特定的值的查询函数。这样的查询函数末尾需要紧跟一对圆括号(( )),然后在圆括号内写入参数表。比如下面的例子中的query.get_nearby_entities函数,他的第一个参数接受一个数字值,第二个参数接受一个字符串。

v.x = 0;
for_each(v.pig, query.get_nearby_entities(4, 'minecraft:pig'), {
    v.x = v.x + v.pig->query.get_relative_block_state(0, 1, 0, 'flammable');
});

具体的查询函数列表依旧可以在bedrock.dev上托管的Molang文档 (opens new window)中找到。在版本列表中选择与当前我的世界中国版相吻合的版本即可查看相对应版本的查询函数列表。

# 自定义变量

Molang中具备各种变量Variable)。变量可以用来存储一个值,以供后续访问。Molang的变量存在三种类型,分别是实体变量、临时变量和上下文变量。

实体变量Entity Variable)是一种存储在实体上的变量,这里的实体指的是广义的实体,指的是ECS框架中的实体,包括了方块、物品、粒子、世界生成器等。实体变量的生存周期与实体相一致,当实体在内存中被销毁时(比如实体在世界中消失),它的实体变量也将随之销毁而变得无法访问。这种变量我们使用variable.variable_name语法定义。

临时变量Temp Variable)是一种存储在暂存器中的变量,这种变量在他们所定义的作用域中是有效的,在作用域的运算结束后将被销毁。不过,由于一些缺陷,目前的临时变量生命周期依然是全局的,所以在给临时变量命名时格外小心。这种变量使用temp.variable_name语法定义。

上下文变量Context Variable)是一种只读变量,只能由硬编码的游戏引擎定义。这种变量是依赖于游戏运行状态的上下文而存在的。比如在铁砧上下文中,存在context.other代表铁砧的第二个输入槽位。如果脱离了铁砧的环境,将不存在该变量。这种变量使用context.variable_name语法定义。

开发者可以灵活地自定义实体变量和临时变量,在合适的实机改写或访问各种变量的值,从而使开发事倍功半。

https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg

高级

20分钟

基本概念

运算符和关键字

查询函数

自定义变量