meta data for this page
📚 脚本&框架
脚本文件&引擎框架作为模组逻辑的控制单元,完全控制着模组何时、如何执行某些行为。
📒 逻辑入口
模组的所有逻辑都由代码控制组成,而代码(由英文、字符组成)则被编写到一个文本文档(*.js
格式)。
这个“文本文档”即是我们所说的“脚本”(包含代码的文本)。
常规情况下,编程与我们的人脑思维非常相似,就好像在阅读某篇文章时,均是采用由前到后、由上到下的阅读方式,阅读每一行文本的内容,并同时尝试理解它的含义。
沙盘引擎 模组运行第一步(逻辑入口)即在脚本目录(Script/Main
),这里包含着模组的初始代码。
也就是说,模组加载完成后,即在内部读取Script/Main
下的脚本文件,然后“一行一行”的阅读并执行脚本所写的代码内容。
想想一下,您曾经玩过的其他游戏,无非都是按顺序执行的N个过程。
例如:1. 展示Logo 2. 进入主菜单 3. 弹出欢迎画面 4. XXX 5. XXX ...
游戏的开发过程即是如此:将N个简单的代码按顺序拼凑起来,最终组成复杂的玩法。
📒 初识脚本
让我们大胆一点!已知无论如何模组会先从Script/Main
读取脚本,那我们就打开这个脚本文件夹。
此时通常应该会看到至少1~2个文件(文件扩展名为*.js
,表示JavaScript
脚本文件)。
以常规(示例)模组为例,我们可能会看到World.js
和Client.js
两个文件,如果文件不存在,也可以手动创建文件(注意扩展名)。
沙盘引擎 是World(Server)+Client
同步的脚本编写模式,也就是服务端脚本、客户端脚本同时存在的逻辑模式。
在不同脚本文件编写的代码(及API规范)有所不同,用途也大不相同。
脚本 | 说明 | 用途示例 |
---|---|---|
World.js | 世界脚本(服务端) 玩法主要编写在此处,会自动多人同步 | 控制世界天气、生成角色、生成载具等 |
Client.js | 客户端脚本(本地端) 主要编写仅针对本地的玩法,不会自动多人同步 | 控制游戏相机镜头、本地UI显示等 |
为什么要将脚本分成两份?
沙盘引擎 是原生支持多人联机的玩法创作,因此在脚本编写和设计上并非仅考虑本地形式。
世界脚本 [World]
用于编写主要玩法逻辑,在实际游戏过程中,只有主机\服务端玩家会运行此脚本,普通玩家(后续加入的玩家)不会运行世界脚本。
客户端脚本 [Client]
用于编写本地辅助逻辑,在实际游戏过程中,无论是主机\服务端还是普通玩家都会运行客户端脚本。
由此得知,世界脚本更像是一个控制全局的关键,而客户端脚本主要是为玩家本地可见的内容做辅助。
简单总结:编写 世界脚本 时应站在全局(所有人)的角度,而编写 客户端脚本 时应站在自身的角度。
📒 脚本构成
脚本文件(xxx.js
)意义上仍然属于文本文档,其特殊的后缀*.js
以供 沙盘引擎 识别为脚本文件,并由引擎识别其内部的代码文本含义。
因此,编写模组脚本起点非常简单,最简单至用记事本等工具即可开始编写。
让我们先从脚本的结构入手,用任何代码或文本工具打开Script/Main/World.js
脚本文件。
不同的模组脚本内容均不相同,本文将以部分脚本代码解释意义,抛砖引玉。
📘 事件 (Event)
此类样式的代码,通常表示监听事件,也就是当某情况发生时代码应该做些什么。
根据代码相关的命名规范,通常以OnXXX
为开头的“方法”代码,均被认为是事件函数,这多数是 沙盘引擎 内置的,当然也可以自行编写。
function OnScriptLoad() { }
解释:function
是一个保留关键字,表示在脚本中声明一个方法体,其后面的"签名"OnScriptLoad
是关键部分,不同的“签名”表示不同的含义,而方法体中{...}
即可编写相关代码,表示此方法触发后将执行的内容。
📘 方法 (Function)
此类样式的代码,通常表示执行方法,也就是调用代码API做一些事情。
World.Core.SetHostName("Test Server");
解释:World.Core.SetHostName
是 沙盘引擎 内置的方法API,括号内表示方法的“参数”,不同的方法有不同的参数,具体需要参考脚本API。
📘 属性 (Property)
此类样式的代码,通常表示获取或修改某属性,也就是调用代码API修改一些内容。
World.Core.Player.Find(0).Name = "Hello";
解释:World.Core.Player.Find(0)
是 沙盘引擎 内置的方法API,此代码用于获取ID为0
的玩家对象,关键在于后面的.Name = "Hello";
,表示将其玩家昵称修改为Hello
。
Name
是玩家对象的一个属性,在玩家对象后.Name
,即为访问此属性,需要注意的是,并非所有属性均可写,部分为只读属性,例如Player.FPS
,显而易见的,玩家的帧数不可能被我们修改,这取决于玩家的帧数限制&设备配置。
📒 基础探索
通过上述的脚本构成介绍,我们了解 沙盘引擎 通常依靠这几种关键代码形式来实现功能。
对于初学者来说,看起来有些复杂难懂吗?
不要灰心,只需要将代码理解成特定形式的编写格式,特殊的符号(点、空格、括号、双引号)有着不同的含义。
由浅入深,可以先适应代码的编写样式,逐一根据单词的意思理解其用途和含义。
让我们继续探索一段相对完整的代码,例如它编写在World.js
脚本文件中:
function OnScriptLoad() { World.Core.SetHostName("Test Server"); World.Core.SetWorldWeather(6); World.Core.SetWorldTime(22, 00); }
这段代码主要是一个事件方法,其签名为OnScriptLoad
(当,脚本,加载),顾名思义,也就是当脚本被加载后要做些什么。
根据JavaScript
代码编写要求,我们可以将要执行的代码写在方法体内(也就是{...}
两个大括号之间,不限长度)。
在示例代码的方法体中,我们看到了许多以World.Core
作为开头的前缀,这表示执行某个代码类下的子方法。
什么是类的子方法?
首先,前缀的World
表示是世界脚本(World.js
)下支持的代码,因此要以World
作为前缀,反之Client
作为客户端脚本也是一样。
其次,随着代码功能数量的增加(可能成千上万),我们需要系统的整理和快速定位某个方法,相当于是一个书架&书签的功能。
例如,当需要设置世界主要内容时,就使用World.Core
(世界,核心)下的代码内容。
抛砖引玉,假设需要设置玩家相关内容时,自然就使用World.Player
(世界,玩家)下的代码内容。
结合以上的解释,我们稍微试着一字一句的阅读一遍代码,就能理解此代码的如下含义:
- 当世界脚本加载后执行
World.Core.SetHostName()
设置服务器名称为Test Server
World.Core.SetWorldTime()
设置世界时间为22, 00
(晚上十点)
📒 深入探索
当我们完整阅读并理解上述代码,距离学会脚本已经成功一大步了!
尽管正式模组开发的过程并非如此简单,但游戏玩法的制作就是一项多个简单组成一个复杂的工作,让我们进一步学习看看!
function OnScriptLoad() { World.Core.SetWorldTime(12, 00); } function OnPlayerJoin( player ) { World.Core.SetWorldTime(20, 00); World.Core.Message("Player: " + player.Name + " joined!"); }
这段代码包含两个事件方法,根据代码的样式,我们大致可以看出这是完整且独立的两个结构。
两个方法的签名(方法名)、参数(括号里的内容)、方法体代码均有所不同,因此至少说明这段代码可以在2个条件触发下执行一些事情。
首先,最上方的OnScriptLoad
我们已经熟悉了,表示脚本加载后做的事情,这里的方法体编写了World.Core.SetWorldTime(12, 00);
,表示将世界时间调整为12点。
至此,方法OnScriptLoad
的内容到}
就执行完毕了,编程执行的逻辑顺序和我们想的一样,随后它会继续向下阅读并执行。
接下来,继续执行到OnPlayerJoin
(当,玩家,加入),在括号中还有一个名为player
的参数(一种代码编写形式),我们可以理解为它是一个资料袋,里面装着关于这个参数的相关属性和功能。
那么考虑到此方法及参数的命名,我们能够直接理解这是一个关于“玩家”的事件及附带参数。
向下继续阅读,方法体编写了World.Core.SetWorldTime(20, 00);
,表示将世界时间调整为20点。
再向下一行,方法体编写了World.Core.Message("Player: " + player.Name + " joined!");
,表示发送一条公屏信息,内容是"Player: " + player.Name + " joined!"
,也就是:“玩家:玩家名称 加入了!”(此处的[玩家名称]会被动态识别为玩家名称)。
通过以上的案例,也许会有疑问:为什么此处可以识别到玩家的名称?
World.Core.Message()
是一个发送公屏消息的方法,在括号(参数)内我们需要填写要发送的内容。
而代码中的"Player: " + player.Name + " joined!"
实际上是三个不同的内容,最终组合为一段话。
分别是:"Player: "
+ player.Name
(这里是代码属性 [Property]
) + " joined!"
。
其中的player.Name
即是引用自方法参数的player
,也就是获取player.Name
(玩家,名称)。
通过这段代码的案例,我们知道代码的编写是非常自由的,只要确保格式的规范正确,可以几乎无限制的编写。
📒 更多实践
沙盘引擎 在设计时尽可能将复杂的代码进行封装和简化,因此许多功能只需简单几行代码即可调用实现。
更多情况下,需要的是开发者扩展的思维,以及在何时、何处编写什么样的代码,以及用多个相关的代码最终组成想要实现的功能。
JavaScript
是一个比较容易上手的编程语言,在正式模组开发之前,非常建议至少掌握任意一门编程语言的基础语法知识。
必要掌握的知识:数据类型、语法格式、if分支语句、方法及参数、变量、数组等。
在上述内容中,我们已经大致学习了语法、格式、方法等基础知识。
在后续的学习和编写过程中,会逐渐遇到更多的编程知识,建议至少掌握上述必要知识在后再继续向下实践。
通常情况下,0基础学习至上述要求的必要知识进度,正常学习时间1~2小时即可掌握。
📒 重载和调试
当模组内的脚本等文件发生更改(保存)后,引擎并不会直接更新最新的逻辑,而是会在下次模组重载时自动更新。
此过程手动触发也非常简单,通常在模组\脚本修改完成后,可以点击或使用重连按钮(F12)快速重新建立连接,已达到更新模组的作用。
每次重连(重载)后,模组即会使用最新的模组内配置及脚本。
在少数情况下,如果系统文件更新有延迟,也可以使用Shift+F12
强制完整更新并重连,但通常无需此操作。
📒 参考API文档
在实际脚本开发的过程中,代码API几乎贯彻着开发全局,因此我们精心整理了脚本开发及API文档。
对于游戏中可能遇到的各类功能、事件、属性,均在文档中有所说明。
当开发准备就绪时,您至少应该阅读一遍基础相关API,再进行后续的开发工作。
📒 进阶脚本工具
在实际脚本开发的过程中,开发者可能更喜欢有代码补全的智能编写体验,沙盘引擎 为此做了充足的支持工作。
例如:编写输入 Chara
时,编辑器会自动帮助补全 Character
,以及相关参数、属性、返回值的类型和说明。
沙盘引擎 推荐使用VSCode进行脚本编写,并且对此编辑器的支持更加完善。
📘 安装步骤
- 启动 沙盘引擎,加载想要编写的模组
- 启动游戏控制台(按键
[~]
,需要在游戏设置里开启控制台) - 控制台输入
updatesdk
,为当前已加载的模组更新SDK(VSCode开发工具) - 正常情况下,关于VSCode的辅助开发文件(代码提示文件)已经安装到模组目录
- 模组目录下应该存在
SEngine.code-workspace | SEngine.d.ts | jsconfig.json
相关文件 - 通常可以打开
SEngine.code-workspace
快速进入VSCode模组工作区,或者正常使用VSCode加载模组文件夹 - 享受代码自动补全、内置事件补全等功能