KubeJS 实用 JS 技巧(随机 / 函数 / 调用)¶
原文:原创教程(ぴよまる)
写 KubeJS 脚本时最常用的 JS 技巧都在这篇里了:随机数、函数、调用,外加数组/字符串/数值处理的常用套路。每段都是能直接抄进 kubejs/ 用的。
1. 随机数(Random)¶
1.1 随机小数(0 ~ 1)¶
// let 是声明变量的关键字,r 是变量名(声明后可以改值)
// Math.random() 是 JS 自带的"取随机小数"方法,会返回 0 到 1 之间(不含 1)的小数
let r = Math.random() // 0 ≤ r < 1,比如 0.37、0.99
1.2 随机整数(min ~ max 都包含)¶
// function 是定义函数的关键字,randomInt 是函数名,(min, max) 是调用时要传进来的两个参数
function randomInt(min, max) {
// return 是把计算结果交出去,调用这个函数的地方就能拿到结果
// Math.random() 先取 0~1 的随机小数;(max - min + 1) 是一共有多少个整数
// Math.floor() 是向下取整(去掉小数部分);最后加 min,把范围挪到 min 开始
return Math.floor(Math.random() * (max - min + 1)) + min
}
// 调用函数:把 1 和 100 传进去,得到 1 到 100 的随机整数
randomInt(1, 100) // 1 到 100 的随机整数
// 再调用一次:传 0 和 9,得到 0 到 9 的随机整数
randomInt(0, 9) // 0 到 9
1.3 从列表里随机选一个¶
// 数组用中括号 [ ] 包起来,里面每一项用逗号隔开;这里存了 3 个物品 ID 字符串
let rewards = ['minecraft:diamond', 'minecraft:emerald', 'minecraft:gold_ingot']
// rewards.length 是数组的长度(这里就是 3);乘上随机小数得到一个 0~3 之间的小数
// Math.floor() 向下取整变成整数下标(0、1、2 中的一个);数组下标从 0 开始
// rewards[下标] 用中括号加下标,就能取出数组里对应的那一项
let pick = rewards[Math.floor(Math.random() * rewards.length)]
配合 give 直接发随机奖励:
// ItemEvents.rightClicked 是"玩家右键点击物品"的事件;event 是事件对象
// => 是箭头函数,箭头后面就是要执行的代码;KubeJS 里事件回调都这么写
ItemEvents.rightClicked(event => {
// event.item.id 是当前右键物品的 ID;!== 是"不相等";return 直接结束,不做后面的事
if (event.item.id !== 'kubejs:lucky_box') return
// 奖池数组:除了普通物品 ID,还能写 "3x 物品ID" 表示一次给 3 个
let rewards = ['minecraft:diamond', 'minecraft:emerald', '3x minecraft:iron_ingot']
// 随机挑一个下标取出奖励,然后调用 event.player.give()(给玩家物品)把奖励发出去
event.player.give(rewards[Math.floor(Math.random() * rewards.length)])
})
1.4 概率判断(比如 30% 概率)¶
// if 是"如果":小括号里条件成立就执行花括号里的代码
// Math.random() 是 0~1 的随机小数,它小于 0.3 的概率正好是 30%
if (Math.random() < 0.3) {
// 30% 概率走到这行:调用 event.player.tell() 给玩家发一条消息
event.player.tell('你触发了 30% 的暴击!')
}
1.5 随机数实用例子:随机传送¶
// PlayerEvents.chat 是"玩家在聊天框说话"的事件;event 里装着这次聊天的信息
PlayerEvents.chat(event => {
// event.message 是玩家说的话;如果说的不是 "!随机传送",return 直接结束不处理
if (event.message !== '!随机传送') return
// 把 event.player(当前玩家)存进变量 player,后面写起来更短
let player = event.player
// 调用第 1.2 节定义的 randomInt 函数,在 -500 ~ 500 之间随机一个 x 坐标
let x = randomInt(-500, 500)
// 同样的方法随机一个 z 坐标
let z = randomInt(-500, 500)
// 模板字符串用反引号 ` 包起来,里面 ${变量} 会被替换成变量的值;
// 这里把玩家名和坐标拼进 tp(传送)命令里,再用 runCommandSilent 让服务器执行
player.server.runCommandSilent(`tp ${player.username} ${x} 100 ${z}`)
// 用模板字符串拼出一句话,告诉玩家他被传送到哪了
player.tell(`已传送到 (${x}, 100, ${z})`)
})
💡 记得把
randomInt函数放在同一个文件顶部,或者放进全局工具脚本(见第 3 节「调用」)。
2. 函数(Function)¶
2.1 定义函数¶
// 简单函数:没有参数,没有返回值
// function 定义函数,sayHello 是函数名,小括号里没写东西就是不需要参数
function sayHello() {
// console.log() 是往日志里打印一行文字(在游戏日志里能看到)
console.log('你好!')
}
// 带参数 + 返回值
// a 和 b 是调用这个函数时传进来的两个数(参数)
function add(a, b) {
// return 是把结果交出去:谁调用 add,谁就能拿到 a + b 的结果
return a + b
}
// 调用 add:把 3 和 5 传进去,返回的 8 存进变量 sum
let sum = add(3, 5) // sum = 8
2.2 箭头函数(KubeJS 里最常见)¶
// 普通写法
// 用 function 关键字定义函数:double 接收一个参数 x,返回 x 乘以 2 的结果
function double(x) {
return x * 2
}
// 箭头函数写法(一样的意思)
// const 是声明常量(不能重新赋值);(x) => 是箭头函数,x 是参数,箭头右边是函数体
const double = (x) => {
return x * 2
}
// 更简:单表达式直接返回,省略 return 和花括号
// 箭头函数体只有一个表达式时,会自动把这个表达式的值作为返回值,所以不用写 return
const double = x => x * 2
KubeJS 的事件回调就是箭头函数:
// BlockEvents.broken 是"方块被破坏"的事件;箭头后面是事件发生时执行的代码
BlockEvents.broken(event => {
// event 就是箭头函数的参数(事件对象),装着这次破坏的信息(哪个玩家、哪个方块等)
})
2.3 函数默认参数¶
// count = 1 是默认参数:调用时没传第三个参数,就自动用 1 代替
function giveItem(player, itemId, count = 1) {
// 问号冒号是三元运算符:条件 ? 值A : 值B,条件成立取 A,否则取 B
// count > 1 时拼出 "5x ",否则拼出 ""(空字符串);再加号把物品 ID 拼成完整字符串
player.give((count > 1 ? count + 'x ' : '') + itemId)
}
// 调用:没传第三个参数,count 自动用默认值 1,所以给 1 个苹果
giveItem(event.player, 'minecraft:apple') // 给 1 个
// 调用:传了 5,count 就是 5,给 5 个苹果
giveItem(event.player, 'minecraft:apple', 5) // 给 5 个
2.4 函数返回值用于判断¶
// 定义函数 isOre:接收一个物品 ID,判断它是不是矿石
function isOre(id) {
// .includes() 判断字符串里有没有包含某段文字,有就返回 true
// || 是"或者":ID 里含 "_ore" 或含 "_raw" 都算矿石;return 把 true/false 交出去
return id.includes('_ore') || id.includes('_raw')
}
// 监听"方块被破坏"事件
BlockEvents.broken(event => {
// event.block.id 是被破坏方块的 ID;把它传给 isOre,用返回值做 if 判断
if (isOre(event.block.id)) {
// 判断结果是 true(是矿石)就执行:发消息告诉玩家
event.player.tell('挖到矿石啦!')
}
})
2.5 函数里调用其他函数¶
// 第一个函数:随机从奖池里挑一个奖励名字,用 return 交出去
function getRandomReward() {
// 奖池数组:3 种奖励的名字(字符串)
let pool = ['diamond', 'emerald', 'iron_ingot']
// 随机取一个整数下标,取出对应元素;return 把抽到的奖励交出去
return pool[Math.floor(Math.random() * pool.length)]
}
// 第二个函数:负责把奖励发给玩家,接收一个玩家对象
function giveReward(player) {
// 调用另一个函数 getRandomReward(),把抽到的奖励存进变量 reward
let reward = getRandomReward() // 调用另一个函数
// 加号把 "minecraft:" 和奖励名字拼成完整物品 ID,然后发给玩家
player.give('minecraft:' + reward)
// 告诉玩家他抽到了什么
player.tell('获得了:' + reward)
}
// 监听"方块被破坏"事件
BlockEvents.broken(event => {
// === 是"严格相等";如果被破坏的方块是奖励方块
if (event.block.id === 'kubejs:reward_block') {
// 把当前玩家传给 giveReward,让它去完成发奖励这件事
giveReward(event.player)
}
})
3. 调用(Calling)¶
3.1 调用事件对象的方法¶
KubeJS 里几乎所有操作都是「调用方法」:
// 点号 . 就是"调用":player 这个对象上有 tell 方法,作用是给玩家发消息
event.player.tell('消息') // 调 player 的 tell 方法
// 调 give 方法:给玩家一个钻石
event.player.give('minecraft:diamond') // 调 give 方法
// 先拿 player 的 server(服务器),再调它的 runCommandSilent 方法执行命令
event.player.server.runCommandSilent('say hi') // 调 server 的命令方法
// 调方块对象的 popItem 方法:让这个方块掉落一个苹果
event.block.popItem('minecraft:apple') // 方块掉落物品
// 调 cancel 方法:取消这次事件(比如取消合成、取消右键效果)
event.cancel() // 取消事件
3.2 链式调用(一个接一个)¶
KubeJS 的注册 API 支持链式调用:
// StartupEvents.registry('item', ...) 是"注册物品"事件,event.create 创建物品
StartupEvents.registry('item', event => {
// 每个 .方法() 后面又能接下一个 .方法(),一行接一行写下来,这叫链式调用
event.create('magic_sword')
.displayName('魔法剑') // 设置物品显示名称
.maxStackSize(1) // 设置最大堆叠数为 1(一格只能放一个)
.tooltip('非常厉害!') // 给物品加一条提示文字
.unstackable() // 标记为不可堆叠
})
原理:每个
.方法()都返回对象本身,所以能一直接下去。
3.3 调用游戏命令¶
// 单条命令:runCommandSilent 让服务器静默执行命令,这里把时间设为白天
event.server.runCommandSilent('time set day')
// 带变量的命令(用模板字符串)
// 声明一个变量,存玩家名字
let playerName = 'Steve'
// 反引号模板字符串:${playerName} 会被替换成 Steve,拼出完整的 give 命令
event.server.runCommandSilent(`give ${playerName} minecraft:diamond 5`)
// 执行一串命令(用 ; 或换行分隔)
// 用分号 ; 把两条命令连在一起,一次执行两句
event.server.runCommandSilent('say 第一句; say 第二句')
3.4 调用全局工具函数(跨文件复用)¶
把常用函数放进 server_scripts/utils.js,其他脚本都能直接调:
// utils.js —— 全局工具(所有 server 脚本共享)
// 随机整数函数:和前面一样,返回 min 到 max(含两端)的随机整数
function randomInt(min, max) {
// 随机小数 × 数量范围,向下取整,再加 min
return Math.floor(Math.random() * (max - min + 1)) + min
}
// 随机奖励函数:接收一个玩家对象,给 TA 随机发一件奖励
function giveRandom(player) {
// 奖池数组:3 种物品的 ID
let items = ['minecraft:diamond', 'minecraft:emerald', 'minecraft:gold_ingot']
// randomInt(0, items.length - 1) 随机出 0~2 的合法下标,取出对应物品发给玩家
player.give(items[randomInt(0, items.length - 1)])
}
// 其他任何脚本里直接调用
// 监听玩家聊天事件
PlayerEvents.chat(event => {
// 玩家说的话正好是 "!领奖" 才往下走
if (event.message === '!领奖') {
// 直接调用 utils.js 里定义的全局函数(跨文件共用,不用重新写一遍)
giveRandom(event.player)
}
})
3.5 调方法时注意「谁是调用者」¶
// ❌ 错误:没有指定给谁
// give('minecraft:diamond')
// ✅ 正确:player.give(...)
// 方法必须挂在某个对象上调用:这里明确是"给这个玩家"钻石
event.player.give('minecraft:diamond')
// ❌ 错误:tell 前面没对象
// tell('hi')
// ✅ 正确
// tell 也要有调用者:告诉"这个玩家" hi
event.player.tell('hi')
4. 常用小技巧(写脚本顺手用)¶
4.1 数组常用操作¶
// 定义一个数组,里面 3 个字符串元素,下标分别是 0、1、2
let list = ['a', 'b', 'c']
// .push() 在数组末尾加一个元素,执行后数组变成 ['a','b','c','d']
list.push('d') // 末尾加:['a','b','c','d']
// .includes() 判断数组里有没有 'a',有就返回 true
list.includes('a') // true —— 判断有没有
// .length 是数组的属性,表示元素的个数
list.length // 长度
// forEach 遍历:数组里每个元素都会执行一次花括号里的代码,item 是当前元素
list.forEach(item => { ... }) // 遍历
// .map() 把每个元素都加工一遍(这里每个都加上 "!"),返回一个新数组,原数组不变
list.map(x => x + '!') // 每个元素加工,返回新数组
// .filter() 过滤:只留下不等于 'b' 的元素,返回一个新数组
list.filter(x => x !== 'b') // 过滤
判断物品在不在清单里:
// 禁用清单:一个数组,里面是不能合成的物品 ID
let banned = ['minecraft:bedrock', 'minecraft:command_block']
// 监听"玩家合成物品"事件
ItemEvents.crafted(event => {
// .includes() 判断合成出的物品 ID 在不在禁用清单里
if (banned.includes(event.item.id)) {
// 在清单里就取消这次合成(物品不会合成出来)
event.cancel()
// 再发消息告诉玩家原因
event.player.tell('这个物品不能合成!')
}
})
4.2 字符串常用操作¶
// 声明一个字符串变量,存一个物品 ID
let id = 'minecraft:stone'
// .includes() 判断字符串里有没有包含 'stone' 这段文字
id.includes('stone') // true —— 包含
// .startsWith() 判断字符串是不是以 'mine' 开头
id.startsWith('mine') // true —— 开头
// .endsWith() 判断字符串是不是以 'one' 结尾
id.endsWith('one') // true —— 结尾
// .split(':') 按冒号把字符串切开,得到数组 ['minecraft','stone'];[0] 取第一段
id.split(':')[0] // 'minecraft' —— 按 : 切开取第一段
// [1] 取数组第二段,也就是冒号后面的部分
id.split(':')[1] // 'stone'
// 拼接
// 声明两个字符串变量
let modId = 'kubejs'
let itemId = 'my_item'
// 模板字符串:${modId} 和 ${itemId} 会被替换成实际的值,拼成完整物品 ID
let full = `${modId}:${itemId}` // 'kubejs:my_item'
4.3 数值处理¶
// Math.floor() 是向下取整(去掉小数部分):3.7 变成 3
Math.floor(3.7) // 3 —— 向下取整
// Math.ceil() 是向上取整:3.2 变成 4
Math.ceil(3.2) // 4 —— 向上取整
// Math.round() 是四舍五入:3.5 变成 4
Math.round(3.5) // 4 —— 四舍五入
// Math.max() 取括号里所有数字中最大的那个
Math.max(1, 5) // 5
// Math.min() 取括号里所有数字中最小的那个
Math.min(1, 5) // 1
// Math.abs() 取绝对值(把负数变正数)
Math.abs(-5) // 5 —— 绝对值
4.4 判断空值(undefined / null)¶
// 玩家身上没有 persistentData 时,给个默认值
// 取出玩家身上的持久化数据(存在玩家身上的"小仓库",下次上线还在)
let data = event.player.persistentData
// || 是"或者":data.kills 没值(undefined)时就按 0 算,加 1 后再存回去
data.kills = (data.kills || 0) + 1 // 没有就按 0 算
4.5 条件里多个判断¶
// && 是"并且":两个条件都成立才执行花括号里的代码
if (event.player.username === 'pyz' && event.item.id === 'minecraft:diamond') {
// 两个条件都满足才执行
}
// || 是"或者":两个条件只要有一个成立就执行
if (event.item.id === 'minecraft:apple' || event.item.id === 'minecraft:bread') {
// 满足其中一个就执行
}
5. 综合案例:抽奖箱(随机+函数+调用全用上)¶
// server_scripts/lottery.js
// 这是一个完整的抽奖箱脚本:随机数、函数、调用全用上了
// 1. 函数:随机整数
// 定义随机整数函数:返回 min 到 max(含两端)的随机整数
function randomInt(min, max) {
// 随机小数 × 数量范围,Math.floor() 向下取整,最后加 min
return Math.floor(Math.random() * (max - min + 1)) + min
}
// 2. 函数:从奖池抽一个
// 定义抽奖函数:按照权重随机抽一个奖励对象,用 return 交出去
function rollReward() {
// 奖池:数组里每个元素是一个"对象"(用花括号 { } 包起来,里面是 属性: 值)
// item 是物品 ID,count 是给几个,weight 是权重(数字越大越容易抽中)
let pool = [
{ item: 'minecraft:diamond', count: 1, weight: 10 },
{ item: 'minecraft:emerald', count: 2, weight: 20 },
{ item: 'minecraft:iron_ingot', count: 5, weight: 50 },
{ item: 'minecraft:golden_apple', count: 1, weight: 5 }
]
// 按权重抽奖
// reduce 把数组里每个奖励对象的 weight 累加起来,得到总权重(10+20+50+5=85)
let total = pool.reduce((sum, r) => sum + r.weight, 0)
// 在 0 到总权重之间随机一个小数,相当于转一次转盘
let roll = Math.random() * total
// acc 用来累计已经走过的权重,从 0 开始
let acc = 0
// for...of 循环:依次取出奖池里的每个奖励对象,命名为 r,每轮处理一个
for (let r of pool) {
// 把当前奖励的权重累加到 acc 上
acc += r.weight
// 随机数落在哪一段区间就抽中哪个奖励;return 把抽中的对象交出去
if (roll <= acc) return r
}
// 兜底:万一上面循环都没命中,就返回奖池里的第一个奖励
return pool[0]
}
// 3. 调用:右键打开抽奖箱
// 监听"玩家右键点击物品"事件
ItemEvents.rightClicked(event => {
// 点击的不是抽奖箱就提前结束(return 后面的代码不执行)
if (event.item.id !== 'kubejs:lottery_box') return
// 调用抽奖函数 rollReward(),抽中的奖励对象存进变量 reward
let reward = rollReward() // 调用抽奖函数
// 模板字符串拼出 "2x minecraft:emerald" 这样的字符串,调用 give 发给玩家
event.player.give(`${reward.count}x ${reward.item}`) // 调用 give
// 模板字符串拼出提示消息,§a 是绿色颜色代码;调用 tell 发给玩家
event.player.tell(`§a抽中了:${reward.item} ×${reward.count}!`) // 调用 tell
})
6. 速查表¶
| 想做什么 | 写法 |
|---|---|
| 随机小数 | Math.random() |
| 随机整数 1~100 | Math.floor(Math.random()*100)+1 |
| 随机选一个 | list[Math.floor(Math.random()*list.length)] |
| 30% 概率 | if (Math.random() < 0.3) |
| 定义函数 | function 名字(参数) { ... } |
| 箭头函数 | x => x * 2 |
| 调用方法 | 对象.方法(参数) |
| 链式调用 | .方法().方法().方法() |
| 执行命令 | server.runCommandSilent('命令') |
| 数组包含 | list.includes(x) |
| 遍历数组 | list.forEach(x => { ... }) |
| 字符串包含 | str.includes('xx') |
| 取物品ID | event.item.id |
| 发消息 | event.player.tell('...') |
| 给物品 | event.player.give('id 数量') |
| 取消事件 | event.cancel() |