This commit is contained in:
boybook
2025-12-01 20:59:16 +08:00
parent 12738a142c
commit 760c2dd9ad
5535 changed files with 21070 additions and 2021 deletions

View File

@@ -0,0 +1,109 @@
---
front: https://nie.res.netease.com/r/pic/20211104/69055361-2e7a-452f-8b1a-f23e1262a03a.jpg
hard: 高级
time: 20分钟
---
# 认识Molang
**Molang**是一种基于表达式的类脚本语言旨在使用简单的类脚本语言在较低层次的系统中不脱离数据驱动实现复杂的行为。当然在脚本中我们依旧可以使用Molang这有助于我们轻松获取一些内部成员或旗标的值也可以实现和数据驱动的更复杂联动。Molang常用于实体的资源控制或世界生成器相关的操作中支持单一的简单表达式和多行的复杂表达式的运算。
## 基本概念
Molang在数据驱动的JSON文件中经常作为一个字段的值而出现这往往是一个字符串。字符串中的表达式便是Molang表达式。比如
```json
"some_field": "math.sin(query.anim_time * 1.23)"
```
其中`math.sin(query.anim_time * 1.23)`便是一个Molang表达式。根据国际版最新的概念标准一个Molang**表达式****Expression**指一个被引号包裹住的全部Molang语句的总和。如果一个表达式中存在多个语句那么每个语句都被称为一个**子表达式****Sub-expression**)。比如:
```cpp
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字段的值中。复杂表达式在作为字符串值时所有的语句可以写在同一行中也可以换行且保留缩进类似于
```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.0``false`等价于`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` | 用于提前退出`loop``for_each`的作用域。 |
| `continue` | 用于跳过`loop``for_each`迭代的语句集的其余部分,并移动到下一次迭代。 |
上述运算符或关键字的详细用法可以参考[bedrock.dev上托管的Molang文档](https://bedrock.dev/zh/b/Molang)。
## 查询函数
在上面列出的关键字中,存在一种比较特殊的变量类型,被称为**查询函数****Query Function**),它的语法是`query.function_name`,其中`function_name`为某个查询函数名。顾名思义,查询函数是用于查询一个属性的值的函数。包括**全局参数****Global Parameter**)、**实体成员****Entity Member**)、**实体旗标****Entity Flag**)在内的各种各样的值都可以被查询函数所查询。
查询函数分为**无参查询函数**和**带参查询函数**。无参查询函数就是不具有参数表的查询函数。对于这种查询函数,直接写出其函数名即可获得对应属性的返回。比如本节最开头例子中的`query.anim_time`,便是用来查询一个全局参数`anim_time`的无参查询函数。
带参查询函数指的是比如先传入一些参数作为基础来查询特定的值的查询函数。这样的查询函数末尾需要紧跟一对圆括号(`(` `)`),然后在圆括号内写入参数表。比如下面的例子中的`query.get_nearby_entities`函数,他的第一个参数接受一个数字值,第二个参数接受一个字符串。
```cpp
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文档](https://bedrock.dev/zh/b/Molang)中找到。在版本列表中选择与当前我的世界中国版相吻合的版本即可查看相对应版本的查询函数列表。
## 自定义变量
Molang中具备各种**变量****Variable**。变量可以用来存储一个值以供后续访问。Molang的变量存在三种类型分别是实体变量、临时变量和上下文变量。
**实体变量****Entity Variable**是一种存储在实体上的变量这里的实体指的是广义的实体指的是ECS框架中的实体包括了方块、物品、粒子、世界生成器等。实体变量的生存周期与实体相一致当实体在内存中被销毁时比如实体在世界中消失它的实体变量也将随之销毁而变得无法访问。这种变量我们使用`variable.variable_name`语法定义。
**临时变量****Temp Variable**)是一种存储在暂存器中的变量,这种变量在他们所定义的作用域中是有效的,在作用域的运算结束后将被销毁。不过,由于一些缺陷,目前的临时变量生命周期依然是全局的,所以在给临时变量命名时格外小心。这种变量使用`temp.variable_name`语法定义。
**上下文变量****Context Variable**)是一种只读变量,只能由硬编码的游戏引擎定义。这种变量是依赖于游戏运行状态的上下文而存在的。比如在铁砧上下文中,存在`context.other`代表铁砧的第二个输入槽位。如果脱离了铁砧的环境,将不存在该变量。这种变量使用`context.variable_name`语法定义。
开发者可以灵活地自定义实体变量和临时变量,在合适的实机改写或访问各种变量的值,从而使开发事倍功半。