自定义函数
约 2192 字大约 7 分钟
molang脚本函数
2025-09-17
在 2.5.0
版本,我们加入了自定义函数功能,允许创作者用 molang 编写函数并调用,以实现代码复用。
使用方法
- 函数目录默认为
functions
,
如果要修改,可以在清单文件ysm.json
中插入以下字段:
{
"files": {
//...
"projectiles": {
"minecraft:arrow": {
"model": "models/arrow.json",
"animation": "animations/arrow.animation.json",
"texture": "textures/arrow.png"
}
},
"sound_path": "my_sounds",
"function_path": "my_functions"
}
}
- 在该目录中创建函数文件函数名.molang,
函数名不区分大小写,只能包含字母、下划线、数字;
示例:模型文件夹/functions/my_func.molang
- 编写函数:
a. 每个语句以分号;结尾;
b. 使用args[索引]访问调用时传入的参数;
c. 如果执行了return语句,那么此次执行将在此处停止;
d. 支持 C 风格注释,详见 https://www.cainiaojc.com/c-programming/c-code-comments.html ;
// 获取第一个参数的值,并赋值至临时变量 a
t.a = args[0];
// 将变量 t.a 与第二个参数相加,并返回
return t.a + args[1];
// 由于上面 return 结束了此次调用,下面的语句不会执行
q.debug_output('喵');
闭包返回
仅在自定义函数内,return
语句可以穿透由{...}
构成的闭包,跳出多层嵌套循环,结束整个函数的调用。
链式调用
在自定义函数内也可以调用自定义函数,形成一条调用链,如 fn.a
-> fn.b
-> fn.c
。还支持调用自身以实现递归(如有兴趣可参考https://zhuanlan.zhihu.com/p/165052663 ) 注意调用链的长度不能超过 32,否则返回 null。
临时变量
临时变量t.*
在调用链的每个节点之间不共享,并相互隔离(即使是相同函数)。只有变量v.*
可以共享。 在函数调用结束后,临时变量的值将失效。
示例1
创建两个函数
a.molangt.num = 111; // 调用 b 函数(没有参数可以省略小括号) fn.b; // 输出 t.num 的值以便观察 q.debug_output('fn.a: ', t.num);
b.molangt.num = 222; // 输出 t.num 的值以便观察 q.debug_output('fn.b: ', t.num);
执行指令
/ysmclient molang execute fn.a
结果解析
可以看到 b 函数中修改了
t.num
的值,却没有影响到 a 函数。
示例2
如果将以上两个函数中的 t.num
修改为 v.num
。由于 v.* 变量可以共享,结果将输出相同的值。
v.num = 111;
// 调用 b 函数(没有参数可以省略小括号)
fn.b;
// 输出 v.num 的值以便观察
q.debug_output('fn.a: ', v.num);
v.num = 222;
// 输出 v.num 的值以便观察
q.debug_output('fn.b: ', v.num);
注意
不是函数有独立的临时变量,而是调用链中每个节点都有独立的临时变量。即使多次调用同一个函数,每次调用都无法影响之后调用该函数时的临时变量。
技巧
可以用以下方式实现 if - else
。过去版本的大括号语句{...}
有 bug,此快照版已修复。
v.a ? {
...; // v.a 为 true(非 0 且非 null 值)时执行
...;
} : {
...; // v.a 为 false(0 或 null 值)时执行
...;
}; // 不要忘了分号结尾
// 虽没有强制要求,但是建议在逻辑表达式较为复杂时,把判断的部分用括号括起来
(v.b || v.c && v.d) ? {
...;
} : {
...;
}; // 不要忘了分号结尾
可以用 for_each
遍历参数,详见 https://wiki.mcbe-dev.net/zh-sg/Molang#for_each
// 将任意个参数相加并返回
t.sum = 0;
for_each(t.arg, args, {
t.sum = t.sum + t.arg;
}); // 不要忘了分号结尾
return t.sum;
访问参数时,args[...]
内不止可以写数值
t.a = 1;
return args[0] + args[t.a] + args[t.a + 1];
可以用 loop
执行循环,详见 https://wiki.mcbe-dev.net/zh-sg/Molang#loop
// 从 1 加到 10
t.num = 0;
t.sum = 0;
loop(10, {
t.num = t.num + 1;
t.sum = t.sum + t.num;
}); // 不要忘了分号结尾
return t.sum;
当然,循环也支持 break
和 continue
v.x = 1;
v.y = 1;
loop(10, {
t.x = v.x + v.y;
v.x = v.y;
v.y = t.x;
(v.y > 20) ? break;
});
v.x = 0;
loop(10, {
(v.x > 5) ? continue;
v.x = v.x + 1;
});
事件订阅
在触发某些事件时自动调用预设的函数。 使用方法:创建函数文件函数名@事件名.molang
即可订阅一个事件。多个函数可以订阅同一个事件,但触发事件时调用这些函数的顺序是随机的。 如:创建setup@player_init.molang
即可创建一个名为setup
的函数,并订阅player_init
事件。 目前支持的事件如下:
事件名称 | 触发时机 |
---|---|
player_init | 玩家切换到该模型或玩家实体加载时 |
player_update | 每次更新玩家动画之前 |
sync | (见下一节) |
同时触发时的顺序:player_init>player_update > sync
。 v.roaming
变量的同步在player_init
触发之前完成。
主动同步
用于解决随机数或部分预置变量(如 ctrl.fly)在服务器上不同玩家的游戏中不同步的问题。
使用方法
新建一个函数文件
test@sync.molang
,订阅sync
事件;test@sync.molangq.debug_output('同步完成!参数为:', args[0], ' 和 ', args[1])
游戏内调用函数
ysm.sync
;/ysmclient molang execute ysm.sync(1234, 5678)
服务器上所有玩家的游戏内将以相同参数触发
sync
事件;
注意
- 最多可以传递16个参数,超过会导致解析失败;
- 参数类型只支持数值,不支持字符串、结构体等其他类型;
- 由于涉及到网络传输,一次同步的开销相当大,不要频繁的发起同步,尤其不能用于物理计算、坐标计算等用途,否则很容易卡服;
- 调用
ysm.sync
发起同步后将立刻结束并返回null
,不会卡在这里等待事件触发才结束,也就是不会影响帧数(即“异步”); - 从发起同步到触发事件可能相隔几十毫秒,要在相当一段时间之后才会生效。
动画控制
使用 molang 修改硬编码控制器的逻辑,比基岩版控制器更轻量。
使用方法
- 确定要修改的控制器名称(参考 wiki),这里以主动画控制器player.main为例;
- 在自定义函数目录内创建动画控制函数
@player_ctrl_main.molang
,文件名即为动画控制器的名称把.替换成_ctrl_。
该函数每帧都会自动执行一次。
控制逻辑
1. 调用ctrl.set_animation(...)
设置动画:
该函数接受 1~2 个参数,第一个参数为动画名称,第二个参数可选,为动画循环类型。若不指定循环类型,则使用动画预设的循环类型(即 BlockBench 内设置的类型)。可用的循环类型如下:
循环类型 说明 ctrl.loop 永远循环 ctrl.play_once 只播放一次 ctrl.hold_on_last_frame 停在最后一帧 - 示例1:ctrl.set_animation('walk', ctrl.loop);
- 示例2:ctrl.set_animation('run');
调用该函数后,控制器将切换到指定动画,但未必立刻播放;
注意如果设置的动画与控制器当前动画相同,则该操作会被忽略,而不是重载当前动画(即使当前动画已停止);
若要重载动画,需要在设置动画之前调用
ctrl.indicate_reload
(无参数)。
2. 返回动画控制谓词
在设置动画之后,需要使用return
表达式返回一个谓词,指示控制器在这一帧内如何处理当前动画。可用的谓词如下。
谓词 | 说明 |
---|---|
ctrl.state_continue | 正常播放当前动画。 |
ctrl.state_pause | 暂停播放,但是不暂停时间轴。可以使当前动画失效一段时间。 |
ctrl.state_stop | 平滑地停止当前动画。 |
ctrl.state_bypass | 表示当前控制逻辑无操作,使动画控制器转而使用内置的控制逻辑。 如果返回该谓词,前面可以不设置动画。 |
示例:
return ctrl.state_continue;
return ctrl.state_stop;
当返回ctrl.state_bypass
时,下方的ctrl.state_stop
不执行。
其它
- 可调用
ctrl.reset
(无参数)立刻重置动画控制器至初始状态。若有动画正在播放,会粗暴的中止而不会平滑过渡。此外,该函数还包含了ctrl.indicate_reload
的作用; - 可以通过
q.all_animations_finished
检测当前动画是否已播放完毕; - 可以配合基岩版控制器用的
ctrl.xxx
变量判断当前实体状态,如ctrl.idle
判断是否正在待机状态。
示例
v.test_main == 1 ? {
ctrl.set_animation('run');
return ctrl.state_continue;
};
v.test_main == 2 ? {
ctrl.set_animation('walk', ctrl.loop);
return ctrl.state_continue;
};
return ctrl.state_bypass;
该控制逻辑在v.test_main
值为 1 时播放动画run
,值为 2 时播放动画walk
。其它情况使用动画控制器内置的逻辑(即返回 bypass)。效果如下:
注意尾巴转圈是内置控制逻辑设置的飞行动画。