认识Molang¶
Molang是基岩版附加包里最常见的“小公式”。当JSON字段不能只写死一个值,而是要根据实体状态、动画时间、方块状态或随机数变化时,你通常会遇到Molang。
你不需要把Molang当成一门大型编程语言。更合适的理解是:它在游戏需要某个值时快速算出这个值,然后把结果交给动画、粒子、渲染控制器、方块置换或实体组件使用。
第一个表达式¶
这条表达式的意思是:如果当前实体生命值小于5,返回1;否则返回0。在很多条件位置,1会被当成真,0会被当成假。
如果要让一个值随时间周期变化,可以用数学函数:
query.life_time会随着实体存在时间增长,math.sin会产生正负往复变化,所以这类表达式很适合做摇摆、呼吸、闪烁等效果。
写在什么地方¶
Molang通常写在资源包或行为包的JSON文件里。常见位置包括:
- 动画文件:控制骨骼的位置、旋转和缩放。
- 动画控制器:决定状态什么时候转移。
- 渲染控制器:选择几何体、材质、纹理或控制模型部件可见性。
- 粒子特效:控制粒子寿命、大小、速度和发射条件。
- 实体定义:预先计算变量,或在部分组件字段中返回数值。
- 方块定义:在方块置换条件中判断方块状态。
不要假设同一个表达式在所有地方都能使用。每个位置能读取的查询函数、变量和资源类型都可能不同。
常用作用域¶
| 写法 | 含义 | 示例 |
|---|---|---|
query.或q. | 游戏提供的只读查询。 | q.is_on_ground |
variable.或v. | 当前实体上的变量,可读写。 | v.timer = (v.timer ?? 0) + q.delta_time |
temp.或t. | 本次表达式求值中的临时变量,可读写。 | t.speed = q.ground_speed * 1.2 |
context.或c. | 当前上下文提供的只读变量。 | c.item_slot |
query最常见,因为它能读取实体状态、时间、位置和渲染上下文。variable适合保存实体还存在时需要复用的状态,但它不会保存到存档里;重新进入世界或实体被卸载后,变量需要重新初始化。temp适合中间计算,不要把它当成长期存储。context由具体调用位置提供,不能写入。
简单表达式和复杂表达式¶
只有一条表达式时,通常不要写分号:
如果你要写多条语句,每条语句都要用分号结束,并用return明确返回结果:
如果复杂表达式没有执行return,结果会是0。这是很多“表达式看起来写了很多,但游戏里没有效果”的原因。
条件和默认值¶
Molang的逻辑写法和很多编程语言相似:
&&表示并且,||表示或者,!表示取反。三元表达式写作:
如果变量可能还没有初始化,可以用??提供默认值:
这表示:如果v.timer已经有值,就用原值;如果它还不可用,就先当成0。
在实体定义中预先计算¶
客户端实体定义的scripts.pre_animation会在动画和渲染控制器处理前执行。你可以在这里把后面多次使用的值提前算好:
"scripts": {
"pre_animation": [
"variable.wing_flap = ((math.sin(query.wing_flap_position * 57.3) + 1) * query.wing_flap_speed);"
]
}
之后,动画文件就可以直接读取这个变量:
这种写法的好处是:同一个复杂计算只需要维护一次,也能让动画文件更容易阅读。
在动画中使用¶
动画文件经常用Molang控制骨骼变换。下面的写法会让头部绕Z轴摆动:
在动画里,this表示当前通道原本要写入的值。原版动画常用- this或目标值 - this抵消已有值或叠加差值。修改原版表达式时,不要随意删掉this,否则骨骼姿态可能突然偏移。
在动画控制器中使用¶
动画控制器用Molang决定状态转移。例如:
这表示当query.is_sitting为真时,进入sitting状态。动画控制器适合处理“什么时候播放哪组动画”。如果你只是想让动画权重随条件变化,也可以在客户端实体定义的scripts.animate中传入Molang:
"animate": [
"look_at_target",
{
"move": "query.modified_move_speed"
},
{
"baby_transform": "query.is_baby"
}
]
需要注意:scripts.animate里的数值通常控制动画混合权重,不一定会让动画每次从头播放。需要严格重新播放时,优先考虑动画控制器状态。
在渲染控制器中使用¶
渲染控制器可以用Molang选择资源,也可以控制部件可见性:
"part_visibility": [
{ "leg*": "!query.is_sleeping" },
{ "head": "!query.is_sleeping" },
{ "head_sleeping": "query.is_sleeping" }
],
"textures": [ "array.skins[query.variant]" ]
这里query.is_sleeping控制普通头部和睡眠头部是否可见,query.variant从纹理数组中选择皮肤。数组越界不会直接报错:负数会当成0,超过末尾的正数会回绕到数组前面。这个特性可以用来循环资源,但也可能掩盖错误索引。
资源引用必须返回正确类型。写在textures里的表达式应返回纹理,写在geometry里的表达式应返回几何体,写在materials里的表达式应返回材质。
在粒子中使用¶
粒子文件也大量使用Molang。比如下面的片段用随机数决定粒子寿命,并让粒子大小随年龄变化:
"minecraft:particle_lifetime_expression": {
"max_lifetime": "math.random(0.6, 2.0)"
},
"minecraft:particle_appearance_billboard": {
"size": [
"(0.1 + variable.particle_random_1 * 0.1) - (0.1 * variable.particle_age)",
"(0.1 + variable.particle_random_1 * 0.1) - (0.1 * variable.particle_age)"
]
}
粒子中的变量和实体动画中的变量不一定相同。复制表达式前,要确认当前粒子组件是否提供这些变量。
调试建议¶
- 先把表达式改成固定数值,确认文件路径、标识符和调用位置正确。
- 再逐步加入查询函数和数学运算,每次只改一小段。
- 多语句表达式一定检查分号和
return。 - 对可能未初始化的变量使用
??。 - 打开内容日志,优先修复Molang错误和资源引用错误。
- 升级清单文件的
min_engine_version后,重新检查复杂条件表达式和方块状态相关查询。
Molang很适合做即时计算,但不适合塞进整套玩法逻辑。把长期状态交给实体组件、事件、动画控制器或脚本API,把Molang留给“这一刻需要算出的值”,会更稳定也更容易排错。