Molang¶
Molang是Minecraft基岩版在数据驱动系统中使用的轻量级表达式语言(Expression Language)。它用于在JSON定义文件内嵌入运行时计算,使静态数据可以根据游戏状态、实体状态、渲染上下文或随机结果产生动态值。Molang的名称源自“Mojang Language”,通常写作“Molang”或“MoLang”。
概述¶
Molang位于普通JSON数据与完整脚本语言之间。JSON字段本身只能保存固定值,而许多数据驱动功能需要根据运行时条件决定值,例如根据实体是否幼年选择动画,根据实体变种选择纹理,根据粒子年龄改变粒子大小,或根据方块状态决定方块置换是否生效。Molang为这些位置提供了较低开销的计算能力。
Molang常见于资源包和行为包中的实体定义、客户端实体定义、动画、动画控制器、渲染控制器、粒子特效、方块置换、部分组件字段和部分事件字段。表达式通常以字符串形式写入JSON字段,由游戏在特定上下文中求值。
Molang不是通用脚本语言。它不负责组织大型程序,也不提供与脚本API等价的世界修改能力。它主要用于“计算当前值”和“判断当前状态”,并由调用它的数据驱动系统决定求值时机和返回值用途。
求值环境¶
每个Molang表达式都在特定的上下文中求值。上下文决定表达式可以读取哪些查询函数、上下文变量、实体引用、资源引用和特殊值。例如,动画文件中的表达式通常面向骨骼变换,渲染控制器中的表达式可用于选择几何体、材质和纹理,粒子文件中的表达式则可读取粒子年龄、随机数和发射器相关变量。
同一段表达式在不同系统中不一定具有相同含义。this表示表达式即将写入的当前值,但该当前值由调用位置决定。query.可读取的查询函数也取决于调用位置和当前对象。
粒子上下文¶
粒子特效中的Molang表达式除可使用通用语言能力外,还具有粒子系统提供的上下文变量。粒子Molang支持两类变量:发射器变量描述当前发射器循环状态,粒子变量描述单个粒子的生命周期。
发射器变量¶
| 变量 | 描述 | 用途 |
|---|---|---|
variable.emitter_age | 当前发射器循环开始后经过的时间(秒)。 | 用于实现发射器寿命检查、周期性事件触发、循环动画等。 |
variable.emitter_lifetime | 当前发射器循环持续的时间(秒)。 | 用于归一化发射器生命周期进度(除以该值得到0至1的比例)。 |
variable.emitter_random_1至4 | 当前发射器循环内保持不变的0.0至1.0随机数。 | 为整个发射器循环提供稳定的随机变化,例如随机初始速度、随机颜色等。 |
粒子变量¶
| 变量 | 描述 | 用途 |
|---|---|---|
variable.particle_age | 粒子已存活的时间(秒)。 | 用于时间依赖的计算,如渐变消失。 |
variable.particle_lifetime | 粒子的总寿命(秒)。 | 用于归一化粒子生命周期进度(除以该值得到0至1的比例)。 |
variable.particle_random_1至4 | 粒子生命周期内保持不变的0.0至1.0随机数。 | 为每个粒子实例提供独立的随机变化。 |
其他粒子变量¶
| 变量 | 描述 | 用途 |
|---|---|---|
variable.entity_scale | 粒子特效挂接到实体时,该值为实体的缩放系数。 | 用于根据实体大小调整粒子大小。 |
variable.*(曲线名) | 粒子曲线的输出值,根据曲线定义在每帧求值。 | 用于驱动粒子大小、颜色、速度等属性的平滑变化。 |
常见用例¶
// 粒子淡出:大小随粒子年龄减小
(0.1 - variable.particle_age * 0.1)
// 循环性波动:根据发射器年龄和随机数
0.5 + 0.25 * math.sin(variable.emitter_age * 6.28 + variable.emitter_random_1 * 6.28)
// 生命周期进度(0至1)
variable.particle_age / variable.particle_lifetime
// 根据实体大小调整粒子尺寸
0.1 * variable.entity_scale
当粒子特效挂接到实体时,variable.entity_scale表示该实体的缩放。粒子曲线的求值结果也会写入对应的variable.变量,供粒子大小、颜色、速度等字段继续引用。
值与类型¶
Molang的数值以浮点数为基本表示。布尔值在表达式中也以浮点数保存,0.0表示假,任何非0.0的值表示真。来自活动对象标志等布尔来源的值通常转换为0.0或1.0。
除数值外,Molang还支持若干由引擎提供或由上下文使用的值类型:
- 字符串(String):使用单引号包围,例如
'minecraft:pig'。字符串目前主要支持==和!=比较,且没有转义字符支持,因此字符串中不能包含单引号。 - 几何体(Geometry)、纹理(Texture)和材质(Material):主要用于渲染控制器等资源引用位置。
- 活动对象引用(Actor Reference)和活动对象引用数组(Actor Reference Array):用于引用其他活动对象,常与箭头运算符配合。
- 结构体(Struct):由字段访问隐式形成的复合值,例如
v.location.x、v.location.y和v.location.z可以共同表示一个位置。
数组索引会按C风格转换为整数。负索引会钳制到0,超过数组末尾的正索引会回绕到数组前部。因此,含有10个元素的数组访问索引10会得到第0个元素,访问索引15会得到第5个元素。
标识符与大小写¶
除字符串内容外,Molang中的名称通常不区分大小写。math.sin、Math.Sin和MATH.SIN在语言层面表示同一函数。生产内容仍应保持统一的小写写法,以便与官方参考、社区工具和错误日志保持一致。
未被作用域前缀限定的普通标识符保留给未来用途。实际内容中应使用明确的作用域前缀,例如query.、variable.、temp.和context.。
语句与表达式¶
简单Molang表达式可以由单个值、运算或查询构成:
简单表达式通常省略末尾分号,其求值结果就是表达式返回值。复杂表达式可以包含多条语句。多条语句应以分号结束,并使用return语句返回最终值:
temp.a = math.sin(query.anim_time * 1.23);
temp.b = math.cos(query.life_time + 2.0);
return temp.a * temp.a + temp.b;
复杂表达式如果没有执行return,返回值为0.0。这与许多通用编程语言“最后一条表达式作为结果”的行为不同,是Molang内容中常见的错误来源。
运算符¶
Molang使用接近C语言的表达式语法。常用运算符包括:
| 运算符 | 描述 |
|---|---|
+、-、*、/ | 四则运算。 |
<、<=、>=、>、==、!= | 比较运算。 |
!、&&、\|\| | 逻辑取反、逻辑与、逻辑或。 |
? : | 三元条件运算。 |
? | 二元条件运算,条件为真时才执行右侧表达式。 |
?? | 空值合并运算,用于在变量或引用不可解析时提供默认值。 |
-> | 箭头运算符,用于通过活动对象引用访问其他活动对象的数据。 |
[、] | 数组访问。 |
各运算符之间存在明确的优先级顺序,从高到低依次为:
| 优先级 | 运算符 |
|---|---|
| 1(最高) | ()、[] |
| 2 | -> |
| 3 | !、-(一元取反) |
| 4 | *、/ |
| 5 | +、-(二元减法) |
| 6 | <、<=、>、>= |
| 7 | ==、!= |
| 8 | && |
| 9 | \|\| |
| 10 | ?、? : |
| 11 | ?? |
| 12 | = |
| 13(最低) | return |
除条件运算符外,所有运算符均按从左到右的顺序求值。逻辑运算符具有短路特性,即&&左侧为假时不再求值右侧,||左侧为真时不再求值右侧。同一语句中不可重复使用->运算符。
早期版本曾存在运算符优先级和嵌套条件表达式解析差异,因此较旧包的表达式行为可能受最低引擎版本影响。
变量¶
Molang变量按生命周期和来源分为三类。
- 临时变量(Temporary Variable)
- 使用
temp.或t.前缀,可读写。临时变量用于一次表达式求值中的中间结果。出于性能原因,其生命周期在实现上可能覆盖当前表达式执行过程,而不完全等同于块级作用域;复杂表达式中不应依赖未清晰定义的跨块访问。 - 实体变量(Entity Variable)
- 使用
variable.或v.前缀,可读写。实体变量保存在当前活动对象上,随该活动对象生命周期存在。它们不会保存到存档中,重新进入世界或实体被卸载、删除、重新生成时会丢失。 - 上下文变量(Context Variable)
- 使用
context.或c.前缀,只读。上下文变量由游戏在特定调用位置提供,具体可用内容由相应系统定义。
客户端实体定义可以声明公开变量,使其他活动对象以只读方式访问该变量。公开变量应在初始化脚本中提供默认值,以避免表达式读取未初始化数据。
预置实体变量¶
游戏在特定上下文中会自动为实体设置一些预置的variable.变量,这些变量通常无需手动初始化即可读取:
| 变量 | 描述 |
|---|---|
variable.attack_time | 攻击动画进度(0.0至0.7) |
variable.bob_animation | 闲置或移动时的摆动效果振荡值 |
variable.charge_amount | 蓄力量(用于附着物) |
variable.damage_nearby_mobs | 是否正在对附近生物造成伤害 |
variable.gliding_speed_value | 滑翔时的速度值 |
variable.has_target | 实体当前是否有目标 |
variable.is_brandishing_spear | 实体是否举着三叉戟 |
variable.is_first_person | 是否处于第一人称视角 |
variable.is_holding_left | 左手是否持有物品 |
variable.is_holding_right | 右手是否持有物品 |
variable.is_paperdoll | 纸娃娃当前是否可见 |
variable.is_sneaking | 玩家是否正在潜行 |
variable.is_using_vr | 玩家是否使用VR头显 |
variable.player_arm_height | 手臂高度偏移(通常在第一人称视角中调整) |
variable.swim_amount | 游泳动画的总体进度 |
variable.use_item_interval_progress | 物品使用时间轴的中间阶段进度 |
variable.use_item_startup_progress | 物品使用动画启动阶段进度 |
其他预置变量(如角色皮肤相关的variable.animation_frames_128x128、variable.animation_frames_32x32等)仅在特定渲染上下文中有效。
常见上下文变量¶
上下文变量由游戏在调用位置提供,不同系统可用的上下文变量不同:
| 上下文变量 | 可用上下文 | 描述 |
|---|---|---|
context.count | 配方 | 当前上下文中的数量 |
context.is_first_person | 动画、实体、渲染控制器 | 实体是否在第一人称视角中渲染 |
context.item_slot | 模型 | 当前物品的槽位索引 |
context.other | 物品 | 用于修复目标的"另一个"物品 |
context.owning_entity | 附着物 | 拥有当前上下文的实体(用于读取其查询函数) |
context.player_offhand_arm_height | 模型 | 渲染副手时的手臂高度偏移值 |
查询函数¶
查询函数(Query Function)是Molang读取游戏运行时状态的主要方式,使用query.或q.前缀。查询函数可以返回实体状态、动画时间、移动状态、方块状态、物品状态或渲染相关信息。无参数查询不使用括号,有参数查询使用括号并以逗号分隔参数:
查询函数是只读接口。可用查询由游戏版本、调用上下文和被查询对象决定。不存在或不可用的查询通常会导致内容错误,并按错误处理规则返回默认值。
Molang查询函数的完整清单可参考参考:Molang查询函数。
与命令系统和记分板的边界¶
Molang本身不直接提供记分板读写、命令派发或世界修改能力。其职责是计算表达式值,并将结果交给调用它的数据驱动系统使用。
当内容需要把Molang计算结果写入记分板时,通常采用“计算与执行分离”模式:
- 在动画、动画控制器或其他可求值位置中计算并暂存数值。
- 在支持命令执行的位置(如行为包动画控制器状态的
on_entry或on_exit)调用记分板命令。
该模式本质上是通过命令系统间接桥接Molang和记分板。由于记分板为整数系统,而Molang以浮点数为主,桥接过程中通常需要明确取整、符号处理和精度边界。
数学函数¶
Molang提供以math.为前缀的数学函数,涵盖三角函数、插值、取整、随机数、钳制、幂、对数、取模和骰子函数等。三角函数使用角度制而不是弧度制,这使动画表达式常能直接以query.anim_time * 360等形式描述周期运动。
数学函数常与查询函数组合,用于将时间、速度、年龄、距离或随机值转换为动画、粒子和渲染所需的数值。
Molang数学函数的完整清单可参考参考:Molang数学函数。
流程控制¶
Molang支持有限流程控制。loop(count, expression)用于重复执行表达式,循环计数上限为1024,以避免内容导致游戏长时间卡死。break可跳出当前loop或for_each,continue可跳过当前迭代剩余语句。for_each用于遍历活动对象引用数组。
这些功能适合局部计算,不适合承担复杂游戏逻辑。复杂状态机、世界修改和长期数据存储通常应由组件、事件、动画控制器或脚本API承担。
资源引用¶
渲染控制器等系统可以在Molang中使用几何体、纹理和材质资源引用。资源引用表达式必须返回调用位置要求的资源类型。例如,geometry字段需要几何体,textures字段需要纹理,materials映射需要材质。
渲染控制器还可以声明资源数组,并用Molang表达式选择数组元素:
资源数组的元素必须属于同一资源类型。布尔查询返回的0.0和1.0常用于在两个资源之间选择。
进阶特性¶
this关键字¶
this表示表达式即将写入的字段在求值过程中已累积的当前值。该关键字目前主要在动画系统的骨骼变换场景中可靠使用,在其他上下文中通常解析为0.0。
例如,若某骨骼在x轴上的缩放变换已累积结果为62,在后续动画通道中将该轴的scale表达式设为-this时,结果为-62,可用于抵消之前的变换。这一用法在原版动画文件中较为常见。
字符串的数值特性¶
字符串值在Molang中本质上以浮点数形式进行比较。单字符字符串可通过==和!=运算符进行比较,甚至可以进行算术"调整",但这属于边缘行为,多字符字符串在此类操作中的语义不确定。字符串中不支持通常意义上的反斜杠转义序列——\在Molang字符串中具有特殊含义,且该行为在解析层面存在已知限制,应避免在字符串中使用\。
集合类型的限制¶
Molang中存在两类无法互换的集合类型:
- 实体可迭代对象(如
query.get_nearby_entities的返回值):可供query.count等函数处理,但不能以[]下标操作。 - 数组(如资源引用数组或通过
for_each访问的结构):支持[]下标访问,但不能直接作为+、-、*、/的操作数;数组元素可作为函数参数参与计算。
大括号作用域与语句¶
大括号{}可在任何允许使用表达式的位置使用,以创建包含多条语句的代码块。大括号作用域内最后一条语句可以省略末尾的分号。例如:
上例中,当v.spawn_point未定义时,大括号块内的赋值才会执行。
赋值语句会返回被赋予的值,因此支持链式赋值:
数值字面量扩展¶
Molang的数值字面量支持以下书写方式:
- 前导零(如
0012),便于对齐代码。 - 科学计数法(如
2.5e2等价于250),指数部分可带+或-号。 - 尾随
f后缀(如1.0f),常见于原版源码,目前无实际语义影响。
版本化变更¶
Molang解析规则会受到清单文件中min_engine_version的影响。该机制称为版本化变更,用于在修正语言行为时尽量保持旧包兼容。每个表达式根据其所属资源包或行为包的最低引擎版本确定适用规则;同一世界中加载多个包时,不同包内表达式可能使用不同规则。
重要版本化变更包括:1.18.10修正嵌套三元条件表达式结合性,1.18.20修正逻辑与、逻辑或、比较和相等运算的优先级,1.20.10将block_property和has_block_property重命名为block_state和has_block_state,1.20.40弃用旧名称,1.20.50移除旧名称支持。
错误处理¶
Molang中的错误值通常转换为0.0。除零、缺失变量、空引用、无效资源、非法表达式和类型不匹配等情况可能产生内容错误。非发布构建通常会在内容日志中报告这些错误;市场发布内容也可能因内容错误而无法通过检查。
因此,Molang表达式应避免依赖未初始化变量、无效活动对象引用或不存在资源。对于可能缺失的数值变量和活动对象引用,可使用??提供默认值;对于材质、纹理和几何体等资源,必须保证引用真实存在。