沙盘引擎 (SEngine)

创意诞生沙盘世界,不止如此想象!

用户工具

站点工具


scripting

📚 脚本开发及API

脚本,在《沙盘引擎》衍生作品的开发流程中,用来实现开发者的设计。目前引擎支持的脚本语言为JavaScript/TypeScript。

《沙盘引擎》框架将脚本开发细分成了两个部分——“世界端(服务端)”和“客户端”

📒 索引

本文档篇幅较长且相当重要,开发之前务必预览至少一次

📒 名词解释:类名

名称 功能&作用
Client/ 客户端类脚本,只能在Client脚本使用
World/ 主世界\服务器类脚本,只能在World脚本使用
游戏内主要玩法都应再次实现
.../Main Client\World端的主要控制代码
Audio 声音、音乐、音频……
Camera 游戏视角、世界相机……
GUI 游戏用户交互、界面UI、FairyGUI、输入输出……
AI 游戏寻路、智能逻辑……
NativeMenu 原生提供的简单UI界面,可通过脚本快速制作UI Demo
Helper 游戏内可能用到的工具类
Random 随机功能类
Socket 网络\套接字相关功能类
SQLite SQLite数据库类
Timer 延迟\计时器类,可延迟\定时执行代码
Tween 渐变\过渡功能类
Entity 世界关键对象的继承父类
Character 世界:角色对象
(人类、僵尸、动物等)
Checkpoint 世界:检查点对象
(赛车检查点、任务触发、商店入口等)
Model 世界:模型物体对象
(动态模型\建模对象)
Pickup 世界:拾取物对象
(物品掉落、漂浮物品、商店入口等)
Player 世界:玩家对象
(服务器内真实玩家的控制类)
Prop 世界:游戏物品对象
(角色手持物品对象)
Vehicle 世界:载具对象
(摩托、汽车、船、飞机等)
Billboard 世界:展示牌对象
(始终面向相机的UI、图片、文本、进度条)
(例如:角色血量、名称等)

📒 前置概念

经过重新设计与版本迭代,沙盘引擎最终保留了“世界端&客户端”二合一的脚本开发方式。

引擎每次加载世界场景时,将加载一个整体组(World.js + Client.js),前者负责实现世界主要逻辑(服务端),后者负责实现客户端逻辑。

模组的所有脚本文件放置在模组目录/Script目录下,并以【脚本组目录】的方式来命名和管理。

注意:《沙盘引擎》默认入口脚本名为“Main”,入口场景默认使用随机端口!

一个正确的【脚本组目录】结构,至少存在World.jsClient.js两个脚本入口文件(World为世界端脚本;Client为客户端脚本)。

进入任意模组时,引擎会默认加载【Main脚本组】,即加载Mod/Script/Main目录下的Client.js和World.js脚本。

 //Client.js中
 function some()
 {
 	//加载一个【地图为testmap,脚本为MyWorld1】的新游戏世界
     CreateHost("testmap", "MyWorld1");
 }

若地图文件及脚本组文件无异常,在执行CreateHost()代码后,引擎将会断开现有的世界和连接,根据所填参数建立【指定地图+逻辑脚本】的新世界场景。

利用世界及脚本组机制,开发者可以实现很多灵活的功能扩展,更多有关内容请访问【世界空间及场景机制】

📘 脚本分配指引

在设计脚本时,做好客户端和世界端的功能分配,合理设计功能API交互解耦,避免出现过度相互依赖、粘合的情况,有助于后续的版本开发和日常维护。

常规情况下,开发者应优先考虑世界端API来实现主要逻辑,需要客户端GUI相关的内容,才在客户端脚本内进行设计。

正确举例:制作一个载具的进度条(或仪表盘),首先在客户端创建一个进度条GUI,然后创建一个更新函数,在客户端Update Event进行调用,直接从客户端获取Vehicle.Speed进行进度条赋值,这样每帧更新且没有卡顿。

错误举例:同上,制作一个GUI进度条,在服务端判断玩家上车后,在帧循环事件中给玩家发送自定义客户端数据(ServerToClientData),参数附带Character.Vehicle.Speed,客户端收到后把参数赋值给进度条Progress。这样虽然能实现相同的数据效果,但是进度条会因网络等原因导致显示不流畅(虽然可以通过Lerp方式处理,但这个例子说明有些功能并不适合服务端脚本开发,甚至应该避免这种快速每帧、定时循环发送客户端数据的行为),并且造成了不必要的网络通讯开销、资源的浪费。

📗 附:程序开发的基本步骤

1.需求分析:确定想要实现的功能,对其进行简单的描述。

例如:想要设计和实现一个赛车的玩法,需要清楚规则是什么,路线是什么,哪些车参赛,人数怎么安排,比赛怎么进行等。

2.设计:根据需求分析,在开发层面进行抽象、分类和归纳,总结出需要存在哪些数据,功能,流程。

例如:设计赛车的路线由一个个点连接组成,参赛者用列表统一管理,参赛车有什么属性、功能,人数限制的功能描述,什么时候在什么地方会发生什么事(流程)等。

3.实现:用具体的代码和环境等来实现以上的设计(也就是编写代码);同样的设计,可以有各种不同的实现。

例如:赛车的路线用Checkpoint(检查点)来实现,参赛者用数组的方式实现存储,以及自定义的各种函数来实现具体的功能等等。

4.调试:开发的最后一步,及时发现异常,确保项目能正常运行。(最好可以进行可行的优化)

例如:从使用者角度,对各个功能进行实际的上手测试(黑盒测试);从开发者角度,对代码可能存在的隐患和边界条件进行实际的上手测试(白盒测试)。

📘 脚本逻辑指引

沙盘引擎的脚本功能设计偏重于【世界端主逻辑】

也就是说,能在World端实现的功能,不要在其他脚本执行,Client端的API较少(也没必要多,因为更多是客户端本地内容,而不是客户端世界内容),主要的世界玩法功能应该在World端体现

例如:开发者希望实现“当角色手中的物体变成了【马桶】时,在此玩家的视角里弹出一个GUI窗口(上面可能写着一些文本或按钮)”

在沙盘引擎的脚本设计理念下,正确做法应该是:在World端当角色手中的武器被改变事件中,判断角色是否为玩家以及角色手中物品是否为【马桶】,然后发送一条自定义网络消息(或使用客户端反射API)告知客户端打开指定的GUI界面。(关闭GUI逻辑亦然,只需判定手中物品从马桶改变为其他)

反例补充:不应该在Client脚本中(根本没有类似的事件)执行这一逻辑判断。绝大多数游戏世界功能都应优先考虑World脚本,而不是Client脚本,避免出现Server&Client双核大脑开发逻辑冲突耦合等问题。

📒 脚本文件

沙盘引擎的所有模组脚本均在模组目录/Script/XXX脚本组目录。

其中入口脚本有两个,分别是World.jsClient.js

脚本名称 说明
World.js 主动入口脚本
负责一切与主世界相关的内容(服务端权威,如世界时间、天气、其他服务端及玩法类逻辑)
Client.js 主动入口脚本
负责一切与本地客户端相关的内容(如GUI、相机视角、本地事件、本地世界事件等)

需要特别注意,因为世界端(服务端)和客户端逻辑同步有很多不同的地方,所以两种脚本的Event、Function并非是通用的。 (两个脚本分别是不同的工作空间)

具体Client、World脚本开发API,请参考脚本开发文档下的子分类,有些API可能很相似,除非完全一样,否则是不能通用的。

除此之外,也有一些原生通用Native的代码API,这种通常是可以在客户端和主世界通用的代码。

📘 加载子脚本

实际开发过程中,特别是针对游戏设定复杂的游戏,显然只有两个脚本文件是不利于开发者便利的。

沙盘引擎脚本允许开发者使用API加载其他脚本文件(或叠加的意思),这样开发者就可以根据模组情况自行分配多个子脚本。

针对设定复杂的游戏模组,分配好合理易懂的脚本分类可以让开发更清晰,帮助开发者提高开发效率。

加载并成功注册子脚本后,实际上相当于保存在同一个开发工作空间,并不代表真正意义上“其他脚本”或“其他类”,只有脚本分割美观的意义。

//伪脚本示例
LoadScript("newscript.js");
LoadScript("Func/Functions.js");
 
//当开发者需要使用时,直接执行函数、常量即可,并不需要以XXX.Function()等方式使用。
//在任何其他脚本(除非只有Client和World入口脚本是两个不同的空间)建立的变量、常量、函数,在子脚本中也正常使用,由此需要留意避免重名的情况。

新建后的子脚本必须放置在Script目录内(也可以放置到新建子目录),放置后的脚本必须通过API进行加载脚本,否则没有作用(但也可以举一反三,通过此机制实现模块化开发)。

📘 脚本加密

在沙盘引擎模组开发中,无论是客户端还是世界端脚本,都将在模组打包时被附带到模组目录中,因此模组开发源代码、脚本是透明的

由于JavaScript脚本的透明性,如果不希望别人查看自己的脚本,可以通过网络上的混淆、加密工具进行加密,但是【务必确保你手里有源文件的备份,否则可能是不可逆的】,建议只在确认打包发布之前进行一次混淆,本地测试阶段并不需要混淆和加密。

注意:混淆工具并不是完全不可逆的,只能起到加密和阻止完全修改的作用。

网络联机情况下,会自动验证加入客户端的脚本同步性,避免客户端脚本被修改后的错误同步或作弊(如果与服务端下的Client.js最终无法同步,将会被禁止连接)。

出于脚本和资源的开放和部分透明性,《沙盘引擎》官方将尽可能保护原创模组的版权,对于受到盗版争议、未经允许或恶意破解修改已有模组的内容(非法修改版模组),引擎官方将可能在合理范围内对问题模组进行屏蔽阻断和下架处理。

JavaScript在线加密工具:https://obfuscator.io/

📘 脚本加密延伸:模组定位

开发者在建立模组初期就应该对“自己模组”进行一个属性定位,究竟要作为一个衍生游戏还是一个开放性联机游戏

例如《战地1942》与《战地2》,以及和《战地5》进行一个对比,就可以解释这件事情。

《战地1942&战地2》是默认在游戏本体就支持玩家自定义开服(局域网、互联网),这就相当于一个“开放性联机游戏”,在原有玩法的基础上,玩家仍然可以通过修改数据等方式实现游戏的二次创作

反之

《战地5》游戏本体没有任何“可以直接开服并修改”的内容,也就是说玩家只能体验开发者允许玩家体验的内容,这就是传统意义上的“衍生游戏”,玩家自然也无法通过常规手段对游戏进行二次创作。

关于此定位完全决定模组发布时是否要进行指定脚本的加密。

结论:如果制作“衍生游戏”,则建议发布时加密全部脚本;如果制作的是“开放性联机游戏”,则建议只加密Client.js等客户端脚本,World.js服务端脚本可留给玩家进行二次创作。

注意:通常来讲,World.js服务端脚本包含着游戏玩法的核心代码,如果公开可能会造成修改、魔改版本,甚至脚本改编成新模组的情况出现,开发者在发布时应考虑周全,或者应该在模组根目录写入许可说明https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository#disclaimer)。

📘 服务端子脚本(概念阶段)

注意:此功能仍处于概念阶段,仅供引擎开发计划参考和逻辑扩展,不具有实现功能

通过上文的解释,我们可以知道衍生游戏主要依靠Client.js和World.js脚本组成最终游戏。

但是在某些情况下,可能衍生游戏的开发组仍然希望“衍生游戏用户”可以自定义服务器功能脚本,但又不希望用户直接修改Client.js或World.js脚本(或以上两个脚本发布时已经加密混淆),这就有了服务端子脚本的概念,相当于在原有World.js的基础上,增加了一个与其相同作用的Server.js

因为模组开发者已经将Client\World脚本加密,所以模组用户自然无法直接修改这些内容,但是可以通过子脚本实现和World.js几乎相同的功能(对于部分功能,可能会有削减),也应该支持World.js内的一些自定义公开API。

补充:此功能虽然未在《沙盘引擎》版本中官方支持实现,但开发者仍然可考虑通过【子脚本+自定义函数】的方式实现此功能。

📘 开发思维扩展

通过了解,沙盘引擎两个主要入口脚本Client.jsWorld.js分别有各自的激活场景和作用。

这其实也可以扩展出一些其他思维,比如你有版权或用户隐私方面的顾虑或其他打算,你完全可以进行C\S客户端和服务端的分别开发和打包。

也就是说,客户端如果没有“建立主机服务器”的需求,完全可以将World.js留空或删除,实现网游化的开发。

比如可能开发者并不想让任何人都可架设服务器,那么就只需将World.js连同工程单独拷贝出来一份,很简单的形成了单独的“服务端”。

因为届时开发者发布给玩家的模组本体不包含World.js(服务端脚本),所以玩家无法建服(客户端也不应该有开启服务器的UI接口),只能进行连接,而连接是不需要World.js的。

此开发思路只是针对某些特殊玩法或网游服务器需求情况,如果是常规好友派对类型游戏情况下,还是建议将建立主机的能力也附带给玩家。

简单举例:《我的世界》和《战地5》的区别;一个是任何人可以直接本地开启服务端游玩,另一个是只有官方拥有服务器,或者提供租赁服务。

总结:如果作为客户端连接其他服务器时,其实可以不用存在World端脚本,只有Client脚本才是必须的。World端更多是在有【创建本地服务器\主机游戏】需求时才必须存在的。

📘 开放性利弊分析

由于Javascript本身语言的透明性,无法进行二进制等真正意义的编译打包,尽管上面介绍了混淆加密工具的方式,但仍然会有小概率可能会被修改。

无论如何,进行混淆和加密还是很有必要的,否则有可能会出现一些预期之外的情况。

比如没有进行任何混淆和加密,可能脚本会被其他人参考(在某些情况下,这是有助于学习的),但也可能会被他人进行违背作者初衷的魔改。

举例:开发者A制作了解谜小游戏,开发者B直接修改其未加密的代码,添加、开放了许多达到作弊效果的功能和指令,破坏了模组作者设计的初衷。

考虑到类似这样的情况,尽管官方会按照初版发布时间大众意愿评定来进行区分和保护正版模组,但如果开发者抛开混淆和加密仍然有困扰,可以考虑开发思维扩展的方式,综合判定是否要进行“独立服务端”的开发方式(因为世界服务端基本控制着玩法的核心,客户端只有Client.js是只有基础的功能的)。

📘 联机脚本同步匹配

由于脚本在Native的支持下会有很多“危险”的指令(如引擎内文件操作、数据库操作等),这有可能会产生部分恶意脚本影响正常游戏。

无论是作为客户端玩家还是服务端“开服者”,都不建议使用完全未知且不可信任的第三方脚本,除非你知道自己在做什么。

当玩家作为“服务端主机“开启联机服务器时(单机游戏、本地游戏除外),服务器会在每次玩家加入时验证玩家Client.js的脚本是否与服务端同步,如果出现文件MD5等数值不匹配,则被认定为文件不同(哪怕只有微不足道的改动),服务端将会以Client.World.ConnectResult == 5(版本、协议不匹配)拒绝玩家连接到服务器

换句话说,无论是玩家或者服务端主机还是出于版权保护,都不应该修改Client.js文件(通常也是被模组开发者加密混淆)。

📒 客户端(Client)

客户端的操作范围:一切适合在客户端做的事情(如本地数据及复杂运算),以及只为玩家“本地”出现的内容。

客户端的内容是默认保存在本地的,且运算代码也将在本地进行,所以不用担心网络延迟等问题(但要适当考虑漏洞,避免利用本地客户端作弊)。

客户端代码除了允许的世界Event(例如onTimeChange、onCharacterAction)外,还包括一些世界端没有的独有逻辑(例如:打开客户端的某个内置界面,也可以通过自定义网络数据以实现服务端命令的类似效果)。

📒 世界端(World \ Server)

服务端的操作范围:一切需要安全保存的事情(如玩家血量、经验值等),以及需要网络同步的内容(例如一个操作需要让全部玩家都同步看到的)。

服务端的内容是仅保存在“主机玩家”设备中,运算代码都将在“主机玩家”设备进行,并且通常会在运算处理结束后同步给其他玩家,这就造成了无论是多小的运算,都会有网络延迟和影响,不建议频繁运算的功能使用服务端来进行(例如:线性改变玩家视角,虽然服务端API可以做到,但是应该用客户端API来做,因为每次服务端发送到玩家“改变视角”数据是至少需要时间的,看起来会不平滑)。

世界端严格意义上来讲可以被称为“服务端”,但是由于它只在World场景加载后执行,所以也被习惯性称之为世界端。

世界端代码包含大量与世界可见内容息息相关的部分,通常想调整世界内玩法相关的内容,均需要在世界端进行处理。

世界端脚本只在主机玩家(或单人游戏本地玩家)执行,所以是服务器权威(网络同步概念)。

📒 脚本框架及来源

沙盘引擎游戏本体是基于Unity进行开发的,引擎中动态编译解析JavaScript脚本的功能来源于PuerTS框架(致敬感谢)。

尽管引擎脚本是以JavaScript作为编译基础的,但引擎代码的设计思维来自于松鼠语言脚本(另外一个脚本语言)。

如果开发者曾了解过SA-MPVC-MP 0.4,那么在沙盘引擎内你可能会有一些“亲切感”,并且会对沙盘引擎的API代码设计更快速的理解入门。

《沙盘引擎》部分代码开发者曾在VC-MP环境开发过许多内容,所以吸取借鉴了部分代码开发思路(如Event和Function)。

📒 脚本API文档指南

有关脚本API文档将被分成3个主要部分,分别是原生及通用代码客户端代码Client世界端代码World

公开范围内所有的Event(事件)、Property(属性)、Function(方法)等代码都将在以上三个文档内记录呈现,部分没有被详细介绍的API开发者可自行探索,技多不压身。

文档中部分代码(类)实际上可能是静态类名,这里需要根据API文档内【代码首字母大小写】来进行区分(如Timer是一个静态类,而timer则是一个Timer生成的实例)。

Timer.Create() [√正确]
timer.Create() [×错误,Create是静态方法]
 
Timer.Repeat == 0 [×错误,Repeat是成员属性,而非类型属性]
timer.Repeat == 0 [√正确]

为了更好的理解API文档的内容和信息,下方介绍API文档内的一些文档规范和名词解释。

数据类型 说明
int 整数(50)
float 浮点小数(1.0f或1.1234)
double 双精度浮点小数(1.00000001)
string 字符串("文本")
char 单个字符
bool(boolean) 布尔逻辑型(真、假)
any 非硬性检查的类型,会根据具体逻辑进行装箱和拆箱
虽然没有固定的限制,但是开发者有必要确保传递数据类型合法
//在脚本API文档中,可能会出现如下的案例
 
//如果是此类型情况,可视性比较高,所以可能不会标注player具体是什么类型
//因为大概率可以直接看出它是一个player类型
function OnPlayerJoin( player )
 
//如果是此类型情况,对参数名的定义相对模糊,可能会标注告知开发者具体类型(以TypeScript类型方式)
function OnPlayerChat( player, text: string ) //参数名后面": "跟随的就是此参数类型,表示text是一个字符串类型
 
//函数API介绍同理
World.SetPassword( pass: string ) //表示参数pass需要是一个字符串类型
 
//如果是此类型情况,表示此Event、Function拥有返回值(或要求返回值)
function OnPlayerEnterVehicle( player, vehicle, seat ): int //表示需要提供一个int类型的返回值,这可能会影响此Event的功能(拦截或忽略)
{
 
}
 
World.GetPassword(): string //表示返回一个字符串类型的结果
 
//如果是此类情况,表示某些参数是可空或默认参考
World.CreateMapMarker(icon: int, pos: Vector, target: SE.Player = null); //target参数表示可以为空,默认是null

📘 脚本API文档标题

由于引擎主要功能是由World、Client两个主类下的众多子类实现控制的,所以脚本文档采用了【XXX 类参考】的命名方式。

也就是说,例如你想查找一个关于世界端下的玩家方法,那么就应该前往【世界脚本——Player 类参考】进行查找。

除此之外,每个类型参考文档内拥有多个H1、H2标题进行分类,通常会按照以下命名进行区分。

名称 说明
Event 事件入口分类
Static 静态功能分类
只能直接使用类名调用,如Character.Create()注意大小写区分)
Property 属性分类
通常为创建实例化类型后才可使用的属性数据
(例如Character.Pos是不能直接使用的,应该是var newChara = Character.Create(XXX);生成后newChara.Pos来使用)
Function 功能函数分类
通常为创建实例化类型后才可使用的功能函数
(例如Character.Disarm()是不能直接使用的,应该是var newChara = Character.Create(XXX);生成后newChara.Disarm()来使用)

📒 脚本编写及编译

沙盘引擎使用JavaSciprt作为开发脚本语言,所以开发者可通过任何文本编辑器进行脚本编写

脚本开发时并不需要专门的IDE以及编译器,只需要进行增删查改及修改后的保存“文本”即可,待下次引擎载入脚本时将会自动编译

📒 脚本开发基础入门

注意:此处指的基础入门不是JavaScript语言的基础教程,而是指引已有编程逻辑基础的开发者,快速进行了解脚本代码的一些差异和规范。

如果你是一位编程初学者或JavaScript初学者,可以考虑参考以下视频教程。

四十分钟JavaScript快速入门:https://www.bilibili.com/video/BV15L4y1a7or

哔哩哔哩JavaScript入门教程搜索:https://search.bilibili.com/all?keyword=javascript

《沙盘引擎》的开发初衷就是为了精简开发流程以及代码的复杂程度,所以实际上对引擎模组脚本的基础开发并不需要非常过硬的编程技术

如果开发者曾对PawnSquirrel其他高级编程语言有相关经验,甚至并不需要完全学习JavaScript,只需掌握基础的语法使用即可尝试使用。

📘 事件和函数

事件(Event):表示某件事情开始发生,客户端和世界端可能有不同的事件(也有部分是几乎一样的,可通用)

函数(Function):执行一段引擎内置的代码,以来实现某个功能(例如PlaySound API在某处执行播放声音的功能)

//此处表示事件:当玩家加入到服务器
function OnPlayerJoin( player )
{
	//以下是“事件”下的“函数”
	DLog(player.Name + " joined."); //DLog是一个引擎下内置的方法(SE.DLog)
    Message(player.Name + " joined server."); //Message就是一个World.Message函数
}

📘 类名区分及使用

在沙盘引擎的函数方法设计中,都是以Class类的方式来实现的,也就是无论任何功能调用,均是从某个Class类来激活使用的。

由此得知,绝大多数情况下类名的前缀是必须的。(除World.js下的SE.World分类等例子,因为本来就是在类下)

//World.js
 
//设置世界天气
World.SetWeather(0); //标准形式,但是会多打几个字
SetWeather(0); //可以省略World,因为本身就在SE.World类下
 
//新建一个载具
Vehicle.Create(0, Vector(0, 0, 0)); //标准形式,注意(开头大写)Vehicle是类名,而不是vehicle(引用变量等)
vehicle.Create(0, Vector(0, 0, 0)); //错误,因为vehicle不是一个合法的名字,它可能是一个变量或其他任何内容
Create(0, Vector(0, 0, 0)); //错误,很明显这从可读性来讲都不通,因为没有指定“谁”来进行“Create”
 
//修改载具的生命值
let vehicle = Vehicle.Find(100);
if(vehicle) vehicle.Health = 500; //正确,由Vehicle类进行搜索某个索引的载具,并赋值给vehicle变量,然后进行修改
 
Vehicle.Heatlh = 500; //错误,因为Vehicle是一个引擎类(SE.Vehicle),它本身没有任何关于Health的静态方法\属性(只有实例化后才有)
 
//输出一段内容
World.DLog("Hello World!"); //错误,因为DLog并不在World类下
SEngine.DLog("Hello World!"); //标准形式,正确
DLog("Hello World!"); //正确,因为DLog是一个“原生通用代码”(详情参考文档:脚本开发——原生通用代码),此类代码可不写“类前缀”

如果你能比较清晰的理解以上的代码示例,那么你在开发引擎脚本方面就没有太大问题啦!

📘 默认类空间

关于脚本编写时“类名的使用”在上方已经介绍过,实际上并不需要将此问题考虑过于复杂。

为了脚本开发时思路和可视性更清晰,所以引擎内采用了优先必要时类名前缀的调用方式,但实际上某些类是存在“默认工作空间”的,这种情况下是不需要进行前缀调用的(虽然也可以写,但是可以忽略更精简一些)。

脚本环境 默认类 说明
Client.js SE.Client 因为Client类本身就基于SE.Client下
所以在Client.js内可以省略Client.XXXX,而直接使用XXXX()
World.js SE.World 因为World类本身就基于SE.World下
所以在World.js内可以省略World.XXXX,而直接使用XXXX()
通用原生 SE.Native 在SE.Native下的类成员可直接使用
例如:DLog()Vector(0, 0, 0)等均属于原生通用内容,不需要任何前缀
详情参考可见脚本开发——原生通用代码

注意:Client.jsWorld.js是完全不同的两个类,即使有些内容(如Sound类)命名相同,但不代表在底层是基于同一个类。

例如:Sound类看起来名称一样,实际上在底层的表现可能为SE.Client.SoundSE.World.Sound,而不是真正意义上的SE.Sound(这只是一种说明方式)。

这也就导致尽管类名相同,但在Client和World环境下具体用法可能有差异(因为根本严格意义上不是一个类),有些功能是客户端专用,有些是世界端专用,也有部分是通用的功能。

📒 VSCode声明文件

在实际代码开发过程中,有些开发者可能更喜欢有自动补全的JavaScript编写体验,这可以使用【VSCode编辑器+TS声明文件】的方式来实现。

例如:输入Chara时,编辑器会自动帮助补全Character,以及相关参数、返回值的类型和说明。

声明文件下载:https://gitcode.net/Bibiboxs/SandtableEngine/-/blob/master/Sample/index.d.ts

使用方法:放置在要编写的模组根目录,使用VSCode以模组目录作为工作空间即可自动识别。

注意事项:声明文件集合了全部已知的API、属性等自动补全功能,同时支持World\Client两个脚本空间,开发者在补全使用时需要根据注释区分World\Client类型归属(例如:有些代码两个脚本可能有相似之处,但可能参数\返回值有差异,不能完全兼容使用)。

scripting.txt · 最后更改: 2024/06/13 13:58 由 bibiboxs