本帖最后由 TUCAOEVER 于 2021-6-22 17:23 编辑
前言:
复制代码Skript 一个面向MineCraft的编程语言
因为其语法简单而受到很多中小型服主的青睐 很多人多多少少对这块有一些了解
但是毕竟受众人群小 很多时候也会出现想学却无从下手 有问题却无处可问的尴尬境地
站内现有的Skript的教程 https://www.mcbbs.net/thread-492211-1-1.html 最后一次编辑为2016年
虽然讲的东西很多 但是基本上是基于原版教程的少量扩充 对于新手理解起来还是过于困难
作为
Fundamental (https://www.mcbbs.net/thread-822402-1-1.html)
SUPERGUILDS (https://www.mcbbs.net/thread-870934-1-1.html)
的作者 我自认为自己对 Skript 还是多少有些理解
我希望通过这篇教程谈谈我在编写 Skript 脚本过程中的一些经验之谈
基础教程将分为
初识代码
了解"5大类"
"5大类"用法
"loop"用法
注册指令
"function"用法
板块进行教学
————————————————————————————————
初识代码:
Skript 不同于其他语言 正如我在开头所展示的 相比较 Java 而言 Skript 更注重的是语言而非编程
语言是什么 就是 人说的话 也就是说 编写 Skript 插件 = 把你想的说出来 而这种语言是英语
也就是说只要你知道英语的基本语法 学习 Skript 便不成问题
不过对于初学编程的玩家 我还是需要明确几点基本原则
1.理论是基础 也是必要的 实践得到的经验是学好的唯一途径 只学理论永远写不好插件
2.读懂其他脚本编写者的脚本比自己复制粘贴拼凑插件对你的学习更有意义
————————————————————————————————
安装Skript
Skript是一款插件 也就意味着你需要像安装其他插件一样 需要放在 plugins 目录下
很多人认为越新版本的插件对服务器越好 但实际上对于 Skript 不是这样的
作为用户我大致上体验了 2.1.2 2.2dev36 2.2dev37c 2.3.5 这几个版本
不同版本的服务器个人推荐使用的 Skript 插件版本如下
1.7.10 2.1.2 https://dev.bukkit.org/projects/skript/files/779542/download
1.8.8-1.12.2 2.2dev37c https://github.com/SkriptLang/Skript/releases/download/dev37c/Skript.jar
1.13.2+ 最新版 https://github.com/SkriptLang/Skript/releases
————————————————————————————————
如何编写脚本?
将如上文件安装完成后 plugins 目录下将多出 一个 Skript 文件夹 文件夹里面也多出了许许多多文件
现在你不需要理会他们
使用文本编辑器 Sublime/Visual Studio Code/TXT 在 plugins/Skripts/scripts 目录下创建一个名为 "1.sk" 的文件
将如下代码复制进 "1.sk" 文件内 并保存
复制代码重启服务器 加入服务器 你发现服务器向你发送一条消息 "Hello 你的游戏名称"
因为脚本生效了 脚本监听了玩家登陆事件并在玩家触发这一事件后执行了发送信息行动 所以你看到了 "Hello 你的游戏名称"
需要注意的是在 Skript 中 如果一行代码以 ":" 结尾 那么下一行需要进行缩进操作 如果没有就不需要进行缩进
例如:
————————————————————————————————
在正式进入 Skript 教程之前 我们还需要了解编程的几大元素 变量 运算 控制
● 变量
● 运算
● 控制
————————————————————————————————
初识 "5大类"
Skript 脚本核心 即为 "5大类"
分别为 "Events"(事件) "Conditions"(条件) "Effects"(效果) "Expressions"(表达) "Types"(类型)
为了让大家能更好的认知这几大内容 我这里举一个例子 如果我想制作一个如下功能的脚本
复制代码我们来分析这个句子
首先是第一句 玩家加入服务器 名词 + 动词 +名词 的格式
典型的第1类(Events) 服务器会监控每一位在线玩家的一举一动 每一个方块的一举一动
我们统称为事件 我们想要知道这些事件 就需要监听器 监听玩家的行动
复制代码通过查阅 Doc我们知道了加入的监听是 “Join” (有关Doc的内容将在 “五大类”用法 进行详细说明)
有了监听器 我们就可以监控玩家/方块的各种行为 (事件) 并在这些玩家触发监听器时 对这些事件进行调控
————————————————————————————————
send "..." to player #将引号内的信息发送至玩家[/code]
第二句 是否只有一个玩家 名称 + 是否/有无 的格式 典型的判断句
对应第2类(Conditions) 怎么让服务器知道是不是只有一个玩家在线 判断语句就起到了非常重要的作用
获取数据并进行对比 然后得到结果 这就是判断语句需要做的事情
判断句的一般以 “if” 开头 + “判断条件” 构成 这里的 玩家数量 属于第4类 Expressions(表达)
复制代码我们可以看到 这一条判断共有2个分支
1.玩家数量为1 > 发送一条信息
2.玩家数量不为1 > 踢出所有玩家
为1的我们已经知道了 “if player count is 1” 那么不为1的又该如何判断?
这时候 “else” 就充分发挥了作用 “else” 中文翻译为 “其他的,不同的” 顾名思义
如果我们的主条件是玩家数量为1 那么对立面(玩家数量不为1)就是 ”else” 的范畴
复制代码————————————————————————————————
第三句第四句判断句结尾 紧跟着需要执行一个行动
第三句需要执行 发送一条信息至该玩家
第四句需要执行 踢出该玩家
动词 + 名称 典型的动词句 对应Effects(效果) 类
通过查阅 Doc 我们了解到
发送消息的语法是 “send %string% to %commandsenders%”
踢出玩家的语法是 “kick %players%”
看到 “%” 的你们可能和我一样迷茫 这些究竟是什么呢?
这时候就需要第5类Types(类别) 再次查阅 Doc 在 Types(类别) 下
我们找到了command sender 我们得知 %commmandsenders%
该(Types)类别下一共有3个元素可供选择
一个是 “console(后台)” 一个是 “player(玩家)” 一个是 “players(所有玩家)”
我们需要什么呢? “发送一条信息至该玩家” 是的 我们需要的是玩家 即 “player”
同样的 “踢出所有玩家” 我们需要的是所有玩家 即 “players”
综合监听和条件我们就有了一段基础代码 实现了我们设计所需要的效果
复制代码怎么样这样分析下来 是不是感觉 Skript 也没有想象那么难不是么
————————————————————————————————
"5大类"用法
在开讲之前 我需要先和大家分享几个查阅脚本语法的网站
● 推荐
https://skripthub.net/docs/ 部分老版本附属语法查不到 但查询方便 有效
官方Doc https://skriptlang.github.io/Skript原版Skript Doc方便查询新手学习
● 不推荐
中文Wiki 很久没有更新 很多东西都查不到 适合学习不适合查语法
Skunity https://docs.skunity.com/syntax/ 内容非常多 但很多已经无效 没有更新
这些网站将频繁出现在我们的教程之中 请务必收藏好
————————————————————————————————
● Events(事件)
什么是事件?
什么人在什么地方做了什么事情 最简单的三要素构成了事件
那么 "谁" 能知道 什么人在什么地方做了什么事情? 这时候监听器就理所应当成为了那个 "谁"
所谓的 Events 并不是单纯指一个事件 而是指监听器监监听到了一个事件
所有原版的 Skript 监听器都可以在下面这个网址找到
https://skriptlang.github.io/Skript/events.html
可以看到 Skript 提供给我们一共 120 个监听可供使用这么多的监听 我们究竟该怎么选择呢 选择完又该如何使用呢?
这里我们拿 "on command" 监听器为例(命令执行监听器)
我们把我们的定义拿过来 什么人在什么地方做了什么事情
现在我们已知什么事情指的是执行命令 现在我们需要知道什么?
我们需要知道的是 什么人? 什么地方?
那么我们如何知道这些元素呢? 这时候我们需要下面这个网站
https://skripthub.net/docs/
在侧边栏中选中(Skript Events)后 在搜索栏中输入 "on command"
我们要重点关注的是 "Event Values" 这一标签下所对应的内容
可以看到 "on command" 卡片上 "Event Values" 标签下共有三个字段
分别为 "event-world" "event-commandsender" 以及 "event-player"
翻译对应为 "事件-世界" "事件-指令发送者" "事件-玩家"
利用这些 我们便可以获取到 事件中的 什么人 什么地方 这些具体的信息
我们看一个 "on command" 相关示例
复制代码我调取了指令监听器 任何执行者执行 "/op" 指令都会触发此监听
监听被触发后 将自动将三个元素 "event-world" "event-commandsender" "event-player" 输出到后台
如果是一位玩家执行了 "/op" 命令 "event-world" 将会输出玩家在哪个世界执行了命令
"event-commandsender" 将会输出为 "player" 因为监听器由玩家触发 而 "event-player" 则会输出为 "玩家的名字"
那么如果是后台执行了 "/op" 命令呢? 因为后台并不存于任何一个世界 也没有名字
上述三个元素只会有 "event-commandsender" 存在并正常输出为 "console" 而其余不存在元素将全部输出为 "<none>"
相同的你可以利用这样的方法 输出任何一个监听器下 "Event Values" 的元素值
这种获取元素值的方法将在你需要使用任何从来没有接触过得监听器的时候 快速让你掌握监听器的基本信息
既然我们已经监听到了这个事件 除了获取一些与事件相关的信息之外 我们还能做一些什么呢?
取消事件!
事件发生的顺序是 事件准备发生 > 监听器监听到 > 事件正式发生
如果我们在监听器监听到后 加入 取消事件 这一环节
事件发生的顺序就变为了 事件准备发生 > 监听器监听到 > 取消事件 > 事件未发生
我们就成功阻止了 "/op" 指令的发生 那在实际脚本中我们怎么实现这一流程呢?
cancel event 譬如我们上面所说的这段指令 我们在结尾加入 cancel event
复制代码通过测试我们发现 元素仍然能正常输出 但是 "/op" 指令不再生效
即使取消了事件 事件的元素仍然会被传递至插件内 这个是需要注意的
————————————————————————————————
● Conditions(条件)
条件用于判断句 有没有 是不是 都是条件的一种 基本格式为 "if" + 条件
这里我们和学习 Events 一样 我们先通过官方 Doc 找到所有的条件
这里我们拿最常用的一个条件作示例 判断玩家是否有权限
权限的英文是什么? "permission" 我们通过翻阅侧边栏可以得知
与 "permission" 相关的只有 "Has Permission" 一条 官方对这个条件的解释为
"Test whether a player has a certain permission." 翻译过来就是 "检测一个玩家是否拥有某一权限"
即我们所需要的 判断玩家是否有权限 那么我们又该如何使用呢 "Has Permssion" 条件呢?
在 "Has Permission" 下 "Patterns" 给了我们两种标准格式用法
复制代码针对这样的格式 我相信很多人可能一头雾水 了解如下几点 或许能帮助你更好的了解用法
player has permission "player.op"
player have the permissions "player.op"
player have permissions "player.op"
...
正如之前所说的 可以省略的地方 无论如何搭配 表达的意思都是一样的
这也是 Skript 一大特点 并不需要非常严谨的语法 只要意思对 语法可以根据个人喜好进行选择
同样我们还是举一个以 "on command" 监听器为核心的例子加深一下大家的理解
复制代码那么在玩家触发此监听器后 系统将会判断玩家是否有 "player.op" 权限
如果有 指令将会正常进行 并发送 "true" 给玩家
如果没有 指令执行事件将被强制取消 并发送 "false" 给玩家
————————————————————————————————
● Effects(效果)
与其说它是效果 不如称作行动
我们把条件类的示例拿下来接着分析
复制代码我们把两个条件判断句 分别用条件1和条件2替换 那么这段代码就可以写成
复制代码我们可以看到 条件判断结束后 代码并没有直接结束 而是分别执行行动
如果我希望执行其他行动呢? 比如我想给一个玩家发送一个 Title 消息
通过在官方 Doc 搜索 关于 Title 相关的 Effects 一共有两个
https://skriptlang.github.io/Skript/effects.html#EffResetTitle
https://skriptlang.github.io/Skript/effects.html#EffSendTitle
通过查看官方的注释
#EffResetTitle
"Resets the title of the player to the default values."
"重置玩家的 Title 至默认值"
#EffSendTitle
"Sends a title/subtitle to the given player(s) with optional fadein/stay/fadeout times."
"发送 Title/Subtitle 至指定玩家 可自定义渐入和淡出的时间"
一目了然 我们需要的是 #EffSendTitle 关于怎么用
本质上和学习Conditions(条件)一样 我们将注意点放在 "Patterns" 上
复制代码按照我们提到的规则
我们大致上认识到行动的基本用法 即
复制代码将其带入进我们的主代码
复制代码三种方式我们都成功的发送了 Title 信息 只要记住我们的规则
所有的用法问题迎刃而解 学会读 "Patterns" 是初学者必备的技能
下文我将不会再提及如何使用 "Patterns" 查阅用法
————————————————————————————————
● Expressions(表达) & Types(类型)
假设你想要调整玩家的最大血量
调整血量 之前我们提过 动词 + 名词形式 这是属于 Effects(效果) 类
但是实际上是这样么?
实际上并不是 所谓的 Effect(效果) 虽然都是 动词 + 名词形式 但是对于 Effect(效果) 而言
注重的是 动词 而非后面跟着的 名词 例如 在玩家所在位置生成僵尸
Effect(效果) 所能提供的 只有 "生成"
通过查阅 Doc https://skriptlang.github.io/Skript/effects.html#EffSpawn 我们知道生成的用法基本为
复制代码但是除去这个词语 我们还剩 "在玩家所在位置" 和 "僵尸"
这两个词我们又该怎么处理呢? 这时候我们就需要用到 Expressions(表达)
位置的英文单词是 "Location" 我们在官方 Doc 查到了 3 种有关 "Location" 的表达
https://skriptlang.github.io/Skript/expressions.html#ExprLocation
https://skriptlang.github.io/Skript/expressions.html#ExprLocationOf
https://skriptlang.github.io/Skript/expressions.html#ExprLocationAt
我们需要什么呢? 我们需要 "玩家所在的位置" 相同地 通过查看官方的注释
我们知道我们需要的是 #ExprLocationOf (The location of a block or entity.)
但是这只解决了我们 "所在位置" 的问题 并没有解决 "玩家" 和 "僵尸" 的问题
我们可以看到 "所在位置" Expressions(表达) 是没有主语的 但是对于 Skript 而言
我们需要把这些零碎的东西组成句子 必不可少的是主语 次要的是宾语
这时候我们就需要引入 Types(类型) 但凡你发现你的表达里缺少主语/宾语(对象) 来这里准没错
https://skriptlang.github.io/Skript/classes.html
通过翻译我们可以轻松知道 玩家的英文 以及 僵尸的英文 分别为 "player" "zombie"
与之相对应的 我们分别在 Types(类别) 中找到
https://skriptlang.github.io/Skript/classes.html#player
https://skriptlang.github.io/Skript/classes.html#entity
综合上面我们所获得的信息 我们获得了完整一行代码
复制代码————————————————————————————————
Wow 恭喜你 看到这 你就可以开始尝试着写一些插件了
这里刚好有一个例子 不妨动动手 试一试
在玩家破坏方块时 检查玩家是否有 "fundamental.break" 这个权限
如果有那就在让后台发送一条命令 "/broadcast %player% 破坏了方块"
如果没有那就取消这个事件 并 向这个玩家发送 "你不能破坏这个方块"
答案不唯一 仅供参考
当然仅仅学这些并不够 为了做到能更快更灵活的使用各类语法
在闲暇的时候 把官方 Doc 提供的所有语法的注释都认真的看一遍是快速上手 Skript 的一种好办法
————————————————————————————————
"loop"用法
你开始尝试着写一些有一些小功能的脚本了 但是难免的你会出现这样的问题
你为玩家创建了很多变量 很多都是属于一个类型的
比如你把所有人的游戏币数量都存在了 {(玩家的名称)的游戏币数} 这些变量内
你把所有人的点券数量都存在了 {(玩家的名称)的点券数} 这些变量内
平时你单独去操作这些变量的时候 觉得也很容易
但是假设突然有一次 由于回档需要补偿玩家损失
你需要将所有玩家的 {(玩家的名称)的游戏币数} 这个变量都 +1000
还好只有10个玩家数据 你可以一个一个调 就是浪费一点时间 倒还不成问题
但是如果你有10000个玩家数据 如果你一个一个调 可能玩家都走完了 你也调不完
你遇到了新的问题 如何存储并快速操作一类变量?
这时候你需要两样东西 "数组" "loop"
● 数组
数组的基本格式为 {变量名::变量名::变量名......}
我们带入实景 将用 {(玩家的名称)的游戏币数} 存储转为用 {金币::(玩家的名称)} 存储玩家的游戏币数量
我现在服务器有 10000 个玩家
玩家名称为 1,2,3,......,10000
玩家游戏币数量为 100,200,300,......,1000000
那么对应的 {金币::1} 就是 名为 "1" 玩家的游戏币数量 100
如果我用输出语句输出 {金币::1} 至后台 那么很显然我会得到 100 这个数值
同样的我用输出语句输出 {金币::10000} 至后台 我会得到 1000000 这个数值
你可能会说 这不是和用 {(玩家的名称)的游戏币数} 一样么?
是的 确实 如果仅仅需要获得某一个玩家的游戏币数量 两者并没有什么区别
但是 数组好用之处在于 如果我将 {金币::(玩家的名称)} 中玩家的名称改为 "*" 即 {金币::*}
这时候会产生什么样的效果呢?
{金币::*} 将包含 所有 {金币::(玩家的名称)} 变量
而这个 却是 {(玩家的名称)的游戏币数量} 怎么改也做不到的
● Loop
利用数组我们知道了如何快速获取一类数据
但是我们又该如何快速操作这一类数据呢? 这时候就需要引入我们的 Loop 结构
Loop 即 循环结构 是 Skript 里非常常用的结构语句 主要用于操作数据量较大的一类变量
Loop 共有几大标准配合
Loop + 数组 / Loop + 次数 / Loop + Types(类型)
● Loop + 数组 结构
● Loop + 次数 结构
● Loop + Types(类型) 结构
————————————————————————————————
● 注册指令
说到现在 我们所有的代码 似乎都是基于监听器进行编写的
我们都需要去触发监听器 才能执行我们的代码 那有没有什么办法可以主动触发我们的代码?
这时候我们就需要引入 MineCraft 插件最核心的功能 指令功能而在 Java 里你可能需要这样注册一个指令复制代码但是在 Skript 里你只需这样即可
复制代码你并不需要理解其他是什么意思 仅仅需要记住这个格式即可
如果我想注册一个 "/我学你马Java" 的命令 你只需这样
复制代码通过测试 指令正常触发
通过套公式 你可以创造成千上万的指令不成问题 但是实际上我们在使用一个插件的时候
并不是只有 "/..." 结构的指令存在 更多的是 "/... ... ..." 来构成一类指令
那我们又该如何注册这样结构的指令呢? 非常简单 基本格式与上面几乎无异
复制代码本人写代码时常用的结构就是这样
有人问 "类型" 有哪些 其实我也说不全 我常用的有这几种
"text" - 字符类型 什么是字符? 就是字面意思 字词符号
"player" - 在线玩家
"offline player" - 离线玩家
"number" - 数字类型
"integer" - 整数类型
那这些类型又有什么用处呢? 它实际上是限制了你可以输入的参数
比如我创建如下指令复制代码可以看到 第一个空格的位置 我需要的参数类型为在线玩家
那么我在执行这个指令的时候必须在这个位置上填上一个在线玩家的名称
同样的如果我把 "[<player>]" 换成 "[<integer>]" 我就需要在这个位置上填写一个整数
如果我填了 "1.2"(小数/浮点数) Skript 就会提示我 填写的参数类型错误
为什么填写参数 那肯定是在代码段内需要使用这些输进来的参数
那么我们在代码段里有该如何调用这些被我们输入进来的参数呢?
比如像是上面这个指令 它只有一个可以填参数的位置 那么在代码段内 它就是 arg-1 即 第一个参数的意思
我们只需要记住核心规则 它排在第几位 在代码段内 它就是 "arg-几"
当然我在这块的了解并不是很深入 为了不把大家带上歪路 这里引用国外 Skript 作者更为详细的指令注册的教程
————————————————————————————————
复制代码命令名称(必填)
命令名称基本上是命令 您可以在命令名称中使用任何字符(空格字符除外)
当然如果在命令名称中使用空格字符 那么空格字符后的文本将成为参数
命令名称前的斜杠字符(/)是可选的(但这并不意味着您可以在执行命令时不带斜杠)
参数(可选)
可以通过将参数放在 "[]" 中来使其成为可选参数
类型参数
可以通过使用规定的格式来限制参数的类型 例如: <type = default value>
- 类型为 "text/string" 的参数可以接受任何字符 但 "object" 类型不能用作于参数
- 类型可以是多个 (例如 number -> numbers entity -> entities) 通过这样的方法 可以使参数接受多个值
- "= default value" 这一部分是可选的 如果命令执行者未输入参数 系统将自动使用默认值
- 同样你也可以使用这样的方式设置参数默认值 例如: <item = %player's tool%>
命令示例:
复制代码使用 /kill zombies /kill creepers and animals in radius 100 或 /kill monsters in the radius 6 都是可以的
但是如果没有输入数值 系统将自动使用默认值 半径 20
Aliases
子命令 命令的别名 如果需要创建多个子命令 请使用用逗号分隔
示例:(/alias1,alias2,/alias3)
Executable By
指定可以使用该命令的执行者
例如:console(后台) players(玩家) the console and players(后台和玩家)
Usage
使用不正确时 将发送的消息
Description
命令描述 其他插件可以获取/显示此信息
Permission
执行命令所需要的权限
Permission Message
执行者没有权限 提示信息
Cooldown
多长冷却时间后可以再次使用该命令 需要注意的是 关服时所有命令冷却时间将被重置
Cooldown Message
冷却期间 提示信息
Cooldown Bypass
无视冷却时间所需要的权限
Cooldown Storage
存储冷却时间全局变量名称
————————————————————————————————
● Function 第一类结构
众所周知 一个复杂的插件 不免出现相似的代码段
有时候你看着不舒服 想要缩减段落 却又无从下手 这时你就需要 Function 来帮忙了
这里取本人的SUPERGUILDS的一段代码做讲解复制代码可能不知道这段代码的意思 我现在告诉你
这段代码的功能是存储数据至yaml文件内 对于一个非常复杂的脚本 数据的存储是必不可少的
同样也会频繁出现在我们的代码中的 难道真的每一次需要存储数据的时候 都需要再去复制粘贴么?
对于一个初学者来说 无可厚非 就是多几行而已 但是对于一个老手来说 复制粘贴不可取
首先对于一个 5000+ 以上的插件来说 你需要关注的东西很多 一个是代码优化 一个是代码可读性
就拿 SUPERGUILDS 来说 7000+ 行 我所有的数据读取和存储都是通过方法完成 单一个数据写入方法
我就使用了 100+ 次(也就是上面我提到的这段代码) 如果我们把它都像上面一样全部展开 我的脚本将立即增加 600+ 行
但是我们在写脚本的时候真正需要的是这些么? 不 我们需要的是效果 是功能 不是数据处理的流程
每次写入数据都需要白白多占 6 行 既不方便后期维护 同时因为要兼顾路径正确与否 浪费很多时间在查错上面 不划算
Function 为你解决了这些难题 方法的注册和指令的注册有共同的地方 比如它们同样需要参数 结构一般为
复制代码(关于参数名以及参数类型的定义 可以在 "Command" 栏目下找到 此处不再赘述)
套用公式 我们可以把上面的代码段转换成方法段复制代码若想将 "plugins/SUPERGUILDS/playerdata/玩家UUID.yml" 的 "Datas.Username" 设置为 "**EVER"
复制代码代码即可转换为复制代码使用方法的时候请勿画蛇添足在前面另加 "function"
通过这样的方法我们大大减少了代码量 提高了开发效率你以为 Function 就结束了?其实不然 作为方法 很多的时候它并没有 "操作" 功能
更多的是 "整理/查找/判断" 功能 那遇到这样的情况我们又该如何灵活使用 Function 以供我们使用呢?
上面我提到了使用 Function 写入了数据 那么有写入肯定需要读取 我们又该如何利用 Function 来缩短我们读取的代码呢?
————————————————————————————————
● Function 的第二类结构
复制代码与我们之前提到的写入不同 此时在方法的第一行的末尾我们新增了 "输出参数类型"
什么叫输出? 就是代码执行完 得到的一个数值 让这个数值回到主代码段 即 返回一个值
什么叫参数类型 这里举一个例子 我们知道 1+1=2 因为 1 是一个数字 2 也是一个数字 所以才有 数字+数字=数字
那如果我这样写 ①+一 那么又等于什么呢? "①" 和 "一" 在你的思维中 很可能会认为它们仍然是数字 你会觉得答案还是 2
但是执行我们代码的是计算机 并不是 我们 计算机所能识别的数字 只有 阿拉伯数字 "1,2,3..."
而 "①,一..." 计算机只能识别它们一些字符 并不能利用这些字符进行加减运算 就像是现在问你 水 + 苹果 = ?
你或许也没有一个准确的答案. 计算机更是这样
所以这时候就需要参数类型来规范我们运算中的这些值 拿一段来自 SUPERISLANDS 的代码做示例
复制代码通过第一类 Function 的学习我们知道这个方法可以放进去两个参数
最终会返回一个参数类型为 boolean("布尔")的值 即 "false/true"
那么实际操作中我们就可以通过这样一段代码对给定的数值进行判断 是否满足条件
如果满足那么返回 "true" 如果不满足 那么就将返回 "false"
需要知道的是 在 Skript 编程里 如果代码已经成功获得的最终值 那么方法代码将立即终止
从返回成功的一行开始 后面所有代码将不再执行 切记
到此 所有基础教程已结束 谢谢大家赏脸看完 全文 11111 字 都是自己的一些干货 点个收藏 给点人气便是对我最大的支持
-2020.2.28
● 更新日志

前言:
Skript 一个面向MineCraft的编程语言
因为其语法简单而受到很多中小型服主的青睐 很多人多多少少对这块有一些了解
但是毕竟受众人群小 很多时候也会出现想学却无从下手 有问题却无处可问的尴尬境地
站内现有的Skript的教程 https://www.mcbbs.net/thread-492211-1-1.html 最后一次编辑为2016年
虽然讲的东西很多 但是基本上是基于原版教程的少量扩充 对于新手理解起来还是过于困难
作为
Fundamental (https://www.mcbbs.net/thread-822402-1-1.html)
SUPERGUILDS (https://www.mcbbs.net/thread-870934-1-1.html)
的作者 我自认为自己对 Skript 还是多少有些理解
我希望通过这篇教程谈谈我在编写 Skript 脚本过程中的一些经验之谈
基础教程将分为
初识代码
了解"5大类"
"5大类"用法
"loop"用法
注册指令
"function"用法
板块进行教学
————————————————————————————————
初识代码:
Skript 不同于其他语言 正如我在开头所展示的 相比较 Java 而言 Skript 更注重的是语言而非编程
语言是什么 就是 人说的话 也就是说 编写 Skript 插件 = 把你想的说出来 而这种语言是英语
也就是说只要你知道英语的基本语法 学习 Skript 便不成问题
不过对于初学编程的玩家 我还是需要明确几点基本原则
1.理论是基础 也是必要的 实践得到的经验是学好的唯一途径 只学理论永远写不好插件
2.读懂其他脚本编写者的脚本比自己复制粘贴拼凑插件对你的学习更有意义
————————————————————————————————
安装Skript
Skript是一款插件 也就意味着你需要像安装其他插件一样 需要放在 plugins 目录下
很多人认为越新版本的插件对服务器越好 但实际上对于 Skript 不是这样的
作为用户我大致上体验了 2.1.2 2.2dev36 2.2dev37c 2.3.5 这几个版本
不同版本的服务器个人推荐使用的 Skript 插件版本如下
1.7.10 2.1.2 https://dev.bukkit.org/projects/skript/files/779542/download
1.8.8-1.12.2 2.2dev37c https://github.com/SkriptLang/Skript/releases/download/dev37c/Skript.jar
1.13.2+ 最新版 https://github.com/SkriptLang/Skript/releases
————————————————————————————————
如何编写脚本?
将如上文件安装完成后 plugins 目录下将多出 一个 Skript 文件夹 文件夹里面也多出了许许多多文件
现在你不需要理会他们
使用文本编辑器 Sublime/Visual Studio Code/TXT 在 plugins/Skripts/scripts 目录下创建一个名为 "1.sk" 的文件
将如下代码复制进 "1.sk" 文件内 并保存
重启服务器 加入服务器 你发现服务器向你发送一条消息 "Hello 你的游戏名称"
因为脚本生效了 脚本监听了玩家登陆事件并在玩家触发这一事件后执行了发送信息行动 所以你看到了 "Hello 你的游戏名称"
需要注意的是在 Skript 中 如果一行代码以 ":" 结尾 那么下一行需要进行缩进操作 如果没有就不需要进行缩进
例如:
很多刚入门 Skript 的玩家常常会看到 [Server thread/ERROR]: indentation error: 实际上就是缩进导致的出错
————————————————————————————————
在正式进入 Skript 教程之前 我们还需要了解编程的几大元素 变量 运算 控制
● 变量
在Skript中 一共有两大类变量形式 全局变量和局部变量
全局变量以 {...} 存在 括号内 "..." 可以替换成除 "_" 以外的字符
局部变量以 {_...} 存在 括号内 "..." 可以替换成任意字符
这些是学术名词 但具体是什么意思 我们需要具体分析
首先我们先要介绍 variables.csv 这一个 Skript 插件启动时默认生成的文件
可能很多新人在刚开始使用 Skript的时候都不甚了解 有时候还会觉得它太大 占空间
但是我要说的是 这个文件非常重要 它保存了你所有创造的全局变量 你在任何一处都可以调用这些变量
对其进行加减乘除... 不受到关服/开服的影响 只要不删掉 它们就会被永久存在 variables.csv 文件内
而局部变量天生命短 它只能留在你写的一部分代码内 出来了你就用不了了 它们不会被文件保存 随着开服/关服消失
可能我说了这么多 很多人还是不明白 下面我就用两段代码进行对比 具体阐述其存在的意义
在这段代码中 我们分别创建了 {_x} 局部变量(赋值1) 以及 {x} 全局变量(赋值2)
并在脚本加载的时候 将两个变量结果输出至后台 我们可以看到
与之作为对比
在这段代码中 我们仅在加载过程中 创建并赋值了两个变量 并没有直接输出
而是把数据输出的任务交给了 "/test" 命令 输入指令后 我们可以看到
(<none> 代表着这个变量没有被赋值)
但是我已经在赋值了第一段代码也正常输出了结果 为什么在第二段代码中 局部变量变成了 <none> 呢?
因为局部变量只留存在 "on load" 所在的代码内 并没有走出来 在被 "on load" 使用完后
{_x}局部变量便失去了它的赋值 在 "command" 中 {_x} 作为局部变量没有继承之前的数值 所以我们在测试时获得了 <none>
在编写代码的过程中全局变量和局部变量的选择将会大大影响插件实际工作效率 请谨慎选择
————————————————————————————————
进阶变量(数组):
如果我们需要将一系列数据记录在一个变量内 这时就需要我们的数组参与进来
基本格式为 {_x::*} {x::*} {_x::*::*...} {x::*::*...} (仍受全局/局部规则的约束)
如果把只拥有一个值的变量看成一杯饮料 那么数组就是一个饮料柜 里面可以放任意数量的饮料
你可以用任意顺序把他们排列起来 等到需要喝饮料的时候 再把这个柜子搬出来 把需要的拿出来即可
这样可以使代码更为整洁 更有利于较为复杂插件的开发
(本内容与 Skript "loop" 相关知识密切相关 本段落仅为解释 实际使用方法请查看 "loop" 章节)
● 运算
Skript 有三种常见的运算类型
add/remove/set/delete 分别代表 加/减/赋值/删除
相较于其他编程语言 Skript 的运算类型使用更加广泛
今后将会频繁的用到它们
● 控制
代码的核心内容 对于 Skript 而言共有 "5大类" 需要进行学习
每一类都有对应其特有的使用方式 以及 结构组成
————————————————————————————————
初识 "5大类"
Skript 脚本核心 即为 "5大类"
分别为 "Events"(事件) "Conditions"(条件) "Effects"(效果) "Expressions"(表达) "Types"(类型)
为了让大家能更好的认知这几大内容 我这里举一个例子 如果我想制作一个如下功能的脚本
我们来分析这个句子
首先是第一句 玩家加入服务器 名词 + 动词 +名词 的格式
典型的第1类(Events) 服务器会监控每一位在线玩家的一举一动 每一个方块的一举一动
我们统称为事件 我们想要知道这些事件 就需要监听器 监听玩家的行动
通过查阅 Doc我们知道了加入的监听是 “Join” (有关Doc的内容将在 “五大类”用法 进行详细说明)
有了监听器 我们就可以监控玩家/方块的各种行为 (事件) 并在这些玩家触发监听器时 对这些事件进行调控
————————————————————————————————
send "..." to player #将引号内的信息发送至玩家[/code]
第二句 是否只有一个玩家 名称 + 是否/有无 的格式 典型的判断句
对应第2类(Conditions) 怎么让服务器知道是不是只有一个玩家在线 判断语句就起到了非常重要的作用
获取数据并进行对比 然后得到结果 这就是判断语句需要做的事情
判断句的一般以 “if” 开头 + “判断条件” 构成 这里的 玩家数量 属于第4类 Expressions(表达)
我们可以看到 这一条判断共有2个分支
1.玩家数量为1 > 发送一条信息
2.玩家数量不为1 > 踢出所有玩家
为1的我们已经知道了 “if player count is 1” 那么不为1的又该如何判断?
这时候 “else” 就充分发挥了作用 “else” 中文翻译为 “其他的,不同的” 顾名思义
如果我们的主条件是玩家数量为1 那么对立面(玩家数量不为1)就是 ”else” 的范畴
————————————————————————————————
第三句第四句判断句结尾 紧跟着需要执行一个行动
第三句需要执行 发送一条信息至该玩家
第四句需要执行 踢出该玩家
动词 + 名称 典型的动词句 对应Effects(效果) 类
通过查阅 Doc 我们了解到
发送消息的语法是 “send %string% to %commandsenders%”
踢出玩家的语法是 “kick %players%”
看到 “%” 的你们可能和我一样迷茫 这些究竟是什么呢?
这时候就需要第5类Types(类别) 再次查阅 Doc 在 Types(类别) 下
我们找到了command sender 我们得知 %commmandsenders%
该(Types)类别下一共有3个元素可供选择
一个是 “console(后台)” 一个是 “player(玩家)” 一个是 “players(所有玩家)”
我们需要什么呢? “发送一条信息至该玩家” 是的 我们需要的是玩家 即 “player”
同样的 “踢出所有玩家” 我们需要的是所有玩家 即 “players”
综合监听和条件我们就有了一段基础代码 实现了我们设计所需要的效果
怎么样这样分析下来 是不是感觉 Skript 也没有想象那么难不是么
————————————————————————————————
"5大类"用法
在开讲之前 我需要先和大家分享几个查阅脚本语法的网站
● 推荐
https://skripthub.net/docs/ 部分老版本附属语法查不到 但查询方便 有效
官方Doc https://skriptlang.github.io/Skript原版Skript Doc方便查询新手学习
● 不推荐
中文Wiki 很久没有更新 很多东西都查不到 适合学习不适合查语法
Skunity https://docs.skunity.com/syntax/ 内容非常多 但很多已经无效 没有更新
这些网站将频繁出现在我们的教程之中 请务必收藏好
————————————————————————————————
● Events(事件)
什么是事件?
什么人在什么地方做了什么事情 最简单的三要素构成了事件
那么 "谁" 能知道 什么人在什么地方做了什么事情? 这时候监听器就理所应当成为了那个 "谁"
所谓的 Events 并不是单纯指一个事件 而是指监听器监监听到了一个事件
所有原版的 Skript 监听器都可以在下面这个网址找到
https://skriptlang.github.io/Skript/events.html
可以看到 Skript 提供给我们一共 120 个监听可供使用这么多的监听 我们究竟该怎么选择呢 选择完又该如何使用呢?
这里我们拿 "on command" 监听器为例(命令执行监听器)
我们把我们的定义拿过来 什么人在什么地方做了什么事情
现在我们已知什么事情指的是执行命令 现在我们需要知道什么?
我们需要知道的是 什么人? 什么地方?
那么我们如何知道这些元素呢? 这时候我们需要下面这个网站
https://skripthub.net/docs/

在侧边栏中选中(Skript Events)后 在搜索栏中输入 "on command"

我们可以看到对应 "on command" 的一张信息卡片
我们要重点关注的是 "Event Values" 这一标签下所对应的内容
可以看到 "on command" 卡片上 "Event Values" 标签下共有三个字段
分别为 "event-world" "event-commandsender" 以及 "event-player"
翻译对应为 "事件-世界" "事件-指令发送者" "事件-玩家"
利用这些 我们便可以获取到 事件中的 什么人 什么地方 这些具体的信息
我们看一个 "on command" 相关示例
我调取了指令监听器 任何执行者执行 "/op" 指令都会触发此监听
监听被触发后 将自动将三个元素 "event-world" "event-commandsender" "event-player" 输出到后台
如果是一位玩家执行了 "/op" 命令 "event-world" 将会输出玩家在哪个世界执行了命令
"event-commandsender" 将会输出为 "player" 因为监听器由玩家触发 而 "event-player" 则会输出为 "玩家的名字"
那么如果是后台执行了 "/op" 命令呢? 因为后台并不存于任何一个世界 也没有名字
上述三个元素只会有 "event-commandsender" 存在并正常输出为 "console" 而其余不存在元素将全部输出为 "<none>"
相同的你可以利用这样的方法 输出任何一个监听器下 "Event Values" 的元素值
这种获取元素值的方法将在你需要使用任何从来没有接触过得监听器的时候 快速让你掌握监听器的基本信息
既然我们已经监听到了这个事件 除了获取一些与事件相关的信息之外 我们还能做一些什么呢?
取消事件!
事件发生的顺序是 事件准备发生 > 监听器监听到 > 事件正式发生
如果我们在监听器监听到后 加入 取消事件 这一环节
事件发生的顺序就变为了 事件准备发生 > 监听器监听到 > 取消事件 > 事件未发生
我们就成功阻止了 "/op" 指令的发生 那在实际脚本中我们怎么实现这一流程呢?
cancel event 譬如我们上面所说的这段指令 我们在结尾加入 cancel event
通过测试我们发现 元素仍然能正常输出 但是 "/op" 指令不再生效
即使取消了事件 事件的元素仍然会被传递至插件内 这个是需要注意的
————————————————————————————————
● Conditions(条件)
条件用于判断句 有没有 是不是 都是条件的一种 基本格式为 "if" + 条件
这里我们和学习 Events 一样 我们先通过官方 Doc 找到所有的条件
这里我们拿最常用的一个条件作示例 判断玩家是否有权限
权限的英文是什么? "permission" 我们通过翻阅侧边栏可以得知
与 "permission" 相关的只有 "Has Permission" 一条 官方对这个条件的解释为
"Test whether a player has a certain permission." 翻译过来就是 "检测一个玩家是否拥有某一权限"
即我们所需要的 判断玩家是否有权限 那么我们又该如何使用呢 "Has Permssion" 条件呢?
在 "Has Permission" 下 "Patterns" 给了我们两种标准格式用法
针对这样的格式 我相信很多人可能一头雾水 了解如下几点 或许能帮助你更好的了解用法
player has permission "player.op"
player have the permissions "player.op"
player have permissions "player.op"
...
正如之前所说的 可以省略的地方 无论如何搭配 表达的意思都是一样的
这也是 Skript 一大特点 并不需要非常严谨的语法 只要意思对 语法可以根据个人喜好进行选择
同样我们还是举一个以 "on command" 监听器为核心的例子加深一下大家的理解
那么在玩家触发此监听器后 系统将会判断玩家是否有 "player.op" 权限
如果有 指令将会正常进行 并发送 "true" 给玩家
如果没有 指令执行事件将被强制取消 并发送 "false" 给玩家
————————————————————————————————
● Effects(效果)
与其说它是效果 不如称作行动
我们把条件类的示例拿下来接着分析
我们把两个条件判断句 分别用条件1和条件2替换 那么这段代码就可以写成
我们可以看到 条件判断结束后 代码并没有直接结束 而是分别执行行动
如果我希望执行其他行动呢? 比如我想给一个玩家发送一个 Title 消息
通过在官方 Doc 搜索 关于 Title 相关的 Effects 一共有两个
https://skriptlang.github.io/Skript/effects.html#EffResetTitle
https://skriptlang.github.io/Skript/effects.html#EffSendTitle
通过查看官方的注释
#EffResetTitle
"Resets the title of the player to the default values."
"重置玩家的 Title 至默认值"
#EffSendTitle
"Sends a title/subtitle to the given player(s) with optional fadein/stay/fadeout times."
"发送 Title/Subtitle 至指定玩家 可自定义渐入和淡出的时间"
一目了然 我们需要的是 #EffSendTitle 关于怎么用
本质上和学习Conditions(条件)一样 我们将注意点放在 "Patterns" 上
按照我们提到的规则
我们大致上认识到行动的基本用法 即
将其带入进我们的主代码
三种方式我们都成功的发送了 Title 信息 只要记住我们的规则
所有的用法问题迎刃而解 学会读 "Patterns" 是初学者必备的技能
下文我将不会再提及如何使用 "Patterns" 查阅用法
————————————————————————————————
● Expressions(表达) & Types(类型)
假设你想要调整玩家的最大血量
调整血量 之前我们提过 动词 + 名词形式 这是属于 Effects(效果) 类
但是实际上是这样么?
实际上并不是 所谓的 Effect(效果) 虽然都是 动词 + 名词形式 但是对于 Effect(效果) 而言
注重的是 动词 而非后面跟着的 名词 例如 在玩家所在位置生成僵尸
Effect(效果) 所能提供的 只有 "生成"
通过查阅 Doc https://skriptlang.github.io/Skript/effects.html#EffSpawn 我们知道生成的用法基本为
但是除去这个词语 我们还剩 "在玩家所在位置" 和 "僵尸"
这两个词我们又该怎么处理呢? 这时候我们就需要用到 Expressions(表达)
位置的英文单词是 "Location" 我们在官方 Doc 查到了 3 种有关 "Location" 的表达
https://skriptlang.github.io/Skript/expressions.html#ExprLocation
https://skriptlang.github.io/Skript/expressions.html#ExprLocationOf
https://skriptlang.github.io/Skript/expressions.html#ExprLocationAt
我们需要什么呢? 我们需要 "玩家所在的位置" 相同地 通过查看官方的注释
我们知道我们需要的是 #ExprLocationOf (The location of a block or entity.)
但是这只解决了我们 "所在位置" 的问题 并没有解决 "玩家" 和 "僵尸" 的问题
我们可以看到 "所在位置" Expressions(表达) 是没有主语的 但是对于 Skript 而言
我们需要把这些零碎的东西组成句子 必不可少的是主语 次要的是宾语
这时候我们就需要引入 Types(类型) 但凡你发现你的表达里缺少主语/宾语(对象) 来这里准没错
https://skriptlang.github.io/Skript/classes.html
通过翻译我们可以轻松知道 玩家的英文 以及 僵尸的英文 分别为 "player" "zombie"
与之相对应的 我们分别在 Types(类别) 中找到
https://skriptlang.github.io/Skript/classes.html#player
https://skriptlang.github.io/Skript/classes.html#entity
综合上面我们所获得的信息 我们获得了完整一行代码
————————————————————————————————
Wow 恭喜你 看到这 你就可以开始尝试着写一些插件了
这里刚好有一个例子 不妨动动手 试一试
在玩家破坏方块时 检查玩家是否有 "fundamental.break" 这个权限
如果有那就在让后台发送一条命令 "/broadcast %player% 破坏了方块"
如果没有那就取消这个事件 并 向这个玩家发送 "你不能破坏这个方块"
答案不唯一 仅供参考
当然仅仅学这些并不够 为了做到能更快更灵活的使用各类语法
在闲暇的时候 把官方 Doc 提供的所有语法的注释都认真的看一遍是快速上手 Skript 的一种好办法
————————————————————————————————
"loop"用法
你开始尝试着写一些有一些小功能的脚本了 但是难免的你会出现这样的问题
你为玩家创建了很多变量 很多都是属于一个类型的
比如你把所有人的游戏币数量都存在了 {(玩家的名称)的游戏币数} 这些变量内
你把所有人的点券数量都存在了 {(玩家的名称)的点券数} 这些变量内
平时你单独去操作这些变量的时候 觉得也很容易
但是假设突然有一次 由于回档需要补偿玩家损失
你需要将所有玩家的 {(玩家的名称)的游戏币数} 这个变量都 +1000
还好只有10个玩家数据 你可以一个一个调 就是浪费一点时间 倒还不成问题
但是如果你有10000个玩家数据 如果你一个一个调 可能玩家都走完了 你也调不完
你遇到了新的问题 如何存储并快速操作一类变量?
这时候你需要两样东西 "数组" "loop"
● 数组
数组的基本格式为 {变量名::变量名::变量名......}
我们带入实景 将用 {(玩家的名称)的游戏币数} 存储转为用 {金币::(玩家的名称)} 存储玩家的游戏币数量
我现在服务器有 10000 个玩家
玩家名称为 1,2,3,......,10000
玩家游戏币数量为 100,200,300,......,1000000
那么对应的 {金币::1} 就是 名为 "1" 玩家的游戏币数量 100
如果我用输出语句输出 {金币::1} 至后台 那么很显然我会得到 100 这个数值
同样的我用输出语句输出 {金币::10000} 至后台 我会得到 1000000 这个数值
你可能会说 这不是和用 {(玩家的名称)的游戏币数} 一样么?
是的 确实 如果仅仅需要获得某一个玩家的游戏币数量 两者并没有什么区别
但是 数组好用之处在于 如果我将 {金币::(玩家的名称)} 中玩家的名称改为 "*" 即 {金币::*}
这时候会产生什么样的效果呢?
{金币::*} 将包含 所有 {金币::(玩家的名称)} 变量
而这个 却是 {(玩家的名称)的游戏币数量} 怎么改也做不到的
● Loop
利用数组我们知道了如何快速获取一类数据
但是我们又该如何快速操作这一类数据呢? 这时候就需要引入我们的 Loop 结构
Loop 即 循环结构 是 Skript 里非常常用的结构语句 主要用于操作数据量较大的一类变量
Loop 共有几大标准配合
Loop + 数组 / Loop + 次数 / Loop + Types(类型)
● Loop + 数组 结构
我们继续拿上面的数组进行举例
我们需要把每个玩家金币变量值 +1000 的事情还没有结束
为了更好的讲解 我们先把代码放上来
第一行不需要多说 在使用的时候注意 ":" 的下一行缩进问题即可
第二行通过 Effects(效果) 将 "loop-value" 赋值给 {_x}
第三行通过 Effects(效果) 给 {_x} 变量加了 1000
第四行通过 Effects(效果) 将 {金币::%loop-index%} 的值 设置为 {_x}(原数值+1000后的数值)
那么什么是 "loop-value" "loop-index" ?
首先我们需要先了解 Loop 的基本原理
上一环节我们通过认识 数组 知道了
也就是意味着它包含了 {金币::1},{金币::2},{金币::3},{金币::4},{金币::5}......,{金币::10000}
而 Loop +数组结构 作为一个系统 会从 排在最前面的变量开始{金币::1}
一直 到最后 {金币::10000}(按照存进去的顺序) 每循环一次就会从数组里取一个变量出来
我们可以把数组的这些变量看成一列学生队伍 Loop 作为一位老师 他的任务是核查每一位学生的身份信息
循环语句通过这样的方式 检查了数组内 所有的元素
解决了我们在编写代码的过程中 操作同类变量上的不方便 这是在开发大型插件必备的能力 请认真对待
● Loop + 次数 结构
Essential 插件 想必大家都有用过 不知道有没有人注意过
在玩家执行 "/tpaccept" 以接受传送的时候 系统会有一个提示
"传送中 3 秒内请勿移动 否则移动将会被取消" 在没有学过 Loop + 次数 之前
你写的代码可能是这样的
我们发现 在 "wait 3 seconds" 期间 其实系统并没有检查玩家的位置在哪
玩家可以在这 3 秒内想干什么就干什么 而我们之所以不想让玩家动 是因为想通过这个方法减少服务器延时
但我们并没有达到我们的目标 这时候我们就需要引入 Loop + 次数 结构
一样我们还是直接上代码 通过示例解释这样一类结构的运行方式
第一行 通过 Loop + 次数 的结构 我们将
这段代码 循环了 3 次
我们从 3 秒检查 1 次改为了 1 秒检查一次 并且 在玩家移动位置后 直接强行终止了代码进程
也就是说 如果 1 秒前玩家的位置 以及 1 秒后玩家的位置不同 代码将会立即终止
部分将不再运行
● Loop + Types(类型) 结构
相信大家都用过一些聊天控制类型的插件 DeluxeChat MiaoChat ChatControl...
很多聊天插件都有 聊天频道 这样的功能 即 玩家所发送的信息 仅会被一部分玩家接收到
那这样的功能又如何在 Skript 实现呢? 我们还是直接上代码
Loop + Type(类型) 结构与 Loop + 数组 结构类似
Types(类型) 实际上只是 Skript 内置的一些比较常用的数组 这些数组并不需要你手动进行添加或者分类
它们将会在你调用的时候 临时获取 并构成数组 比如这段代码中 "all players" 是所有在线玩家的数组
构成它的是所有 在线玩家(loop-player) 我们通过判断 在线玩家(loop-player) 是否有 "自定义频道" 权限
来实现筛选功能 拥有 "自定义频道" 的玩家将收到该玩家的信息 而没有该权限的玩家将无法收到该玩家信息
当然实现这一功能还有不同的写法 这里只是提供一种思路
————————————————————————————————
● 注册指令
说到现在 我们所有的代码 似乎都是基于监听器进行编写的
我们都需要去触发监听器 才能执行我们的代码 那有没有什么办法可以主动触发我们的代码?
这时候我们就需要引入 MineCraft 插件最核心的功能 指令功能而在 Java 里你可能需要这样注册一个指令但是在 Skript 里你只需这样即可
你并不需要理解其他是什么意思 仅仅需要记住这个格式即可
如果我想注册一个 "/我学你马Java" 的命令 你只需这样
通过测试 指令正常触发
通过套公式 你可以创造成千上万的指令不成问题 但是实际上我们在使用一个插件的时候
并不是只有 "/..." 结构的指令存在 更多的是 "/... ... ..." 来构成一类指令
那我们又该如何注册这样结构的指令呢? 非常简单 基本格式与上面几乎无异
本人写代码时常用的结构就是这样
有人问 "类型" 有哪些 其实我也说不全 我常用的有这几种
"text" - 字符类型 什么是字符? 就是字面意思 字词符号
"player" - 在线玩家
"offline player" - 离线玩家
"number" - 数字类型
"integer" - 整数类型
那这些类型又有什么用处呢? 它实际上是限制了你可以输入的参数
比如我创建如下指令可以看到 第一个空格的位置 我需要的参数类型为在线玩家
那么我在执行这个指令的时候必须在这个位置上填上一个在线玩家的名称
同样的如果我把 "[<player>]" 换成 "[<integer>]" 我就需要在这个位置上填写一个整数
如果我填了 "1.2"(小数/浮点数) Skript 就会提示我 填写的参数类型错误
为什么填写参数 那肯定是在代码段内需要使用这些输进来的参数
那么我们在代码段里有该如何调用这些被我们输入进来的参数呢?
比如像是上面这个指令 它只有一个可以填参数的位置 那么在代码段内 它就是 arg-1 即 第一个参数的意思
我们只需要记住核心规则 它排在第几位 在代码段内 它就是 "arg-几"
当然我在这块的了解并不是很深入 为了不把大家带上歪路 这里引用国外 Skript 作者更为详细的指令注册的教程
————————————————————————————————
命令名称(必填)
命令名称基本上是命令 您可以在命令名称中使用任何字符(空格字符除外)
当然如果在命令名称中使用空格字符 那么空格字符后的文本将成为参数
命令名称前的斜杠字符(/)是可选的(但这并不意味着您可以在执行命令时不带斜杠)
参数(可选)
可以通过将参数放在 "[]" 中来使其成为可选参数
类型参数
可以通过使用规定的格式来限制参数的类型 例如: <type = default value>
- 类型为 "text/string" 的参数可以接受任何字符 但 "object" 类型不能用作于参数
- 类型可以是多个 (例如 number -> numbers entity -> entities) 通过这样的方法 可以使参数接受多个值
- "= default value" 这一部分是可选的 如果命令执行者未输入参数 系统将自动使用默认值
- 同样你也可以使用这样的方式设置参数默认值 例如: <item = %player's tool%>
命令示例:
使用 /kill zombies /kill creepers and animals in radius 100 或 /kill monsters in the radius 6 都是可以的
但是如果没有输入数值 系统将自动使用默认值 半径 20
Aliases
子命令 命令的别名 如果需要创建多个子命令 请使用用逗号分隔
示例:(/alias1,alias2,/alias3)
Executable By
指定可以使用该命令的执行者
例如:console(后台) players(玩家) the console and players(后台和玩家)
Usage
使用不正确时 将发送的消息
Description
命令描述 其他插件可以获取/显示此信息
Permission
执行命令所需要的权限
Permission Message
执行者没有权限 提示信息
Cooldown
多长冷却时间后可以再次使用该命令 需要注意的是 关服时所有命令冷却时间将被重置
Cooldown Message
冷却期间 提示信息
Cooldown Bypass
无视冷却时间所需要的权限
Cooldown Storage
存储冷却时间全局变量名称
————————————————————————————————
● Function 第一类结构
众所周知 一个复杂的插件 不免出现相似的代码段
有时候你看着不舒服 想要缩减段落 却又无从下手 这时你就需要 Function 来帮忙了
这里取本人的SUPERGUILDS的一段代码做讲解可能不知道这段代码的意思 我现在告诉你
这段代码的功能是存储数据至yaml文件内 对于一个非常复杂的脚本 数据的存储是必不可少的
同样也会频繁出现在我们的代码中的 难道真的每一次需要存储数据的时候 都需要再去复制粘贴么?
对于一个初学者来说 无可厚非 就是多几行而已 但是对于一个老手来说 复制粘贴不可取
首先对于一个 5000+ 以上的插件来说 你需要关注的东西很多 一个是代码优化 一个是代码可读性
就拿 SUPERGUILDS 来说 7000+ 行 我所有的数据读取和存储都是通过方法完成 单一个数据写入方法
我就使用了 100+ 次(也就是上面我提到的这段代码) 如果我们把它都像上面一样全部展开 我的脚本将立即增加 600+ 行
但是我们在写脚本的时候真正需要的是这些么? 不 我们需要的是效果 是功能 不是数据处理的流程
每次写入数据都需要白白多占 6 行 既不方便后期维护 同时因为要兼顾路径正确与否 浪费很多时间在查错上面 不划算
Function 为你解决了这些难题 方法的注册和指令的注册有共同的地方 比如它们同样需要参数 结构一般为
(关于参数名以及参数类型的定义 可以在 "Command" 栏目下找到 此处不再赘述)
套用公式 我们可以把上面的代码段转换成方法段若想将 "plugins/SUPERGUILDS/playerdata/玩家UUID.yml" 的 "Datas.Username" 设置为 "**EVER"
代码即可转换为使用方法的时候请勿画蛇添足在前面另加 "function"
通过这样的方法我们大大减少了代码量 提高了开发效率你以为 Function 就结束了?其实不然 作为方法 很多的时候它并没有 "操作" 功能
更多的是 "整理/查找/判断" 功能 那遇到这样的情况我们又该如何灵活使用 Function 以供我们使用呢?
上面我提到了使用 Function 写入了数据 那么有写入肯定需要读取 我们又该如何利用 Function 来缩短我们读取的代码呢?
————————————————————————————————
● Function 的第二类结构
与我们之前提到的写入不同 此时在方法的第一行的末尾我们新增了 "输出参数类型"
什么叫输出? 就是代码执行完 得到的一个数值 让这个数值回到主代码段 即 返回一个值
什么叫参数类型 这里举一个例子 我们知道 1+1=2 因为 1 是一个数字 2 也是一个数字 所以才有 数字+数字=数字
那如果我这样写 ①+一 那么又等于什么呢? "①" 和 "一" 在你的思维中 很可能会认为它们仍然是数字 你会觉得答案还是 2
但是执行我们代码的是计算机 并不是 我们 计算机所能识别的数字 只有 阿拉伯数字 "1,2,3..."
而 "①,一..." 计算机只能识别它们一些字符 并不能利用这些字符进行加减运算 就像是现在问你 水 + 苹果 = ?
你或许也没有一个准确的答案. 计算机更是这样
所以这时候就需要参数类型来规范我们运算中的这些值 拿一段来自 SUPERISLANDS 的代码做示例
通过第一类 Function 的学习我们知道这个方法可以放进去两个参数
最终会返回一个参数类型为 boolean("布尔")的值 即 "false/true"
那么实际操作中我们就可以通过这样一段代码对给定的数值进行判断 是否满足条件
如果满足那么返回 "true" 如果不满足 那么就将返回 "false"
需要知道的是 在 Skript 编程里 如果代码已经成功获得的最终值 那么方法代码将立即终止
从返回成功的一行开始 后面所有代码将不再执行 切记
到此 所有基础教程已结束 谢谢大家赏脸看完 全文 11111 字 都是自己的一些干货 点个收藏 给点人气便是对我最大的支持
-2020.2.28
● 更新日志3.30 event-commandsender is "player" 修正为 event-commandsender is player
备注: 同类型变量进行比较 是编写代码的规范 感谢 zide 指出问题"而没有该权限的玩家将无法收到该玩家信息"" 中 "受到" 更变至 "收到" 感谢热心站内用户反馈
萝卜青菜各有所爱 Skript 和 Java 哪个适合你 哪个就是好编程软件 切勿因此引战

前言:
- #JAVA:
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent evt) {
- Player player = evt.getPlayer(); // 玩家加入
- ItemStack itemstack = new ItemStack(Material.DIAMOND, 64); // 定义钻石
- if (player.hasPermission("")) {
- inventory.addItem(itemstack); // 给予钻石
- player.sendMessage("欢迎你加入服务器!你获得了64枚钻石!");
- }
- }
- #Skript:
- on join:
- if player has permission "":
- message "欢迎你加入服务器!你获得了64枚钻石!"
- give 64 diamond to player
因为其语法简单而受到很多中小型服主的青睐 很多人多多少少对这块有一些了解
但是毕竟受众人群小 很多时候也会出现想学却无从下手 有问题却无处可问的尴尬境地
站内现有的Skript的教程 https://www.mcbbs.net/thread-492211-1-1.html 最后一次编辑为2016年
虽然讲的东西很多 但是基本上是基于原版教程的少量扩充 对于新手理解起来还是过于困难
作为
Fundamental (https://www.mcbbs.net/thread-822402-1-1.html)
SUPERGUILDS (https://www.mcbbs.net/thread-870934-1-1.html)
的作者 我自认为自己对 Skript 还是多少有些理解
我希望通过这篇教程谈谈我在编写 Skript 脚本过程中的一些经验之谈
基础教程将分为
初识代码
了解"5大类"
"5大类"用法
"loop"用法
注册指令
"function"用法
板块进行教学
————————————————————————————————
初识代码:
Skript 不同于其他语言 正如我在开头所展示的 相比较 Java 而言 Skript 更注重的是语言而非编程
语言是什么 就是 人说的话 也就是说 编写 Skript 插件 = 把你想的说出来 而这种语言是英语
也就是说只要你知道英语的基本语法 学习 Skript 便不成问题
不过对于初学编程的玩家 我还是需要明确几点基本原则
1.理论是基础 也是必要的 实践得到的经验是学好的唯一途径 只学理论永远写不好插件
2.读懂其他脚本编写者的脚本比自己复制粘贴拼凑插件对你的学习更有意义
————————————————————————————————
安装Skript
Skript是一款插件 也就意味着你需要像安装其他插件一样 需要放在 plugins 目录下
很多人认为越新版本的插件对服务器越好 但实际上对于 Skript 不是这样的
作为用户我大致上体验了 2.1.2 2.2dev36 2.2dev37c 2.3.5 这几个版本
不同版本的服务器个人推荐使用的 Skript 插件版本如下
1.7.10 2.1.2 https://dev.bukkit.org/projects/skript/files/779542/download
1.8.8-1.12.2 2.2dev37c https://github.com/SkriptLang/Skript/releases/download/dev37c/Skript.jar
1.13.2+ 最新版 https://github.com/SkriptLang/Skript/releases
————————————————————————————————
如何编写脚本?
将如上文件安装完成后 plugins 目录下将多出 一个 Skript 文件夹 文件夹里面也多出了许许多多文件
现在你不需要理会他们
使用文本编辑器 Sublime/Visual Studio Code/TXT 在 plugins/Skripts/scripts 目录下创建一个名为 "1.sk" 的文件
将如下代码复制进 "1.sk" 文件内 并保存
- on join:
- send "Hello %player%" to event-player
因为脚本生效了 脚本监听了玩家登陆事件并在玩家触发这一事件后执行了发送信息行动 所以你看到了 "Hello 你的游戏名称"
需要注意的是在 Skript 中 如果一行代码以 ":" 结尾 那么下一行需要进行缩进操作 如果没有就不需要进行缩进
例如:
————————————————————————————————
在正式进入 Skript 教程之前 我们还需要了解编程的几大元素 变量 运算 控制
● 变量
● 运算
● 控制
————————————————————————————————
初识 "5大类"
Skript 脚本核心 即为 "5大类"
分别为 "Events"(事件) "Conditions"(条件) "Effects"(效果) "Expressions"(表达) "Types"(类型)
为了让大家能更好的认知这几大内容 我这里举一个例子 如果我想制作一个如下功能的脚本
- 有一位玩家进入服务器
- 判断是否只有一个玩家
- 如果只有一个玩家 那么发送一条信息
- 如果不止一个玩家 那么踢出所有玩家
首先是第一句 玩家加入服务器 名词 + 动词 +名词 的格式
典型的第1类(Events) 服务器会监控每一位在线玩家的一举一动 每一个方块的一举一动
我们统称为事件 我们想要知道这些事件 就需要监听器 监听玩家的行动
- Java 监听器
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent e){
- ……
- }
- Skript 监听器
- On join:
- ……
有了监听器 我们就可以监控玩家/方块的各种行为 (事件) 并在这些玩家触发监听器时 对这些事件进行调控
————————————————————————————————
send "..." to player #将引号内的信息发送至玩家[/code]
第二句 是否只有一个玩家 名称 + 是否/有无 的格式 典型的判断句
对应第2类(Conditions) 怎么让服务器知道是不是只有一个玩家在线 判断语句就起到了非常重要的作用
获取数据并进行对比 然后得到结果 这就是判断语句需要做的事情
判断句的一般以 “if” 开头 + “判断条件” 构成 这里的 玩家数量 属于第4类 Expressions(表达)
- 判断 玩家数量 是否为 1
- If player count is 1
1.玩家数量为1 > 发送一条信息
2.玩家数量不为1 > 踢出所有玩家
为1的我们已经知道了 “if player count is 1” 那么不为1的又该如何判断?
这时候 “else” 就充分发挥了作用 “else” 中文翻译为 “其他的,不同的” 顾名思义
如果我们的主条件是玩家数量为1 那么对立面(玩家数量不为1)就是 ”else” 的范畴
- if player count is 1(玩家数量为1):
- 发送一条信息
- else(玩家数量不为1):
- 踢出该玩家
第三句第四句判断句结尾 紧跟着需要执行一个行动
第三句需要执行 发送一条信息至该玩家
第四句需要执行 踢出该玩家
动词 + 名称 典型的动词句 对应Effects(效果) 类
通过查阅 Doc 我们了解到
发送消息的语法是 “send %string% to %commandsenders%”
踢出玩家的语法是 “kick %players%”
看到 “%” 的你们可能和我一样迷茫 这些究竟是什么呢?
这时候就需要第5类Types(类别) 再次查阅 Doc 在 Types(类别) 下
我们找到了command sender 我们得知 %commmandsenders%
该(Types)类别下一共有3个元素可供选择
一个是 “console(后台)” 一个是 “player(玩家)” 一个是 “players(所有玩家)”
我们需要什么呢? “发送一条信息至该玩家” 是的 我们需要的是玩家 即 “player”
同样的 “踢出所有玩家” 我们需要的是所有玩家 即 “players”
综合监听和条件我们就有了一段基础代码 实现了我们设计所需要的效果
- on join:
- if player count is 1:
- send “…” to player
- else:
- kick players
————————————————————————————————
"5大类"用法
在开讲之前 我需要先和大家分享几个查阅脚本语法的网站
● 推荐
https://skripthub.net/docs/ 部分老版本附属语法查不到 但查询方便 有效
官方Doc https://skriptlang.github.io/Skript原版Skript Doc方便查询新手学习
● 不推荐
中文Wiki 很久没有更新 很多东西都查不到 适合学习不适合查语法
Skunity https://docs.skunity.com/syntax/ 内容非常多 但很多已经无效 没有更新
这些网站将频繁出现在我们的教程之中 请务必收藏好
————————————————————————————————
● Events(事件)
什么是事件?
什么人在什么地方做了什么事情 最简单的三要素构成了事件
那么 "谁" 能知道 什么人在什么地方做了什么事情? 这时候监听器就理所应当成为了那个 "谁"
所谓的 Events 并不是单纯指一个事件 而是指监听器监监听到了一个事件
所有原版的 Skript 监听器都可以在下面这个网址找到
https://skriptlang.github.io/Skript/events.html
可以看到 Skript 提供给我们一共 120 个监听可供使用这么多的监听 我们究竟该怎么选择呢 选择完又该如何使用呢?
这里我们拿 "on command" 监听器为例(命令执行监听器)
我们把我们的定义拿过来 什么人在什么地方做了什么事情
现在我们已知什么事情指的是执行命令 现在我们需要知道什么?
我们需要知道的是 什么人? 什么地方?
那么我们如何知道这些元素呢? 这时候我们需要下面这个网站
https://skripthub.net/docs/
在侧边栏中选中(Skript Events)后 在搜索栏中输入 "on command"
我们要重点关注的是 "Event Values" 这一标签下所对应的内容
可以看到 "on command" 卡片上 "Event Values" 标签下共有三个字段
分别为 "event-world" "event-commandsender" 以及 "event-player"
翻译对应为 "事件-世界" "事件-指令发送者" "事件-玩家"
利用这些 我们便可以获取到 事件中的 什么人 什么地方 这些具体的信息
我们看一个 "on command" 相关示例
- on command "/op":
- send "%event-world%" to console
- send "%event-commandsender%" to console
- send "%event-player%" to console
监听被触发后 将自动将三个元素 "event-world" "event-commandsender" "event-player" 输出到后台
如果是一位玩家执行了 "/op" 命令 "event-world" 将会输出玩家在哪个世界执行了命令
"event-commandsender" 将会输出为 "player" 因为监听器由玩家触发 而 "event-player" 则会输出为 "玩家的名字"
那么如果是后台执行了 "/op" 命令呢? 因为后台并不存于任何一个世界 也没有名字
上述三个元素只会有 "event-commandsender" 存在并正常输出为 "console" 而其余不存在元素将全部输出为 "<none>"
相同的你可以利用这样的方法 输出任何一个监听器下 "Event Values" 的元素值
这种获取元素值的方法将在你需要使用任何从来没有接触过得监听器的时候 快速让你掌握监听器的基本信息
既然我们已经监听到了这个事件 除了获取一些与事件相关的信息之外 我们还能做一些什么呢?
取消事件!
事件发生的顺序是 事件准备发生 > 监听器监听到 > 事件正式发生
如果我们在监听器监听到后 加入 取消事件 这一环节
事件发生的顺序就变为了 事件准备发生 > 监听器监听到 > 取消事件 > 事件未发生
我们就成功阻止了 "/op" 指令的发生 那在实际脚本中我们怎么实现这一流程呢?
cancel event 譬如我们上面所说的这段指令 我们在结尾加入 cancel event
- on command "/op":
- send "%event-world%" to console
- send "%event-commandsender%" to console
- send "%event-player%" to console
- cancel event
即使取消了事件 事件的元素仍然会被传递至插件内 这个是需要注意的
————————————————————————————————
● Conditions(条件)
条件用于判断句 有没有 是不是 都是条件的一种 基本格式为 "if" + 条件
这里我们和学习 Events 一样 我们先通过官方 Doc 找到所有的条件
这里我们拿最常用的一个条件作示例 判断玩家是否有权限
权限的英文是什么? "permission" 我们通过翻阅侧边栏可以得知
与 "permission" 相关的只有 "Has Permission" 一条 官方对这个条件的解释为
"Test whether a player has a certain permission." 翻译过来就是 "检测一个玩家是否拥有某一权限"
即我们所需要的 判断玩家是否有权限 那么我们又该如何使用呢 "Has Permssion" 条件呢?
在 "Has Permission" 下 "Patterns" 给了我们两种标准格式用法
- %players/console% (has|have) [the] permission[s] %texts%
- %players/console% (doesn't|does not|do not|don't) have [the] permission[s] %texts%
"[]"内可以省略
"(...|...)"内必须选择一项填写
"%%"内必须根据其所对应的类型进行填写
player has permission "player.op"
player have the permissions "player.op"
player have permissions "player.op"
...
正如之前所说的 可以省略的地方 无论如何搭配 表达的意思都是一样的
这也是 Skript 一大特点 并不需要非常严谨的语法 只要意思对 语法可以根据个人喜好进行选择
同样我们还是举一个以 "on command" 监听器为核心的例子加深一下大家的理解
- on command "/op":
- event-commandsender is player
- if event-player has permission "player.op":
- send "true" to event-player
- else:
- cancel event
- send "false" to event-player
如果有 指令将会正常进行 并发送 "true" 给玩家
如果没有 指令执行事件将被强制取消 并发送 "false" 给玩家
————————————————————————————————
● Effects(效果)
与其说它是效果 不如称作行动
我们把条件类的示例拿下来接着分析
- on command "/op":
- event-commandsender is "player"
- if event-player has permission "player.op":
- send "true" to event-player
- else:
- cancel event
- send "false" to event-player
- 指令监听 "/op":
- 事件-发送者类别 是 玩家
- 条件1:
- send "true" to event-player # 发送消息给玩家
- 条件2:
- cancel event # 取消事件
- send "false" to event-player # 发送消息给玩家
如果我希望执行其他行动呢? 比如我想给一个玩家发送一个 Title 消息
通过在官方 Doc 搜索 关于 Title 相关的 Effects 一共有两个
https://skriptlang.github.io/Skript/effects.html#EffResetTitle
https://skriptlang.github.io/Skript/effects.html#EffSendTitle
通过查看官方的注释
#EffResetTitle
"Resets the title of the player to the default values."
"重置玩家的 Title 至默认值"
#EffSendTitle
"Sends a title/subtitle to the given player(s) with optional fadein/stay/fadeout times."
"发送 Title/Subtitle 至指定玩家 可自定义渐入和淡出的时间"
一目了然 我们需要的是 #EffSendTitle 关于怎么用
本质上和学习Conditions(条件)一样 我们将注意点放在 "Patterns" 上
- send title %text% [with subtitle %text%] [to %players%] [for %time span%] [with fade[(-| )]in %time span%] [(and|with) fade[(-| )]out %time span%]
- send subtitle %text% [to %players%] [for %time span%] [with fade[(-| )]in %time span%] [(and|with) fade[(-| )]out %time span%]
"[]"内可以省略
"(...|...)"内必须选择一项填写
"%%"内必须根据其所对应的类型进行填写
我们大致上认识到行动的基本用法 即
- send title "..." with subtitle "..." to player for ... seconds with fade-in ... seconds and fade-out ... seconds
- 指令监听 "/op":
- 事件-发送者类别 是 玩家
- 条件1:
- send "true" to event-player
- send title "Hello!" with subtitle "持续时间 5s 渐入未设置 淡出未设置" to event-player for 5 seconds
- wait 15 seconds
- send title "Hello!" with subtitle "持续时间 5s 渐入 5s 淡出未设置" to event-player for 5 seconds with fade-in 5 seconds
- wait 15 seconds
- send title "Hello!" with subtitle "持续时间 5s 渐入 5s 淡出 5s" to event-player for 5 seconds with fade-in 5 seconds and fade-out 5 seconds
- 条件2:
- cancel event
- send "false" to event-player
"[]"内可以省略
"(...|...)"内必须选择一项填写
"%%"内必须根据其所对应的类型进行填写
所有的用法问题迎刃而解 学会读 "Patterns" 是初学者必备的技能
下文我将不会再提及如何使用 "Patterns" 查阅用法
————————————————————————————————
● Expressions(表达) & Types(类型)
假设你想要调整玩家的最大血量
调整血量 之前我们提过 动词 + 名词形式 这是属于 Effects(效果) 类
但是实际上是这样么?
实际上并不是 所谓的 Effect(效果) 虽然都是 动词 + 名词形式 但是对于 Effect(效果) 而言
注重的是 动词 而非后面跟着的 名词 例如 在玩家所在位置生成僵尸
Effect(效果) 所能提供的 只有 "生成"
通过查阅 Doc https://skriptlang.github.io/Skript/effects.html#EffSpawn 我们知道生成的用法基本为
- (spawn|summon) %entity types% [%directions% %locations%]
- (spawn|summon) %number% of %entity types% [%directions% %locations%]
这两个词我们又该怎么处理呢? 这时候我们就需要用到 Expressions(表达)
位置的英文单词是 "Location" 我们在官方 Doc 查到了 3 种有关 "Location" 的表达
https://skriptlang.github.io/Skript/expressions.html#ExprLocation
https://skriptlang.github.io/Skript/expressions.html#ExprLocationOf
https://skriptlang.github.io/Skript/expressions.html#ExprLocationAt
我们需要什么呢? 我们需要 "玩家所在的位置" 相同地 通过查看官方的注释
我们知道我们需要的是 #ExprLocationOf (The location of a block or entity.)
但是这只解决了我们 "所在位置" 的问题 并没有解决 "玩家" 和 "僵尸" 的问题
我们可以看到 "所在位置" Expressions(表达) 是没有主语的 但是对于 Skript 而言
Skipt 不同于其他语言 正如我在开头所展示的 相比较 Java 而言 Skript 更注重的是语言而非编程
我们需要把这些零碎的东西组成句子 必不可少的是主语 次要的是宾语
这时候我们就需要引入 Types(类型) 但凡你发现你的表达里缺少主语/宾语(对象) 来这里准没错
https://skriptlang.github.io/Skript/classes.html
通过翻译我们可以轻松知道 玩家的英文 以及 僵尸的英文 分别为 "player" "zombie"
与之相对应的 我们分别在 Types(类别) 中找到
https://skriptlang.github.io/Skript/classes.html#player
https://skriptlang.github.io/Skript/classes.html#entity
综合上面我们所获得的信息 我们获得了完整一行代码
- spawn zombie at location of player
Wow 恭喜你 看到这 你就可以开始尝试着写一些插件了
这里刚好有一个例子 不妨动动手 试一试
在玩家破坏方块时 检查玩家是否有 "fundamental.break" 这个权限
如果有那就在让后台发送一条命令 "/broadcast %player% 破坏了方块"
如果没有那就取消这个事件 并 向这个玩家发送 "你不能破坏这个方块"
答案不唯一 仅供参考
当然仅仅学这些并不够 为了做到能更快更灵活的使用各类语法
在闲暇的时候 把官方 Doc 提供的所有语法的注释都认真的看一遍是快速上手 Skript 的一种好办法
————————————————————————————————
"loop"用法
你开始尝试着写一些有一些小功能的脚本了 但是难免的你会出现这样的问题
你为玩家创建了很多变量 很多都是属于一个类型的
比如你把所有人的游戏币数量都存在了 {(玩家的名称)的游戏币数} 这些变量内
你把所有人的点券数量都存在了 {(玩家的名称)的点券数} 这些变量内
平时你单独去操作这些变量的时候 觉得也很容易
但是假设突然有一次 由于回档需要补偿玩家损失
你需要将所有玩家的 {(玩家的名称)的游戏币数} 这个变量都 +1000
还好只有10个玩家数据 你可以一个一个调 就是浪费一点时间 倒还不成问题
但是如果你有10000个玩家数据 如果你一个一个调 可能玩家都走完了 你也调不完
你遇到了新的问题 如何存储并快速操作一类变量?
这时候你需要两样东西 "数组" "loop"
● 数组
数组的基本格式为 {变量名::变量名::变量名......}
我们带入实景 将用 {(玩家的名称)的游戏币数} 存储转为用 {金币::(玩家的名称)} 存储玩家的游戏币数量
我现在服务器有 10000 个玩家
玩家名称为 1,2,3,......,10000
玩家游戏币数量为 100,200,300,......,1000000
那么对应的 {金币::1} 就是 名为 "1" 玩家的游戏币数量 100
如果我用输出语句输出 {金币::1} 至后台 那么很显然我会得到 100 这个数值
同样的我用输出语句输出 {金币::10000} 至后台 我会得到 1000000 这个数值
你可能会说 这不是和用 {(玩家的名称)的游戏币数} 一样么?
是的 确实 如果仅仅需要获得某一个玩家的游戏币数量 两者并没有什么区别
但是 数组好用之处在于 如果我将 {金币::(玩家的名称)} 中玩家的名称改为 "*" 即 {金币::*}
这时候会产生什么样的效果呢?
{金币::*} 将包含 所有 {金币::(玩家的名称)} 变量
而这个 却是 {(玩家的名称)的游戏币数量} 怎么改也做不到的
● Loop
利用数组我们知道了如何快速获取一类数据
但是我们又该如何快速操作这一类数据呢? 这时候就需要引入我们的 Loop 结构
Loop 即 循环结构 是 Skript 里非常常用的结构语句 主要用于操作数据量较大的一类变量
Loop 共有几大标准配合
Loop + 数组 / Loop + 次数 / Loop + Types(类型)
● Loop + 数组 结构
● Loop + 次数 结构
● Loop + Types(类型) 结构
————————————————————————————————
● 注册指令
说到现在 我们所有的代码 似乎都是基于监听器进行编写的
我们都需要去触发监听器 才能执行我们的代码 那有没有什么办法可以主动触发我们的代码?
这时候我们就需要引入 MineCraft 插件最核心的功能 指令功能而在 Java 里你可能需要这样注册一个指令
- @Override
- public boolean onCommand(final CommandSender sender, Command cmd, String label, String[] arg){
- if (cmd.getName().equalsIgnoreCase("自定义指令")){
- 代码段落
- }
- return true;
- }
- command /自定义指令:
- trigger:
- 代码段
如果我想注册一个 "/我学你马Java" 的命令 你只需这样
- command /我学你马Java:
- trigger:
- kill player
- send "不许说Java坏话" to player
通过套公式 你可以创造成千上万的指令不成问题 但是实际上我们在使用一个插件的时候
并不是只有 "/..." 结构的指令存在 更多的是 "/... ... ..." 来构成一类指令
那我们又该如何注册这样结构的指令呢? 非常简单 基本格式与上面几乎无异
- command /自定义指令 [<类型>] [<类型>] ...:
- trigger:
- 代码段
有人问 "类型" 有哪些 其实我也说不全 我常用的有这几种
"text" - 字符类型 什么是字符? 就是字面意思 字词符号
"player" - 在线玩家
"offline player" - 离线玩家
"number" - 数字类型
"integer" - 整数类型
那这些类型又有什么用处呢? 它实际上是限制了你可以输入的参数
比如我创建如下指令
- command /hello [<player>]:
- trigger:
- 代码段
那么我在执行这个指令的时候必须在这个位置上填上一个在线玩家的名称
同样的如果我把 "[<player>]" 换成 "[<integer>]" 我就需要在这个位置上填写一个整数
如果我填了 "1.2"(小数/浮点数) Skript 就会提示我 填写的参数类型错误
为什么填写参数 那肯定是在代码段内需要使用这些输进来的参数
那么我们在代码段里有该如何调用这些被我们输入进来的参数呢?
比如像是上面这个指令 它只有一个可以填参数的位置 那么在代码段内 它就是 arg-1 即 第一个参数的意思
我们只需要记住核心规则 它排在第几位 在代码段内 它就是 "arg-几"
当然我在这块的了解并不是很深入 为了不把大家带上歪路 这里引用国外 Skript 作者更为详细的指令注册的教程
————————————————————————————————
- command /<指令名称> <参数>:
- aliases:
- executable by:
- usage:
- description:
- permission:
- permission message:
- cooldown: <冷却时间>
- cooldown message:
- cooldown bypass:
- cooldown storage: <变量>
- trigger:
- 代码段
命令名称基本上是命令 您可以在命令名称中使用任何字符(空格字符除外)
当然如果在命令名称中使用空格字符 那么空格字符后的文本将成为参数
命令名称前的斜杠字符(/)是可选的(但这并不意味着您可以在执行命令时不带斜杠)
参数(可选)
可以通过将参数放在 "[]" 中来使其成为可选参数
类型参数
可以通过使用规定的格式来限制参数的类型 例如: <type = default value>
- 类型为 "text/string" 的参数可以接受任何字符 但 "object" 类型不能用作于参数
- 类型可以是多个 (例如 number -> numbers entity -> entities) 通过这样的方法 可以使参数接受多个值
- "= default value" 这一部分是可选的 如果命令执行者未输入参数 系统将自动使用默认值
- 同样你也可以使用这样的方式设置参数默认值 例如: <item = %player's tool%>
命令示例:
- command /kill <entity types> [in [the] radius <number = 20>]:
但是如果没有输入数值 系统将自动使用默认值 半径 20
Aliases
子命令 命令的别名 如果需要创建多个子命令 请使用用逗号分隔
示例:(/alias1,alias2,/alias3)
Executable By
指定可以使用该命令的执行者
例如:console(后台) players(玩家) the console and players(后台和玩家)
Usage
使用不正确时 将发送的消息
Description
命令描述 其他插件可以获取/显示此信息
Permission
执行命令所需要的权限
Permission Message
执行者没有权限 提示信息
Cooldown
多长冷却时间后可以再次使用该命令 需要注意的是 关服时所有命令冷却时间将被重置
Cooldown Message
冷却期间 提示信息
Cooldown Bypass
无视冷却时间所需要的权限
Cooldown Storage
存储冷却时间全局变量名称
————————————————————————————————
● Function 第一类结构
众所周知 一个复杂的插件 不免出现相似的代码段
有时候你看着不舒服 想要缩减段落 却又无从下手 这时你就需要 Function 来帮忙了
这里取本人的SUPERGUILDS的一段代码做讲解
- file "plugins/SUPERGUILDS/%{_fileDir}%.yml" does not exists:
- create file "plugins/SUPERGUILDS/%{_fileDir}%.yml"
- yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml" is not loaded:
- load yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml" as "plugins/SUPERGUILDS/%{_fileDir}%.yml"
- set yaml value "%{_variableName}%" from "plugins/SUPERGUILDS/%{_fileDir}%.yml" to "%{_value}%"
- save yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml"
这段代码的功能是存储数据至yaml文件内 对于一个非常复杂的脚本 数据的存储是必不可少的
同样也会频繁出现在我们的代码中的 难道真的每一次需要存储数据的时候 都需要再去复制粘贴么?
对于一个初学者来说 无可厚非 就是多几行而已 但是对于一个老手来说 复制粘贴不可取
首先对于一个 5000+ 以上的插件来说 你需要关注的东西很多 一个是代码优化 一个是代码可读性
就拿 SUPERGUILDS 来说 7000+ 行 我所有的数据读取和存储都是通过方法完成 单一个数据写入方法
我就使用了 100+ 次(也就是上面我提到的这段代码) 如果我们把它都像上面一样全部展开 我的脚本将立即增加 600+ 行
但是我们在写脚本的时候真正需要的是这些么? 不 我们需要的是效果 是功能 不是数据处理的流程
每次写入数据都需要白白多占 6 行 既不方便后期维护 同时因为要兼顾路径正确与否 浪费很多时间在查错上面 不划算
Function 为你解决了这些难题 方法的注册和指令的注册有共同的地方 比如它们同样需要参数 结构一般为
- function 方法名(参数名:参数类型, 参数名:参数类型, ...):
- 代码段落
套用公式 我们可以把上面的代码段转换成方法段
- function SG_writeFile(variableName: text, value: text, fileDir: text):
- file "plugins/SUPERGUILDS/%{_fileDir}%.yml" does not exists:
- create file "plugins/SUPERGUILDS/%{_fileDir}%.yml"
- yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml" is not loaded:
- load yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml" as "plugins/SUPERGUILDS/%{_fileDir}%.yml"
- set yaml value "%{_variableName}%" from "plugins/SUPERGUILDS/%{_fileDir}%.yml" to "%{_value}%"
- save yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml"
- file "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml" does not exists:
- create file "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml"
- yaml "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml" is not loaded:
- load yaml "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml" as "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml"
- set yaml value "Datas.Username" from "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml" to "**EVER"
- save yaml "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml"
- SG_writeFile("Datas.Username", "**EVER", "playerdata/%uuid of player%")
通过这样的方法我们大大减少了代码量 提高了开发效率你以为 Function 就结束了?其实不然 作为方法 很多的时候它并没有 "操作" 功能
更多的是 "整理/查找/判断" 功能 那遇到这样的情况我们又该如何灵活使用 Function 以供我们使用呢?
上面我提到了使用 Function 写入了数据 那么有写入肯定需要读取 我们又该如何利用 Function 来缩短我们读取的代码呢?
————————————————————————————————
● Function 的第二类结构
- function 方法名(参数名:参数类型, 参数名:参数类型, ...) :: 输出参数类型:
- 代码段落
- return 返回值
什么叫输出? 就是代码执行完 得到的一个数值 让这个数值回到主代码段 即 返回一个值
什么叫参数类型 这里举一个例子 我们知道 1+1=2 因为 1 是一个数字 2 也是一个数字 所以才有 数字+数字=数字
那如果我这样写 ①+一 那么又等于什么呢? "①" 和 "一" 在你的思维中 很可能会认为它们仍然是数字 你会觉得答案还是 2
但是执行我们代码的是计算机 并不是 我们 计算机所能识别的数字 只有 阿拉伯数字 "1,2,3..."
而 "①,一..." 计算机只能识别它们一些字符 并不能利用这些字符进行加减运算 就像是现在问你 水 + 苹果 = ?
你或许也没有一个准确的答案. 计算机更是这样
所以这时候就需要参数类型来规范我们运算中的这些值 拿一段来自 SUPERISLANDS 的代码做示例
- function SI_isSlotAvaliable(s: integer, z: integer) :: boolean:
- set {_m} to {_z} * 9 - 1
- {_s} is not between 0 and {_m}:
- return false
- return true
最终会返回一个参数类型为 boolean("布尔")的值 即 "false/true"
那么实际操作中我们就可以通过这样一段代码对给定的数值进行判断 是否满足条件
如果满足那么返回 "true" 如果不满足 那么就将返回 "false"
需要知道的是 在 Skript 编程里 如果代码已经成功获得的最终值 那么方法代码将立即终止
从返回成功的一行开始 后面所有代码将不再执行 切记
到此 所有基础教程已结束 谢谢大家赏脸看完 全文 11111 字 都是自己的一些干货 点个收藏 给点人气便是对我最大的支持
-2020.2.28
● 更新日志
2021.12 数据,可能有更多内容
萝卜青菜各有所爱 Skript 和 Java 哪个适合你 哪个就是好编程软件 切勿因此引战
前言:
代码:
- #JAVA:
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent evt) {
- Player player = evt.getPlayer(); // 玩家加入
- ItemStack itemstack = new ItemStack(Material.DIAMOND, 64); // 定义钻石
- if (player.hasPermission("")) {
- inventory.addItem(itemstack); // 给予钻石
- player.sendMessage("欢迎你加入服务器!你获得了64枚钻石!");
- }
- }
- #Skript:
- on join:
- if player has permission "":
- message "欢迎你加入服务器!你获得了64枚钻石!"
- give 64 diamond to player
因为其语法简单而受到很多中小型服主的青睐 很多人多多少少对这块有一些了解
但是毕竟受众人群小 很多时候也会出现想学却无从下手 有问题却无处可问的尴尬境地
站内现有的Skript的教程 https://www.mcbbs.net/thread-492211-1-1.html 最后一次编辑为2016年
虽然讲的东西很多 但是基本上是基于原版教程的少量扩充 对于新手理解起来还是过于困难
作为
Fundamental (https://www.mcbbs.net/thread-822402-1-1.html)
SUPERGUILDS (https://www.mcbbs.net/thread-870934-1-1.html)
的作者 我自认为自己对 Skript 还是多少有些理解
我希望通过这篇教程谈谈我在编写 Skript 脚本过程中的一些经验之谈
基础教程将分为
初识代码
了解"5大类"
"5大类"用法
"loop"用法
注册指令
"function"用法
板块进行教学
————————————————————————————————
初识代码:
Skript 不同于其他语言 正如我在开头所展示的 相比较 Java 而言 Skript 更注重的是语言而非编程
语言是什么 就是 人说的话 也就是说 编写 Skript 插件 = 把你想的说出来 而这种语言是英语
也就是说只要你知道英语的基本语法 学习 Skript 便不成问题
不过对于初学编程的玩家 我还是需要明确几点基本原则
1.理论是基础 也是必要的 实践得到的经验是学好的唯一途径 只学理论永远写不好插件
2.读懂其他脚本编写者的脚本比自己复制粘贴拼凑插件对你的学习更有意义
————————————————————————————————
安装Skript
Skript是一款插件 也就意味着你需要像安装其他插件一样 需要放在 plugins 目录下
很多人认为越新版本的插件对服务器越好 但实际上对于 Skript 不是这样的
作为用户我大致上体验了 2.1.2 2.2dev36 2.2dev37c 2.3.5 这几个版本
不同版本的服务器个人推荐使用的 Skript 插件版本如下
1.7.10 2.1.2 https://dev.bukkit.org/projects/skript/files/779542/download
1.8.8-1.12.2 2.2dev37c https://github.com/SkriptLang/Skript/releases/download/dev37c/Skript.jar
1.13.2+ 最新版 https://github.com/SkriptLang/Skript/releases
————————————————————————————————
如何编写脚本?
将如上文件安装完成后 plugins 目录下将多出 一个 Skript 文件夹 文件夹里面也多出了许许多多文件
现在你不需要理会他们
使用文本编辑器 Sublime/Visual Studio Code/TXT 在 plugins/Skripts/scripts 目录下创建一个名为 "1.sk" 的文件
将如下代码复制进 "1.sk" 文件内 并保存
代码:
- on join:
- send "Hello %player%" to event-player
因为脚本生效了 脚本监听了玩家登陆事件并在玩家触发这一事件后执行了发送信息行动 所以你看到了 "Hello 你的游戏名称"
需要注意的是在 Skript 中 如果一行代码以 ":" 结尾 那么下一行需要进行缩进操作 如果没有就不需要进行缩进
例如:
代码:
- on join:
- if player has permission "player.join":
- if player is op:
- player is online:
- send "..." to console # 需要缩进
- send "..." to console #不需要缩进
- kick player #不需要缩进
————————————————————————————————
在正式进入 Skript 教程之前 我们还需要了解编程的几大元素 变量 运算 控制
● 变量
在Skript中 一共有两大类变量形式 全局变量和局部变量
全局变量以 {...} 存在 括号内 "..." 可以替换成除 "_" 以外的字符
局部变量以 {_...} 存在 括号内 "..." 可以替换成任意字符
这些是学术名词 但具体是什么意思 我们需要具体分析
首先我们先要介绍 variables.csv 这一个 Skript 插件启动时默认生成的文件
可能很多新人在刚开始使用 Skript的时候都不甚了解 有时候还会觉得它太大 占空间
但是我要说的是 这个文件非常重要 它保存了你所有创造的全局变量 你在任何一处都可以调用这些变量
对其进行加减乘除... 不受到关服/开服的影响 只要不删掉 它们就会被永久存在 variables.csv 文件内
而局部变量天生命短 它只能留在你写的一部分代码内 出来了你就用不了了 它们不会被文件保存 随着开服/关服消失
可能我说了这么多 很多人还是不明白 下面我就用两段代码进行对比 具体阐述其存在的意义
代码:
- on load:
- set {_x} to 1
- set {x} to 2
- send "onload {_x}局部变量的数值为 %{_x}%" to console
- send "onload {x}全局变量的数值为 %{x}%" to console
并在脚本加载的时候 将两个变量结果输出至后台 我们可以看到
代码:
- "{_x}局部变量的数值为 1"
- "{x}全局变量的数值为 2"
代码:
- on load:
- set {_x} to 1
- set {x} to 2
- command /test:
- trigger:
- send "command {_x}局部变量的数值为 %{_x}%" to console
- send "command {x}全局变量的数值为 %{x}%" to console
而是把数据输出的任务交给了 "/test" 命令 输入指令后 我们可以看到
代码:
- "{_x}局部变量的数值为 <none>"
- "{x}全局变量的数值为 2"
但是我已经在赋值了第一段代码也正常输出了结果 为什么在第二段代码中 局部变量变成了 <none> 呢?
因为局部变量只留存在 "on load" 所在的代码内 并没有走出来 在被 "on load" 使用完后
{_x}局部变量便失去了它的赋值 在 "command" 中 {_x} 作为局部变量没有继承之前的数值 所以我们在测试时获得了 <none>
在编写代码的过程中全局变量和局部变量的选择将会大大影响插件实际工作效率 请谨慎选择
————————————————————————————————
进阶变量(数组):
如果我们需要将一系列数据记录在一个变量内 这时就需要我们的数组参与进来
基本格式为 {_x::*} {x::*} {_x::*::*...} {x::*::*...} (仍受全局/局部规则的约束)
如果把只拥有一个值的变量看成一杯饮料 那么数组就是一个饮料柜 里面可以放任意数量的饮料
你可以用任意顺序把他们排列起来 等到需要喝饮料的时候 再把这个柜子搬出来 把需要的拿出来即可
这样可以使代码更为整洁 更有利于较为复杂插件的开发
(本内容与 Skript "loop" 相关知识密切相关 本段落仅为解释 实际使用方法请查看 "loop" 章节)
● 运算
Skript 有三种常见的运算类型
add/remove/set/delete 分别代表 加/减/赋值/删除
相较于其他编程语言 Skript 的运算类型使用更加广泛
今后将会频繁的用到它们
● 控制
代码的核心内容 对于 Skript 而言共有 "5大类" 需要进行学习
每一类都有对应其特有的使用方式 以及 结构组成
————————————————————————————————
初识 "5大类"
Skript 脚本核心 即为 "5大类"
分别为 "Events"(事件) "Conditions"(条件) "Effects"(效果) "Expressions"(表达) "Types"(类型)
为了让大家能更好的认知这几大内容 我这里举一个例子 如果我想制作一个如下功能的脚本
代码:
- 有一位玩家进入服务器
- 判断是否只有一个玩家
- 如果只有一个玩家 那么发送一条信息
- 如果不止一个玩家 那么踢出所有玩家
首先是第一句 玩家加入服务器 名词 + 动词 +名词 的格式
典型的第1类(Events) 服务器会监控每一位在线玩家的一举一动 每一个方块的一举一动
我们统称为事件 我们想要知道这些事件 就需要监听器 监听玩家的行动
代码:
- Java 监听器
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent e){
- ……
- }
- Skript 监听器
- On join:
- ……
有了监听器 我们就可以监控玩家/方块的各种行为 (事件) 并在这些玩家触发监听器时 对这些事件进行调控
————————————————————————————————
send "..." to player #将引号内的信息发送至玩家[/code]
第二句 是否只有一个玩家 名称 + 是否/有无 的格式 典型的判断句
对应第2类(Conditions) 怎么让服务器知道是不是只有一个玩家在线 判断语句就起到了非常重要的作用
获取数据并进行对比 然后得到结果 这就是判断语句需要做的事情
判断句的一般以 “if” 开头 + “判断条件” 构成 这里的 玩家数量 属于第4类 Expressions(表达)
代码:
- 判断 玩家数量 是否为 1
- If player count is 1
1.玩家数量为1 > 发送一条信息
2.玩家数量不为1 > 踢出所有玩家
为1的我们已经知道了 “if player count is 1” 那么不为1的又该如何判断?
这时候 “else” 就充分发挥了作用 “else” 中文翻译为 “其他的,不同的” 顾名思义
如果我们的主条件是玩家数量为1 那么对立面(玩家数量不为1)就是 ”else” 的范畴
代码:
- if player count is 1(玩家数量为1):
- 发送一条信息
- else(玩家数量不为1):
- 踢出该玩家
第三句第四句判断句结尾 紧跟着需要执行一个行动
第三句需要执行 发送一条信息至该玩家
第四句需要执行 踢出该玩家
动词 + 名称 典型的动词句 对应Effects(效果) 类
通过查阅 Doc 我们了解到
发送消息的语法是 “send %string% to %commandsenders%”
踢出玩家的语法是 “kick %players%”
看到 “%” 的你们可能和我一样迷茫 这些究竟是什么呢?
这时候就需要第5类Types(类别) 再次查阅 Doc 在 Types(类别) 下
我们找到了command sender 我们得知 %commmandsenders%
该(Types)类别下一共有3个元素可供选择
一个是 “console(后台)” 一个是 “player(玩家)” 一个是 “players(所有玩家)”
我们需要什么呢? “发送一条信息至该玩家” 是的 我们需要的是玩家 即 “player”
同样的 “踢出所有玩家” 我们需要的是所有玩家 即 “players”
综合监听和条件我们就有了一段基础代码 实现了我们设计所需要的效果
代码:
- on join:
- if player count is 1:
- send “…” to player
- else:
- kick players
————————————————————————————————
"5大类"用法
在开讲之前 我需要先和大家分享几个查阅脚本语法的网站
● 推荐
https://skripthub.net/docs/ 部分老版本附属语法查不到 但查询方便 有效
官方Doc https://skriptlang.github.io/Skript原版Skript Doc方便查询新手学习
● 不推荐
中文Wiki 很久没有更新 很多东西都查不到 适合学习不适合查语法
Skunity https://docs.skunity.com/syntax/ 内容非常多 但很多已经无效 没有更新
这些网站将频繁出现在我们的教程之中 请务必收藏好
————————————————————————————————
● Events(事件)
什么是事件?
什么人在什么地方做了什么事情 最简单的三要素构成了事件
那么 "谁" 能知道 什么人在什么地方做了什么事情? 这时候监听器就理所应当成为了那个 "谁"
所谓的 Events 并不是单纯指一个事件 而是指监听器监监听到了一个事件
所有原版的 Skript 监听器都可以在下面这个网址找到
https://skriptlang.github.io/Skript/events.html
可以看到 Skript 提供给我们一共 120 个监听可供使用这么多的监听 我们究竟该怎么选择呢 选择完又该如何使用呢?
这里我们拿 "on command" 监听器为例(命令执行监听器)
我们把我们的定义拿过来 什么人在什么地方做了什么事情
现在我们已知什么事情指的是执行命令 现在我们需要知道什么?
我们需要知道的是 什么人? 什么地方?
那么我们如何知道这些元素呢? 这时候我们需要下面这个网站
https://skripthub.net/docs/

在侧边栏中选中(Skript Events)后 在搜索栏中输入 "on command"

我们可以看到对应 "on command" 的一张信息卡片
我们要重点关注的是 "Event Values" 这一标签下所对应的内容
可以看到 "on command" 卡片上 "Event Values" 标签下共有三个字段
分别为 "event-world" "event-commandsender" 以及 "event-player"
翻译对应为 "事件-世界" "事件-指令发送者" "事件-玩家"
利用这些 我们便可以获取到 事件中的 什么人 什么地方 这些具体的信息
我们看一个 "on command" 相关示例
代码:
- on command "/op":
- send "%event-world%" to console
- send "%event-commandsender%" to console
- send "%event-player%" to console
监听被触发后 将自动将三个元素 "event-world" "event-commandsender" "event-player" 输出到后台
如果是一位玩家执行了 "/op" 命令 "event-world" 将会输出玩家在哪个世界执行了命令
"event-commandsender" 将会输出为 "player" 因为监听器由玩家触发 而 "event-player" 则会输出为 "玩家的名字"
那么如果是后台执行了 "/op" 命令呢? 因为后台并不存于任何一个世界 也没有名字
上述三个元素只会有 "event-commandsender" 存在并正常输出为 "console" 而其余不存在元素将全部输出为 "<none>"
相同的你可以利用这样的方法 输出任何一个监听器下 "Event Values" 的元素值
这种获取元素值的方法将在你需要使用任何从来没有接触过得监听器的时候 快速让你掌握监听器的基本信息
既然我们已经监听到了这个事件 除了获取一些与事件相关的信息之外 我们还能做一些什么呢?
取消事件!
事件发生的顺序是 事件准备发生 > 监听器监听到 > 事件正式发生
如果我们在监听器监听到后 加入 取消事件 这一环节
事件发生的顺序就变为了 事件准备发生 > 监听器监听到 > 取消事件 > 事件未发生
我们就成功阻止了 "/op" 指令的发生 那在实际脚本中我们怎么实现这一流程呢?
cancel event 譬如我们上面所说的这段指令 我们在结尾加入 cancel event
代码:
- on command "/op":
- send "%event-world%" to console
- send "%event-commandsender%" to console
- send "%event-player%" to console
- cancel event
即使取消了事件 事件的元素仍然会被传递至插件内 这个是需要注意的
————————————————————————————————
● Conditions(条件)
条件用于判断句 有没有 是不是 都是条件的一种 基本格式为 "if" + 条件
这里我们和学习 Events 一样 我们先通过官方 Doc 找到所有的条件
这里我们拿最常用的一个条件作示例 判断玩家是否有权限
权限的英文是什么? "permission" 我们通过翻阅侧边栏可以得知
与 "permission" 相关的只有 "Has Permission" 一条 官方对这个条件的解释为
"Test whether a player has a certain permission." 翻译过来就是 "检测一个玩家是否拥有某一权限"
即我们所需要的 判断玩家是否有权限 那么我们又该如何使用呢 "Has Permssion" 条件呢?
在 "Has Permission" 下 "Patterns" 给了我们两种标准格式用法
代码:
- %players/console% (has|have) [the] permission
%texts%
%players/console% (doesn't|does not|do not|don't) have [the] permission%texts%
"[]"内可以省略
"(...|...)"内必须选择一项填写
"%%"内必须根据其所对应的类型进行填写
"(...|...)"内必须选择一项填写
"%%"内必须根据其所对应的类型进行填写
player has permission "player.op"
player have the permissions "player.op"
player have permissions "player.op"
...
正如之前所说的 可以省略的地方 无论如何搭配 表达的意思都是一样的
这也是 Skript 一大特点 并不需要非常严谨的语法 只要意思对 语法可以根据个人喜好进行选择
同样我们还是举一个以 "on command" 监听器为核心的例子加深一下大家的理解
代码:
- on command "/op":
- event-commandsender is player
- if event-player has permission "player.op":
- send "true" to event-player
- else:
- cancel event
- send "false" to event-player
如果有 指令将会正常进行 并发送 "true" 给玩家
如果没有 指令执行事件将被强制取消 并发送 "false" 给玩家
————————————————————————————————
● Effects(效果)
与其说它是效果 不如称作行动
我们把条件类的示例拿下来接着分析
代码:
- on command "/op":
- event-commandsender is "player"
- if event-player has permission "player.op":
- send "true" to event-player
- else:
- cancel event
- send "false" to event-player
代码:
- 指令监听 "/op":
- 事件-发送者类别 是 玩家
- 条件1:
- send "true" to event-player # 发送消息给玩家
- 条件2:
- cancel event # 取消事件
- send "false" to event-player # 发送消息给玩家
如果我希望执行其他行动呢? 比如我想给一个玩家发送一个 Title 消息
通过在官方 Doc 搜索 关于 Title 相关的 Effects 一共有两个
https://skriptlang.github.io/Skript/effects.html#EffResetTitle
https://skriptlang.github.io/Skript/effects.html#EffSendTitle
通过查看官方的注释
#EffResetTitle
"Resets the title of the player to the default values."
"重置玩家的 Title 至默认值"
#EffSendTitle
"Sends a title/subtitle to the given player(s) with optional fadein/stay/fadeout times."
"发送 Title/Subtitle 至指定玩家 可自定义渐入和淡出的时间"
一目了然 我们需要的是 #EffSendTitle 关于怎么用
本质上和学习Conditions(条件)一样 我们将注意点放在 "Patterns" 上
代码:
- send title %text% [with subtitle %text%] [to %players%] [for %time span%] [with fade[(-| )]in %time span%] [(and|with) fade[(-| )]out %time span%]
- send subtitle %text% [to %players%] [for %time span%] [with fade[(-| )]in %time span%] [(and|with) fade[(-| )]out %time span%]
"[]"内可以省略
"(...|...)"内必须选择一项填写
"%%"内必须根据其所对应的类型进行填写
"(...|...)"内必须选择一项填写
"%%"内必须根据其所对应的类型进行填写
我们大致上认识到行动的基本用法 即
代码:
- send title "..." with subtitle "..." to player for ... seconds with fade-in ... seconds and fade-out ... seconds
代码:
- 指令监听 "/op":
- 事件-发送者类别 是 玩家
- 条件1:
- send "true" to event-player
- send title "Hello!" with subtitle "持续时间 5s 渐入未设置 淡出未设置" to event-player for 5 seconds
- wait 15 seconds
- send title "Hello!" with subtitle "持续时间 5s 渐入 5s 淡出未设置" to event-player for 5 seconds with fade-in 5 seconds
- wait 15 seconds
- send title "Hello!" with subtitle "持续时间 5s 渐入 5s 淡出 5s" to event-player for 5 seconds with fade-in 5 seconds and fade-out 5 seconds
- 条件2:
- cancel event
- send "false" to event-player
"[]"内可以省略
"(...|...)"内必须选择一项填写
"%%"内必须根据其所对应的类型进行填写
"(...|...)"内必须选择一项填写
"%%"内必须根据其所对应的类型进行填写
所有的用法问题迎刃而解 学会读 "Patterns" 是初学者必备的技能
下文我将不会再提及如何使用 "Patterns" 查阅用法
————————————————————————————————
● Expressions(表达) & Types(类型)
假设你想要调整玩家的最大血量
调整血量 之前我们提过 动词 + 名词形式 这是属于 Effects(效果) 类
但是实际上是这样么?
实际上并不是 所谓的 Effect(效果) 虽然都是 动词 + 名词形式 但是对于 Effect(效果) 而言
注重的是 动词 而非后面跟着的 名词 例如 在玩家所在位置生成僵尸
Effect(效果) 所能提供的 只有 "生成"
通过查阅 Doc https://skriptlang.github.io/Skript/effects.html#EffSpawn 我们知道生成的用法基本为
代码:
- (spawn|summon) %entity types% [%directions% %locations%]
- (spawn|summon) %number% of %entity types% [%directions% %locations%]
这两个词我们又该怎么处理呢? 这时候我们就需要用到 Expressions(表达)
位置的英文单词是 "Location" 我们在官方 Doc 查到了 3 种有关 "Location" 的表达
https://skriptlang.github.io/Skript/expressions.html#ExprLocation
https://skriptlang.github.io/Skript/expressions.html#ExprLocationOf
https://skriptlang.github.io/Skript/expressions.html#ExprLocationAt
我们需要什么呢? 我们需要 "玩家所在的位置" 相同地 通过查看官方的注释
我们知道我们需要的是 #ExprLocationOf (The location of a block or entity.)
但是这只解决了我们 "所在位置" 的问题 并没有解决 "玩家" 和 "僵尸" 的问题
我们可以看到 "所在位置" Expressions(表达) 是没有主语的 但是对于 Skript 而言
Skipt 不同于其他语言 正如我在开头所展示的 相比较 Java 而言 Skript 更注重的是语言而非编程
我们需要把这些零碎的东西组成句子 必不可少的是主语 次要的是宾语
这时候我们就需要引入 Types(类型) 但凡你发现你的表达里缺少主语/宾语(对象) 来这里准没错
https://skriptlang.github.io/Skript/classes.html
通过翻译我们可以轻松知道 玩家的英文 以及 僵尸的英文 分别为 "player" "zombie"
与之相对应的 我们分别在 Types(类别) 中找到
https://skriptlang.github.io/Skript/classes.html#player
https://skriptlang.github.io/Skript/classes.html#entity
综合上面我们所获得的信息 我们获得了完整一行代码
代码:
- spawn zombie at location of player
Wow 恭喜你 看到这 你就可以开始尝试着写一些插件了
这里刚好有一个例子 不妨动动手 试一试
在玩家破坏方块时 检查玩家是否有 "fundamental.break" 这个权限
如果有那就在让后台发送一条命令 "/broadcast %player% 破坏了方块"
如果没有那就取消这个事件 并 向这个玩家发送 "你不能破坏这个方块"
答案不唯一 仅供参考
代码:
- on break:
- if [event-]player has permission "fundamental.break":
- execute console command "/broadcast %[event-]player% 破坏了方块"
- else:
- cancel event
- send "你不能破坏这个方块" to [event-]player
当然仅仅学这些并不够 为了做到能更快更灵活的使用各类语法
在闲暇的时候 把官方 Doc 提供的所有语法的注释都认真的看一遍是快速上手 Skript 的一种好办法
————————————————————————————————
"loop"用法
你开始尝试着写一些有一些小功能的脚本了 但是难免的你会出现这样的问题
你为玩家创建了很多变量 很多都是属于一个类型的
比如你把所有人的游戏币数量都存在了 {(玩家的名称)的游戏币数} 这些变量内
你把所有人的点券数量都存在了 {(玩家的名称)的点券数} 这些变量内
平时你单独去操作这些变量的时候 觉得也很容易
但是假设突然有一次 由于回档需要补偿玩家损失
你需要将所有玩家的 {(玩家的名称)的游戏币数} 这个变量都 +1000
还好只有10个玩家数据 你可以一个一个调 就是浪费一点时间 倒还不成问题
但是如果你有10000个玩家数据 如果你一个一个调 可能玩家都走完了 你也调不完
你遇到了新的问题 如何存储并快速操作一类变量?
这时候你需要两样东西 "数组" "loop"
● 数组
数组的基本格式为 {变量名::变量名::变量名......}
我们带入实景 将用 {(玩家的名称)的游戏币数} 存储转为用 {金币::(玩家的名称)} 存储玩家的游戏币数量
我现在服务器有 10000 个玩家
玩家名称为 1,2,3,......,10000
玩家游戏币数量为 100,200,300,......,1000000
那么对应的 {金币::1} 就是 名为 "1" 玩家的游戏币数量 100
如果我用输出语句输出 {金币::1} 至后台 那么很显然我会得到 100 这个数值
同样的我用输出语句输出 {金币::10000} 至后台 我会得到 1000000 这个数值
你可能会说 这不是和用 {(玩家的名称)的游戏币数} 一样么?
是的 确实 如果仅仅需要获得某一个玩家的游戏币数量 两者并没有什么区别
但是 数组好用之处在于 如果我将 {金币::(玩家的名称)} 中玩家的名称改为 "*" 即 {金币::*}
这时候会产生什么样的效果呢?
{金币::*} 将包含 所有 {金币::(玩家的名称)} 变量
而这个 却是 {(玩家的名称)的游戏币数量} 怎么改也做不到的
● Loop
利用数组我们知道了如何快速获取一类数据
但是我们又该如何快速操作这一类数据呢? 这时候就需要引入我们的 Loop 结构
Loop 即 循环结构 是 Skript 里非常常用的结构语句 主要用于操作数据量较大的一类变量
Loop 共有几大标准配合
Loop + 数组 / Loop + 次数 / Loop + Types(类型)
● Loop + 数组 结构
我们继续拿上面的数组进行举例
我们需要把每个玩家金币变量值 +1000 的事情还没有结束
为了更好的讲解 我们先把代码放上来
代码:
- loop {金币::*}:
- set {_x} to loop-value
- add 1000 to {_x}
- set {金币::%loop-index%} to {_x}
第二行通过 Effects(效果) 将 "loop-value" 赋值给 {_x}
第三行通过 Effects(效果) 给 {_x} 变量加了 1000
第四行通过 Effects(效果) 将 {金币::%loop-index%} 的值 设置为 {_x}(原数值+1000后的数值)
那么什么是 "loop-value" "loop-index" ?
首先我们需要先了解 Loop 的基本原理
上一环节我们通过认识 数组 知道了
{金币::*} 将包含 所有 {金币::(玩家的名称)} 变量
也就是意味着它包含了 {金币::1},{金币::2},{金币::3},{金币::4},{金币::5}......,{金币::10000}
而 Loop +数组结构 作为一个系统 会从 排在最前面的变量开始{金币::1}
一直 到最后 {金币::10000}(按照存进去的顺序) 每循环一次就会从数组里取一个变量出来
我们可以把数组的这些变量看成一列学生队伍 Loop 作为一位老师 他的任务是核查每一位学生的身份信息
拿第一次循环来说 Loop 老师 查到了 1(loop-index) 学生 学生把身份信息 {金币::1} 栏目中的值 100(loop-value)
告诉 Loop Loop 把 100(loop-value) 存在 计算器({_x}) 里 并加了 1000 再把结果 1100({_x})
还给学生 1(loop-index) (此时学生 1(loop-index) 身份信息 {金币::1(loop-index)} 一栏 就变成了 1000({_x}))
Loop 老师继续检查下一个学生 2(loop-index) 并重复相同的动作 直至 队列检查完
告诉 Loop Loop 把 100(loop-value) 存在 计算器({_x}) 里 并加了 1000 再把结果 1100({_x})
还给学生 1(loop-index) (此时学生 1(loop-index) 身份信息 {金币::1(loop-index)} 一栏 就变成了 1000({_x}))
Loop 老师继续检查下一个学生 2(loop-index) 并重复相同的动作 直至 队列检查完
循环语句通过这样的方式 检查了数组内 所有的元素
解决了我们在编写代码的过程中 操作同类变量上的不方便 这是在开发大型插件必备的能力 请认真对待
● Loop + 次数 结构
Essential 插件 想必大家都有用过 不知道有没有人注意过
在玩家执行 "/tpaccept" 以接受传送的时候 系统会有一个提示
"传送中 3 秒内请勿移动 否则移动将会被取消" 在没有学过 Loop + 次数 之前
你写的代码可能是这样的
代码:
- set {_loc1} to location of player
- wait 3 seconds
- set {_loc2} to location of player
- if {_loc1} is not {_loc2}:
- send "传送失败"
- else:
- teleport player to {_loc}(传送目标的坐标)
- send "传送成功"
玩家可以在这 3 秒内想干什么就干什么 而我们之所以不想让玩家动 是因为想通过这个方法减少服务器延时
但我们并没有达到我们的目标 这时候我们就需要引入 Loop + 次数 结构
一样我们还是直接上代码 通过示例解释这样一类结构的运行方式
代码:
- loop 3 times:
- set {_loc1} to location of player
- wait 1 second
- set {_loc2} to location of player
- {_loc1} is not {_loc2}:
- send "传送失败" to player
- stop
- teleport player to {_loc}
- send "传送成功" to player
代码:
- set {_loc1} to location of player # 将 {_loc1} 设置为玩家当前位置
- wait 1 second # 等待 1 秒
- set {_loc2} to location of player # 将 {_loc2} 设置为玩家当前位置
- {_loc1} is not {_loc2}: # 如果 1 秒前玩家的位置 以及 1 秒后玩家的位置不同
- send "传送失败" to player # 发送 "传送失败" 给该玩家
- stop # 强行终止代码进程
我们从 3 秒检查 1 次改为了 1 秒检查一次 并且 在玩家移动位置后 直接强行终止了代码进程
也就是说 如果 1 秒前玩家的位置 以及 1 秒后玩家的位置不同 代码将会立即终止
代码:
- teleport player to {_loc}
- send "传送成功" to player
● Loop + Types(类型) 结构
相信大家都用过一些聊天控制类型的插件 DeluxeChat MiaoChat ChatControl...
很多聊天插件都有 聊天频道 这样的功能 即 玩家所发送的信息 仅会被一部分玩家接收到
那这样的功能又如何在 Skript 实现呢? 我们还是直接上代码
代码:
- on chat:
- player has permission "自定义频道"
- loop all players:
- loop-player has permission "自定义频道":
- send "%message%" to loop-player
Types(类型) 实际上只是 Skript 内置的一些比较常用的数组 这些数组并不需要你手动进行添加或者分类
它们将会在你调用的时候 临时获取 并构成数组 比如这段代码中 "all players" 是所有在线玩家的数组
构成它的是所有 在线玩家(loop-player) 我们通过判断 在线玩家(loop-player) 是否有 "自定义频道" 权限
来实现筛选功能 拥有 "自定义频道" 的玩家将收到该玩家的信息 而没有该权限的玩家将无法收到该玩家信息
当然实现这一功能还有不同的写法 这里只是提供一种思路
————————————————————————————————
● 注册指令
说到现在 我们所有的代码 似乎都是基于监听器进行编写的
我们都需要去触发监听器 才能执行我们的代码 那有没有什么办法可以主动触发我们的代码?
这时候我们就需要引入 MineCraft 插件最核心的功能 指令功能而在 Java 里你可能需要这样注册一个指令
代码:
- @Override
- public boolean onCommand(final CommandSender sender, Command cmd, String label, String[] arg){
- if (cmd.getName().equalsIgnoreCase("自定义指令")){
- 代码段落
- }
- return true;
- }
代码:
- command /自定义指令:
- trigger:
- 代码段
如果我想注册一个 "/我学你马Java" 的命令 你只需这样
代码:
- command /我学你马Java:
- trigger:
- kill player
- send "不许说Java坏话" to player
通过套公式 你可以创造成千上万的指令不成问题 但是实际上我们在使用一个插件的时候
并不是只有 "/..." 结构的指令存在 更多的是 "/... ... ..." 来构成一类指令
那我们又该如何注册这样结构的指令呢? 非常简单 基本格式与上面几乎无异
代码:
- command /自定义指令 [<类型>] [<类型>] ...:
- trigger:
- 代码段
有人问 "类型" 有哪些 其实我也说不全 我常用的有这几种
"text" - 字符类型 什么是字符? 就是字面意思 字词符号
"player" - 在线玩家
"offline player" - 离线玩家
"number" - 数字类型
"integer" - 整数类型
那这些类型又有什么用处呢? 它实际上是限制了你可以输入的参数
比如我创建如下指令
代码:
- command /hello [<player>]:
- trigger:
- 代码段
那么我在执行这个指令的时候必须在这个位置上填上一个在线玩家的名称
同样的如果我把 "[<player>]" 换成 "[<integer>]" 我就需要在这个位置上填写一个整数
如果我填了 "1.2"(小数/浮点数) Skript 就会提示我 填写的参数类型错误
为什么填写参数 那肯定是在代码段内需要使用这些输进来的参数
那么我们在代码段里有该如何调用这些被我们输入进来的参数呢?
比如像是上面这个指令 它只有一个可以填参数的位置 那么在代码段内 它就是 arg-1 即 第一个参数的意思
我们只需要记住核心规则 它排在第几位 在代码段内 它就是 "arg-几"
当然我在这块的了解并不是很深入 为了不把大家带上歪路 这里引用国外 Skript 作者更为详细的指令注册的教程
————————————————————————————————
代码:
- command /<指令名称> <参数>:
- aliases:
- executable by:
- usage:
- description:
- permission:
- permission message:
- cooldown: <冷却时间>
- cooldown message:
- cooldown bypass:
- cooldown storage: <变量>
- trigger:
- 代码段
命令名称基本上是命令 您可以在命令名称中使用任何字符(空格字符除外)
当然如果在命令名称中使用空格字符 那么空格字符后的文本将成为参数
命令名称前的斜杠字符(/)是可选的(但这并不意味着您可以在执行命令时不带斜杠)
参数(可选)
可以通过将参数放在 "[]" 中来使其成为可选参数
类型参数
可以通过使用规定的格式来限制参数的类型 例如: <type = default value>
- 类型为 "text/string" 的参数可以接受任何字符 但 "object" 类型不能用作于参数
- 类型可以是多个 (例如 number -> numbers entity -> entities) 通过这样的方法 可以使参数接受多个值
- "= default value" 这一部分是可选的 如果命令执行者未输入参数 系统将自动使用默认值
- 同样你也可以使用这样的方式设置参数默认值 例如: <item = %player's tool%>
命令示例:
代码:
- command /kill <entity types> [in [the] radius <number = 20>]:
但是如果没有输入数值 系统将自动使用默认值 半径 20
Aliases
子命令 命令的别名 如果需要创建多个子命令 请使用用逗号分隔
示例:(/alias1,alias2,/alias3)
Executable By
指定可以使用该命令的执行者
例如:console(后台) players(玩家) the console and players(后台和玩家)
Usage
使用不正确时 将发送的消息
Description
命令描述 其他插件可以获取/显示此信息
Permission
执行命令所需要的权限
Permission Message
执行者没有权限 提示信息
Cooldown
多长冷却时间后可以再次使用该命令 需要注意的是 关服时所有命令冷却时间将被重置
Cooldown Message
冷却期间 提示信息
Cooldown Bypass
无视冷却时间所需要的权限
Cooldown Storage
存储冷却时间全局变量名称
————————————————————————————————
● Function 第一类结构
众所周知 一个复杂的插件 不免出现相似的代码段
有时候你看着不舒服 想要缩减段落 却又无从下手 这时你就需要 Function 来帮忙了
这里取本人的SUPERGUILDS的一段代码做讲解
代码:
- file "plugins/SUPERGUILDS/%{_fileDir}%.yml" does not exists:
- create file "plugins/SUPERGUILDS/%{_fileDir}%.yml"
- yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml" is not loaded:
- load yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml" as "plugins/SUPERGUILDS/%{_fileDir}%.yml"
- set yaml value "%{_variableName}%" from "plugins/SUPERGUILDS/%{_fileDir}%.yml" to "%{_value}%"
- save yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml"
这段代码的功能是存储数据至yaml文件内 对于一个非常复杂的脚本 数据的存储是必不可少的
同样也会频繁出现在我们的代码中的 难道真的每一次需要存储数据的时候 都需要再去复制粘贴么?
对于一个初学者来说 无可厚非 就是多几行而已 但是对于一个老手来说 复制粘贴不可取
首先对于一个 5000+ 以上的插件来说 你需要关注的东西很多 一个是代码优化 一个是代码可读性
就拿 SUPERGUILDS 来说 7000+ 行 我所有的数据读取和存储都是通过方法完成 单一个数据写入方法
我就使用了 100+ 次(也就是上面我提到的这段代码) 如果我们把它都像上面一样全部展开 我的脚本将立即增加 600+ 行
但是我们在写脚本的时候真正需要的是这些么? 不 我们需要的是效果 是功能 不是数据处理的流程
每次写入数据都需要白白多占 6 行 既不方便后期维护 同时因为要兼顾路径正确与否 浪费很多时间在查错上面 不划算
Function 为你解决了这些难题 方法的注册和指令的注册有共同的地方 比如它们同样需要参数 结构一般为
代码:
- function 方法名(参数名:参数类型, 参数名:参数类型, ...):
- 代码段落
套用公式 我们可以把上面的代码段转换成方法段
代码:
- function SG_writeFile(variableName: text, value: text, fileDir: text):
- file "plugins/SUPERGUILDS/%{_fileDir}%.yml" does not exists:
- create file "plugins/SUPERGUILDS/%{_fileDir}%.yml"
- yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml" is not loaded:
- load yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml" as "plugins/SUPERGUILDS/%{_fileDir}%.yml"
- set yaml value "%{_variableName}%" from "plugins/SUPERGUILDS/%{_fileDir}%.yml" to "%{_value}%"
- save yaml "plugins/SUPERGUILDS/%{_fileDir}%.yml"
代码:
- file "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml" does not exists:
- create file "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml"
- yaml "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml" is not loaded:
- load yaml "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml" as "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml"
- set yaml value "Datas.Username" from "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml" to "**EVER"
- save yaml "plugins/SUPERGUILDS/playerdata/%uuid of player%.yml"
代码:
- SG_writeFile("Datas.Username", "**EVER", "playerdata/%uuid of player%")
通过这样的方法我们大大减少了代码量 提高了开发效率你以为 Function 就结束了?其实不然 作为方法 很多的时候它并没有 "操作" 功能
更多的是 "整理/查找/判断" 功能 那遇到这样的情况我们又该如何灵活使用 Function 以供我们使用呢?
上面我提到了使用 Function 写入了数据 那么有写入肯定需要读取 我们又该如何利用 Function 来缩短我们读取的代码呢?
————————————————————————————————
● Function 的第二类结构
代码:
- function 方法名(参数名:参数类型, 参数名:参数类型, ...) :: 输出参数类型:
- 代码段落
- return 返回值
什么叫输出? 就是代码执行完 得到的一个数值 让这个数值回到主代码段 即 返回一个值
什么叫参数类型 这里举一个例子 我们知道 1+1=2 因为 1 是一个数字 2 也是一个数字 所以才有 数字+数字=数字
那如果我这样写 ①+一 那么又等于什么呢? "①" 和 "一" 在你的思维中 很可能会认为它们仍然是数字 你会觉得答案还是 2
但是执行我们代码的是计算机 并不是 我们 计算机所能识别的数字 只有 阿拉伯数字 "1,2,3..."
而 "①,一..." 计算机只能识别它们一些字符 并不能利用这些字符进行加减运算 就像是现在问你 水 + 苹果 = ?
你或许也没有一个准确的答案. 计算机更是这样
所以这时候就需要参数类型来规范我们运算中的这些值 拿一段来自 SUPERISLANDS 的代码做示例
代码:
- function SI_isSlotAvaliable(s: integer, z: integer) :: boolean:
- set {_m} to {_z} * 9 - 1
- {_s} is not between 0 and {_m}:
- return false
- return true
最终会返回一个参数类型为 boolean("布尔")的值 即 "false/true"
那么实际操作中我们就可以通过这样一段代码对给定的数值进行判断 是否满足条件
如果满足那么返回 "true" 如果不满足 那么就将返回 "false"
需要知道的是 在 Skript 编程里 如果代码已经成功获得的最终值 那么方法代码将立即终止
从返回成功的一行开始 后面所有代码将不再执行 切记
到此 所有基础教程已结束 谢谢大家赏脸看完 全文 11111 字 都是自己的一些干货 点个收藏 给点人气便是对我最大的支持
-2020.2.28
● 更新日志3.30 event-commandsender is "player" 修正为 event-commandsender is player
备注: 同类型变量进行比较 是编写代码的规范 感谢 zide 指出问题"而没有该权限的玩家将无法收到该玩家信息"" 中 "受到" 更变至 "收到" 感谢热心站内用户反馈
本帖最后由 TUCAOEVER 于 2020-3-16 18:46 编辑
欢迎加入 Skript 交流群 906768617
欢迎加入 Skript 交流群 906768617
谢谢楼主,希望能继续更新下去:)
前来支持和学习!

是大佬我死了太硬霸了
加油加油!sk大法好!

特别详细 弄好了就才能够好
skript能否识别物品的data值
592764254 发表于 2020-3-15 18:54
skript能否识别物品的data值
没有理解你的意思?
event-commandsender is "player"实际上是"%event-commandsender%" is "player"的缩写,而这个缩写将一个实体与一个字符串比较会使新人迷惑。建议改成sender is a player。
子德 发表于 2020-3-30 10:14
event-commandsender is "player"实际上是"%event-commandsender%" is "player"的缩写,而这个缩写将一个实 ...
感谢指出 已修改
能否使用for循环
Shepred 发表于 2020-3-31 13:01
能否使用for循环
Skript 里 loop / while 负责循环操作 for 是 Java / C 编程语言的循环 你说有没有 for 那答案是没有 如果你问有没有循环语句 那么答案是有
想问一下写function的时候报错 There's no player in a function event 是什么原因?
北鼻的娘 发表于 2020-4-3 08:48
想问一下写function的时候报错 There's no player in a function event 是什么原因?
function 内部是局部变量 除非输入进玩家参数 否则无法直接调用 player
非常棒的教程,写的很详细,实用,谢谢楼主
写的很棒,谢谢楼主
本帖最后由 52608634 于 2020-8-6 11:30 编辑
怎么设置时间限制来着一定时间内玩家没操作将踢出,就是点击验证的
这种的设置时间限制
怎么设置时间限制来着一定时间内玩家没操作将踢出,就是点击验证的

592764254 发表于 2020-3-15 18:54
skript能否识别物品的data值
當然可以啊
但它不是Java 那種 .getItemMeta()
你可以直接去get他的display name, lore,amount,之類的
要sk交流可以dc 加我哦!茶茶#0760
52608634 发表于 2020-8-6 11:29
怎么设置时间限制来着一定时间内玩家没操作将踢出,就是点击验证的这种的设置时间限制 ...
on join:
//你發出那個聊天
wait 5 seconds
if {%player%.驗證了} is not true:
kick the player due to "你沒有驗證"!
在他按點擊驗證時候記得將這個 {%player%.驗證了} 設置去true
set {%player%.驗證了} to true
本帖最后由 Howie宇宙 于 2021-1-25 13:03 编辑
感谢!教程写的很详细,现在大部分功能都能自己做了~

感谢分享啊
dal6666666666666
MCBBS有你更精彩~
十分感谢大佬给的教程,已经学习上了~
skript-yaml 有没有教程
还有loop的那种教程在哪看
还有loop的那种教程在哪看
收藏了,就是不知道我什么时候开始
66666666666666666666
666666666666666666666666
gzwd2 发表于 2021-6-20 16:41
666666666666666666666666
6666666666666666666
本帖最后由 xuan_tian233 于 2021-8-7 21:41 编辑
作者可以多举点例子吗没看懂
作者可以多举点例子吗没看懂
非常感谢楼主的教程,mcbbs有你更精彩
感谢楼主mcbbs有你更精彩
教程不错呢,支持
可以的!不错
很好很好,每日完成任务!支持了!
不得不说还挺有用的
感谢大佬的分享呀,收益匪浅
建议想隔壁的喵式脚本一样增加模块化编程功能,更有利于萌新腐竹的加入
问一下,我怎么知道玩家的所有权限啊,谢谢
请问一下trigger可以直接在监听下用吗
yuan_su_he 发表于 2022-7-4 22:46
请问一下trigger可以直接在监听下用吗
还有就是如果可以用的话能在trigger前加条件吗
yuan_su_he 发表于 2022-7-4 22:46
请问一下trigger可以直接在监听下用吗
trigger一般是command下面用的 事件监听一般不用写trigger
TUCAOEVER 发表于 2022-7-5 09:16
trigger一般是command下面用的 事件监听一般不用写trigger
我用trigger主要是因为不知道怎么让oraxen和skript联动,所以直接trigger oraxen的指令来给予物品
请问一下能不能做到drop带nbt的标签……如果execute的话无法指定event-player
yuan_su_he 发表于 2022-7-5 20:13
请问一下能不能做到drop带nbt的标签……如果execute的话无法指定event-player
请尽量加群问这些技术方面的问题。。。 要带nbt skbee附属是必要的
对我有点帮助
Function 的第二类结构中的"return true" "return false"有什么实际作用呢?