meta data for this page
  •  

📚 差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录前一修订版
scripting [2025/04/30 02:57] bibiboxsscripting [2025/07/25 18:03] (当前版本) bibiboxs
行 2: 行 2:
 # 脚本开发及API # 脚本开发及API
  
-本,在《沙盘引擎》衍生作品的开发流程中,用来实现开发者的设计。目引擎支持的脚本语言为`JavaScript`(TypeScript在计划中)。+文档篇幅较长且相当重要,开发务必预览**至少一次**! 
  
-《沙盘引擎》框架将脚本开发细分成了两个部分——==**“世界端(服务端)”和“客户端”**==。 
  
 ## 索引 ## 索引
行 11: 行 11:
 {{indexmenu>:scripting|js}} {{indexmenu>:scripting|js}}
 </script> </script>
- 
-> 本文档篇幅较长且相当重要,开发之前务必预览**至少一次**! 
- 
- 
- 
-## 名词解释:类名 
- 
-| 名称           | 功能&作用                                                    | 
-| -------------- | ------------------------------------------------------------ | 
-| **`Client/`**  | 客户端类脚本,只能在`Client`脚本使用                         | 
-| **`World/`**   | 主世界\服务器类脚本,只能在`World`脚本使用<br />==**游戏内主要玩法都应再次实现**== | 
-| **`.../Main`** | **`Client\World`端的主要控制代码**                           | 
-|                |                                                              | 
-| `Audio`        | 声音、音乐、音频……                                           | 
-| `Camera`       | 游戏视角、世界相机……                                         | 
-| `GUI`          | 游戏用户交互、界面UI、FairyGUI、输入输出……                   | 
-|                |                                                              | 
-| `AI`           | 游戏寻路、智能逻辑……                                         | 
-| `NativeMenu`   | 原生提供的简单UI界面,可通过脚本快速制作UI Demo              | 
-|                |                                                              | 
-| `Helper`       | 游戏内可能用到的工具类                                       | 
-| `Random`       | 随机功能类                                                   | 
-| `Socket`       | 网络\套接字相关功能类                                        | 
-| `SQLite`       | SQLite数据库类                                               | 
-| `Timer`        | 延迟\计时器类,可延迟\定时执行代码                           | 
-| `Tween`        | 渐变\过渡功能类                                              | 
-|                |                                                              | 
-| `Entity`       | **==世界关键对象==的继承父类**                               | 
-| `Character`    | ==世界:角色对象==<br />(人类、僵尸、动物等)               | 
-| `Checkpoint`   | ==世界:检查点对象==<br />(赛车检查点、任务触发、商店入口等) | 
-| `Model`        | ==世界:模型物体对象==<br />(动态模型\建模对象)            | 
-| `Pickup`       | ==世界:拾取物对象==<br />(物品掉落、漂浮物品、商店入口等) | 
-| `Player`       | ==世界:玩家对象==<br />**(服务器内真实玩家的控制类)**     | 
-| `Prop`         | ==世界:游戏物品对象==<br />(角色手持物品对象)             | 
-| `Vehicle`      | ==世界:载具对象==<br />(摩托、汽车、船、飞机等)           | 
-|                |                                                              | 
-| `Billboard`    | 世界:展示牌对象<br />(始终面向相机的UI、图片、文本、进度条)<br />(例如:角色血量、名称等) | 
  
  
行 53: 行 16:
 ## 前置概念 ## 前置概念
  
-经过重新设计与版本迭代,沙盘引擎最终保留了`“世界端&客户端”二合一`的脚本开发方式。 +沙盘引擎 脚本采用**世界脚本&客户端脚本”二合一**的开发方式。
- +
-**引擎每次加载世界场景时,将加载一个整体组(World.js + Client.js)**,前者负责实现世界主要逻辑(服务端),后者负责实现客户端逻辑。 +
- +
-模组的所有脚本文件放置在`模组目录/Script`目录下,并以**【脚本组目录】**的方式来命名和管理 +
- +
-> **==注意:《沙盘引擎》默认入口脚本名为“Main”,入口场景默认使用随机端口!==**。 +
- +
-一个正确的【脚本组目录】结构,至少存在`World.js`和`Client.js`两个脚本==入口文件==(World为世界端脚本;Client为客户端脚本)。 +
- +
-> 进入任意模组时,引擎会默认加载【Main脚本组】,即加载`Mod/Script/Main`目录下的Client.js和World.js脚本。 +
- +
- ```javascript +
- //Client.js中 +
- function some() +
- { +
-  //加载一个【地图为testmap,脚本为MyWorld1】的新游戏世界 +
-     CreateHost("testmap", "MyWorld1"); +
- } +
- ```+
  
-若地图文件及脚本组文件无异常,在执行`CreateHost()`代码后引擎将会断开有的世界和连接根据所填参数建立【指定地图+逻辑脚本】的新世界场景+引擎每次**加载世界场景**时,将加载一个整体**脚本组**(`World.js + Client.js`前者负责实现世界主要逻辑(服务端)后者负责实现客户端逻辑。
  
-利用世界及脚本组机制,开发者可以实现很多灵活功能扩展更多有关内容请访问[【世界空间及场景机制】](developer/mod/scene)+> 模组加载完成后,引擎会默认加载`Main`脚本组脚本即加载`Mod/Script/Main`目录下的`Client.js`和`World.js`脚本
  
  
行 82: 行 26:
 ### 脚本分配指引 ### 脚本分配指引
  
-在设计脚本时,**做好客户端和世界端的功能分配**,合理设计功能API交互解耦,避免出现**过度相互依赖、粘合**的情况,有助于后续的版本开发和日常维护。+在设计脚本时,应该做好**世界脚本及客户端脚本**的功能责任分配,避免出现**过度相互依赖**的情况,有助于后续的版本开发和日常维护。
  
-常规情况下,开发者**优先考虑世界API来实现主要逻辑**,需要客户端GUI相关的内容,才在客户端脚本进行设计。+常规情况下,应优先考虑**世界脚本API**来实现主要逻辑,需要**客户端本地**相关的内容,才在**客户端脚本**进行设计和实现
  
-> **正确举例:**制作一个载具的进度条(或仪表盘),首先在客户端创建一个进度条GUI,然后创建一个更新函数,在客户端Update Event进行调用,直接从客户端获取Vehicle.Speed进行进度条赋值,这样每帧更新且没有卡顿。+> **正确举例:**制作一个载具的进度条(或仪表盘),首先在客户端创建一个进度条GUI,然后创建一个更新函数,在客户端每帧进行更新,直接从客户端获取`Vehicle.Speed`进行进度条赋值,这样每帧更新且没有卡顿。
  
-> **错误举例:**同上,制作一个GUI进度条,在服务端判断玩家上车后,在帧循环事件中给玩家发送**自定义客户端数据(ServerToClientData)**,参数附带Character.Vehicle.Speed,客户端收到后把参数赋值给进度条Progress。这样虽然能实现相同的数据效果,但进度条会因网络等原因导致显示不流畅(虽然可以通过Lerp方式处理,但这个例子说明有些功能并不适合服务端脚本开发,甚至应该避免这种快速每帧、定时循环发送客户端数据的行为),并且造成了不必要的网络通讯开销、资源的浪费。+> **错误举例:**同上,制作一个GUI进度条,在服务端判断玩家上车后,在帧循环事件中给玩家发送**自定义客户端数据(ServerToClientData)**,参数附带`Character.Vehicle.Speed`,客户端收到后把参数赋值给进度条Progress。这样虽然能实现**相同的数据效果**,但进度条会因**网络延迟**等导致**显示不流畅**(虽然可以通过**插值方式**处理,但有些功能并不适合服务端开发,通常应该避免这种**快速每帧、定时循环**发送客户端数据的情况),并且造成了不必要的**网络通讯**开销浪费。
  
-#### 附:程序开发的基本步骤 
  
-1.需求分析:确定想要实现的功能,对其进行简单的描述。 
  
-> 例如:想要设计和实现一个赛车玩法,需要清楚则是什么,路线是什么,哪些车参赛,人数怎么安排,比赛怎么进行等。+### 功能开发步骤
  
-2.设计:根据需求分析,在开发层面进行抽象、分类和归纳,总结出需存在哪些数据,功能,流程。+1. 需求分析:确定想实现的功能,对其进行简单的描述
  
-例如:设计赛车的路线由一个个点连接组成,参赛者用列表统一管理,参赛车有什么属性、功能,人数限制的功能描述什么时候在什么地方会发生什事(流程)+   例如:想要设计和实现一个赛车的玩法,需要清楚规则是什么,路线是什么哪些车参赛,人数怎么安排比赛怎进行
  
-3.实现:用具体的代码和环境等来实现以上的设计(也就是编写代码);同样的设计,可以有各种不同的实现。+2. 设计:根据需求分析,在开发层面进行抽象、分类和归纳,总结出需要存在哪些数据,功能流程
  
-例如:赛车的路线用Checkpoint(检查)来实现,参赛者用数组的方式实现存储以及自定义的各种函来实现具体的功能等等。+   例如:设计赛车的路线由一个个连接组成,参赛者用列表统一管理参赛车有什么属性、功能,人限制的功能描述,什么时候在什么地方会发生什么事(流程)
  
-4.调试开发最后一步,及时发异常,确保项目能正常运行。(最好可进行可行优化+3实现用具体代码和环境等来实现以设计(也就是编写代码
  
-例如:从使者角度,对各个功能进行实际的上手测试黑盒测试;从开发角度对代码可能存在隐患和边界条件进行上手测试(白盒测试)。+   例如:赛车的路线Checkpoint检查点来实现,参赛用数组的方式实现存储以及自定义各种函数来现具体功能等等
  
 +4. 调试:开发的最后一步,及时发现异常,确保项目能正常运行
  
- +   例如:从使用者角度对各个功能实际上手测试黑盒测试;从开发者角对代码可能在的隐患和边界条进行实际测试白盒测试
-### 脚本逻辑指引 +
- +
-沙盘引擎的脚本功能设计偏重于==【世界端主逻辑】==。 +
- +
-也就是说**能在World端实现的功能,不要在其他脚本执**,Client端API较少也没必要多,因为更多是客户端本地内容,而不是客户端世界内容,**主要的世界玩法功能应该在World端体现**。 +
- +
-> **例如:**开发者希望实现“当色手中的物体变成了【马桶】时在此玩家的视角里弹出一个GUI窗口(上面可能写着一些文本或按钮)” +
-+
-沙盘引擎脚本设计理念下,**正确做法应该是:在World端`当角色手中的武器被改变`事中,判断角色是否为玩家以及角色手中物品是否为【马桶】,然后发送一条自定义网络消息(或使用客户端反射API)告知客户端打开指定GUI界面。(关闭GUI逻辑亦然,只需判定中物品从马桶改变为其他)** +
-+
-> **反例补充:**不应该在Client脚本中根本没有类似的事件执行这一逻辑判断。绝大多数**游戏世界功能**都应优先考虑World脚本,而不是Client脚本,避免出现Server&Client双核大脑开发逻辑冲突耦合等问题。+
  
  
行 126: 行 58:
 ## 脚本文件 ## 脚本文件
  
-沙盘引擎的**所有模组脚本**均在`模组目录/Script/XXX脚本组`目录。+沙盘引擎的**所有模组脚本**均在模组目录下`Script/XXX`目录。
  
-其中入口脚本有两个,分别是**World.js****Client.js**+其中**入口脚本**有两个,分别是`World.js``Client.js`
  
-| 脚本名称  | 说明                                                         |+| 脚本      | 说明                                                         |
 | --------- | ------------------------------------------------------------ | | --------- | ------------------------------------------------------------ |
 | World.js  | 主动入口脚本<br />负责一切与主世界相关的内容(服务端权威,如世界时间、天气、其他服务端及玩法类逻辑) | | World.js  | 主动入口脚本<br />负责一切与主世界相关的内容(服务端权威,如世界时间、天气、其他服务端及玩法类逻辑) |
 | Client.js | 主动入口脚本<br />负责一切与本地客户端相关的内容(如GUI、相机视角、本地事件、本地世界事件等) | | Client.js | 主动入口脚本<br />负责一切与本地客户端相关的内容(如GUI、相机视角、本地事件、本地世界事件等) |
  
-**需要特别注意,因为世界端(服务端)和客户端逻辑同步有很多不同的地方,所以两种脚本的Event、Function并非是通的。** **(两个脚本分别是不同的工作空间** +特别注意两种脚本的用空间**不是互通**的,只在各自代码空间生效
- +
-> 具体Client、World脚本开发API,请参考**脚本开发**文档下子分类有些API可能很相似,除非完全一样,否则是不能通用的。+
 > >
-> 除此之外,也有一些**原生通用Native**的代码API,这种通常是可以在客户端和主世界通用的代码+> 除此之外,也有一些**原生通用Native**的代码API,此分类下的代码可以在两种脚本空间通用,但数据通常不互通
  
  
行 145: 行 75:
 ### 加载子脚本 ### 加载子脚本
  
-实际开发过程中,特别是针对游戏设定复杂的游戏,显然只有两个脚本文件是不利于开发者便利的。+实际开发过程中,特别是针对**设定复杂**的游戏,显然只有两个**脚本文件**是不利于开发者**逻辑清晰**的。
  
-**沙盘引擎脚本**允许开发者使用API加载其他脚本文件(或叠加的意思)这样开发者可以根据模组情况自行分配多个子脚本。+模组允许开发者**使用API加载其他脚本**,开发者可以根据情况**自行分配**多个**子脚本**,帮助开发者提高开发效率
  
-针对设定复杂的游戏模组,分配好合理易懂的脚本分类可以让开发更清晰,帮助开发者提高开发效率。 +成功加载**子脚本**后,实际相当于将代码**叠加在同一个作空间**,并不代表真正意义分割为**“其他脚本”**。 
- +
-加载并成功注册子脚本后,实际相当于**保存在同一个开发工作空间**,并不代表真正意义“其他脚本”或“其他类”,只有脚本分割美观意义+> 当开发者需要使用**子脚本**方法时,直接正常执行即可,不需要增加文件名前缀等
  
 ```javascript ```javascript
-//伪脚本示例 
 LoadScript("newscript.js"); LoadScript("newscript.js");
 LoadScript("Func/Functions.js"); LoadScript("Func/Functions.js");
- 
-//当开发者需要使用时,直接执行函数、常量即可,并不需要以XXX.Function()等方式使用。 
-//在任何其他脚本(除非只有Client和World入口脚本是两个不同的空间)建立的变量、常量、函数,在子脚本中也正常使用,由此需要留意避免重名的情况。 
 ``` ```
  
-新建的子脚本必须放置在Script目录内(也可以放置到新建子目录),放置后的脚本必须通过API进行**加载脚本**,否则没有作用(但也可以举一反三,通过此机制实现模块化开发)。+任何新建的**子脚本**必须放置在`Script`目录内(子目录),放置后的**子脚本**必须通过API进行**加载脚本**,否则没有作用(抛砖引玉通过此机制实现模块化开发)。
  
  
行 168: 行 94:
 ### 脚本加密 ### 脚本加密
  
-在沙盘引擎模组开发中,无论是客户端还是世界端脚本,都将在模组打包时被附带到模组目录中,因此**模组开发源代码、脚本是透明的**。+在 沙盘引擎 模组开发中,无论是**世界脚本**还是**客户端脚本**,都将被放置在模组目录中,因此模组的**脚本源代码是透明的**。
  
-由于**JavaScript脚本**的透明性,如果不希望别人查看自己的脚本,可以通过网络上的混淆、加密工具进行加密,但是==**务必确保你手里有源文件的备份,否则可能是不可逆的==**,建议只在确认打包发布之前进行一次混淆,本地测试阶段并不需要混淆和加密。+由于`JavaScript`脚本的透明性,如果不希望别人查看自己的脚本,可以通过网络上的**混淆、加密工具**进行加密,但是==**务必确保源文件的备份,否则可能是不可逆的==**,建议只在**确认打包**之前进行一次混淆,本地测试阶段并不需要混淆和加密。
  
-==***注意:混淆工具并不是完全不可逆,只能起到加密和阻止完全修改的作用。***== +注意:由于 沙盘引擎 的联机自动同步机制即使在开发过程中,也可能有玩家会连接到当前服务并且自动下载**最新的模组全部文件**包括脚本),因此如果不希望其他人进入,以提前设置服务器密码或相关权限
- +
-> 网络联机情况下,会自动验证加入客户端的脚本同步避免客户端脚本被修改后的错误同步或作弊(如果与服务端下的Client.js最终无法同步被禁止连接)。 +
- +
-**出于脚本和资源开放和部分透明性,《沙盘引擎》官方将尽可能保护原创模组的版权,对于受到盗版争议、未经允许或恶意破解修改已有模组的内容非法修改版模组),引擎官方将能在合理范围内对问题模组进行屏蔽阻断和下架处理**+
  
 > JavaScript在线加密工具:https://obfuscator.io/ > 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)。+### 本体思维扩展
  
 +[note2]
  
 +此部分可能包含过时内容,暂时仅供参考。
  
-### 服务端子脚本(概念阶段) +[/note]
- +
-> 注意:此功能仍处于概念阶段,仅供引擎开发计划参考和逻辑扩展,**不具有实现功能**。 +
- +
-通过上文的解释,我们可以知道衍生游戏主要依靠Client.js和World.js脚本组成最终游戏。 +
- +
-但是在某些情况下,可能衍生游戏的开发组仍然希望“衍生游戏用户”可以自定义服务器功能脚本,但又不希望用户直接修改Client.js或World.js脚本(或以上两个脚本发布时已经加密混淆),这就有了**服务端子脚本**的概念,相当于在原有World.js的基础上,增加了一个与其相同作用的`Server.js`。 +
- +
-因为模组开发者已经将Client\World脚本加密,所以模组用户自然无法直接修改这些内容,但是可以通过子脚本实现和World.js几乎相同的功能(对于部分功能,可能会有削减),也应该支持World.js内的一些自定义公开API。 +
- +
-==补充:此功能虽然未在《沙盘引擎》版本中官方支持实现,但开发者仍然可考虑通过【子脚本+自定义函数】的方式实现此功能。== +
- +
- +
- +
-### 开发思维扩展+
  
 通过了解,沙盘引擎两个主要入口脚本`Client.js`和`World.js`分别有各自的激活场景和作用。 通过了解,沙盘引擎两个主要入口脚本`Client.js`和`World.js`分别有各自的激活场景和作用。
行 222: 行 118:
 这其实也可以扩展出一些其他思维,比如你有版权或用户隐私方面的顾虑或其他打算,你完全可以进行`C\S`客户端和服务端的分别开发和打包。 这其实也可以扩展出一些其他思维,比如你有版权或用户隐私方面的顾虑或其他打算,你完全可以进行`C\S`客户端和服务端的分别开发和打包。
  
-也就是说,客户端如果没有“建立主机服务器”的需求,完全可以将World.js留空或删除,实现**网游化**的开发。+也就是说,客户端如果没有**“建立主机服务器”**的需求,完全可以将`World.js`留空或删除,实现**网游化**的开发。
  
-如可能开发者不想让任何人都**架设服务器**,那么就只需将World.js连同工程单独拷贝出来一份,很简单的形成了单独的“服务端”。+可能开发者不想让任何人都**架设服务器**,那么就只需将`World.js`连同工程单独拷贝出来一份,很简单的形成了单独的**“服务端”**
  
-*因为届时开发者发布给玩家的**模组本体不包含World.js**(服务端脚本),所以玩家无法建服**(客户端也不应该有开启服务器的UI接口)**,只能进行连接,而连接是不需要World.js的。*+因为开发者发布给玩家的**模组本体**可以不包含**世界脚本**(`World.js`),所以玩家**无法建务器**(客户端也不应该有开启服务器的UI接口),因此只能进行加入游戏,而**加入游戏**可以不需要**世界脚本**的。
  
-> 此开发思路只是针对**某些特殊玩法或网游服务器需求情况**,如果是常规**好友派对类型游戏**情况下,还是建议将建立主机的能力附带给玩家。+> 此开发思路只是针对某些**特殊玩法****网游服务器**需求情况,如果是常规**好友派对游戏**情况下,还是建议将**建立主机**的能力附带给玩家。
 > >
-> 简单举例:《我的世界》和《战地5》的区别;一个是任何人可以直接本地开启服务端游玩,另一个是只有官方拥有服务器,或者提供租赁服务。 
- 
-*总结:如果作为客户端连接其他服务器时,其实可以不用存在World端脚本,只有Client脚本才是必须的。World端更多是在有【创建本地服务器\主机游戏】需求时才必须存在的。* 
  
  
行 238: 行 131:
 ### 开放性利弊分析 ### 开放性利弊分析
  
-由于Javascript本身语言的透明性,无法进行二进制等真正意义的编译打包,尽管上面介绍了混淆加密工具的方式,但仍然会有小概率可能会被修改。+由于`JavaScript`本身语言的透明性,无法进行**二进制**等真正意义的**编译打包,**尽管上面介绍了**混淆加密工具**的方式,但仍然会有小概率可能会被修改。
  
-无论如何,进行混淆和加密有必要的,否则可能会出现一些预期之外的情况。+无论如何,最终版本进行**混淆和加密**或许是有必要的,否则可能会出现一些预期之外的情况。
  
-如没有进行任何混淆和加密,可能脚本会被其他人参考(在某些情况下,这是有助于学习的),但也可能会被他人进行违背作者初衷的魔改。+**例:**没有进行任何混淆和加密,可能脚本会被其他人参考(在某些情况下,这是有助于学习的),但也可能会被他人进行**违背作者初衷**的魔改。
 > >
-> 举例:开发者A制作了解谜小游戏,开发者B直接修改其未加密的代码,添加、开放了许多达到作弊效果的功能和指令,破坏了模组作者设计的初衷。+**举例:**开发者制作了解谜小游戏,玩家直接修改其**未加密**的代码,添加了许多达到**作弊效果**的功能和指令,破坏了模组作者设计的初衷。
  
-考虑到类似这样的情况,尽管官方会按照**初版发布时间**和**大众意愿评定**来进行区分和保护正版模组,但如果开发者抛开混淆和加密仍然有困扰,可以考虑[开发思维扩展](#开发思维扩展)的,综合判定是否要进行“独立服务端”的开发方式(因为世界服务端基本控制着玩法的核心,客户端只有Client.js是只有基础的功能的)。 +考虑到类似这样的情况,尽管官方会按照**初版发布时间**和**大众意愿评定**来进行区分和保护正版模组,但如果开发者抛开混淆和加密仍然有困扰,可以考虑本文其他的扩展方,综合判定是否要进行**“独立服务端”**的开发方式等。
- +
- +
- +
-### 联机脚本同步匹配 +
- +
-由于脚本在Native的支持下会有很多“危险”的指令(如引擎内文件操作、数据库操作),这有可能会产生部分恶意脚本影响正常游戏。 +
- +
-无论是作为客户端玩家还是服务端“开服者”,都不建议使用**完全未知且不可信任**的第三方脚本,除非你知道自己在做什么。 +
- +
-> 当玩家作为“服务端主机“开启联机服务器时(单机游戏、本地游戏除外),服务器会在每次玩家加入时**验证玩家`Client.js`的脚本是否与服务端同步**,如果出现文件MD5等数值不匹配,则被认定为文件不同(哪怕只有微不足道的改动),==服务端将会以`Client.World.ConnectResult == 5`(版本、协议不匹配)拒绝玩家连接到服务器==。 +
-+
-> 换句话说,无论是玩家或者服务端主机还是出于版权保护,都不应该修改`Client.js`文件(通常也是被模组开发者加密混淆)。 +
- +
- +
- +
-## 客户端(Client) +
- +
-> 客户端的操作范围:一切适合在客户端做的事情(如本地数据及复杂运算),以及只为玩家“本地”出现的内容。 +
-+
-> 客户端的内容是默认保存在本地的,且运算代码也将在本地进行,所以不用担心网络延迟等问题(但要适当考虑漏洞,避免利用本地客户端作弊)。 +
- +
-客户端代码除了允许的世界Event(例如onTimeChange、onCharacterAction)外,还包括一些世界端没有的独有逻辑(例如:**打开客户端的某个内置界面**,也可以通过**自定义网络数据**以实现服务端命令的类似效果)。 +
- +
- +
- +
-## 世界端(World \ Server) +
- +
-> 服务端的操作范围:一切需要安全保存的事情(如玩家血量、经验值等),以及需要网络同步的内容(例如一个操作需要让全部玩家都同步看到的)。 +
-+
-> 服务端的内容是仅保存在“主机玩家”设备中,运算代码都将在“主机玩家”设备进行,并且通常会在运算处理结束后同步给其他玩家,这就造成了无论是多小的运算,都会有网络延迟和影响,不建议频繁运算的功能使用服务端来进行(例如:线性改变玩家视角,虽然服务端API可以做到,但是应该用客户端API来做,因为每次服务端发送到玩家“改变视角”数据是至少需要时间的,看起来会不平滑)。 +
- +
-世界端严格意义上来讲**可以被称为“服务端”**,但是由于它只在World场景加载后执行,所以也被习惯性称之为世界端。 +
- +
-世界端代码包含大量与世界可见内容息息相关的部分,通常想调整世界内玩法相关的内容,均需要在世界端进行处理。 +
- +
-==世界端脚本只在主机玩家(或单人游戏本地玩家)执行,所以是服务器权威(网络同步概念)==+
  
  
行 288: 行 145:
 ## 脚本框架及来源 ## 脚本框架及来源
  
-沙盘引擎游戏本体是基于[Unity](https://unity.com/)进行开发的,引擎中动态编译解析`JavaScript`脚本的功能来源于[PuerTS](https://github.com/Tencent/puerts)框架(致敬感谢)。+沙盘引擎 游戏本体是基于[Unity](https://unity.com/)进行开发的,引擎中动态编译解析`JavaScript`脚本的功能来源于[PuerTS](https://github.com/Tencent/puerts)框架(致敬)。
  
 尽管引擎脚本是以`JavaScript`作为编译基础的,但引擎代码的设计思维来自于[松鼠语言脚本](http://squirrel-lang.org/)(另外一个脚本语言)。 尽管引擎脚本是以`JavaScript`作为编译基础的,但引擎代码的设计思维来自于[松鼠语言脚本](http://squirrel-lang.org/)(另外一个脚本语言)。
- 
-> 如果开发者曾了解过[SA-MP](https://www.sa-mp.com/)或[VC-MP 0.4](http://vc-mp.org/),那么在沙盘引擎内你可能会有一些“亲切感”,并且会对沙盘引擎的API代码设计更快速的理解入门。 
-> 
-> *《沙盘引擎》部分代码开发者曾在VC-MP环境开发过许多内容,所以吸取借鉴了部分代码开发思路(如Event和Function)。* 
  
  
  
-## ==脚本API文档指南==+## 脚本API文档指南
  
-有关脚本API文档将被分成3个主要部分,分别是`原生及通用代码``客户端代码Client``世界端代码World`+有关脚本API文档将被分成**主要部分**,分别是**原生及通用代码****客户端代码Client)****世界端代码World)**
  
-公开范围内所有的Event(事件)、Property(属性)、Function(方法)等代码都将在**以上三个文档内**记录呈现,部分没有被详细介绍的API开发者可自行探索,技多不压身+公开范围内所有的`Event`(事件)、`Property`(属性)、`Function`(方法)等代码都将在**以上三个文档内**记录呈现。
  
-> 文档中部分代码(类)实际上可能是**静态类名**,这里需要根据API文档内**【代码首字母大小写**进行区分(如`Timer`是一个静态类,而`timer`则是一个Timer生成的实例)。+> 文档中部分代码(类)实际上可能是**静态类名**,这里需要根据API文档内**首字母大小写**进行区分(`Timer`是一个静态类,而`timer`则是一个生成的实例)。
  
 ```javascript ```javascript
-Timer.Create() [√正确+Timer.Create() [√] 
-timer.Create() [×错误,Create是静态方法]+timer.Create() [×] //no instance function
  
-Timer.Repeat == 0 [×错误,Repeat是成员属性,而非类型属性+Timer.Repeat == 0 [×] //No static property 
-timer.Repeat == 0 [√正确]+timer.Repeat == 0 [√]
 ``` ```
  
-> 为了更好的理解API文档的内容和信息,下方介绍API文档一些文档规范和名词解释。+> 为了更好的理解**API文档**的内容规范文档的部分**类型名词**解释。
  
-| 数据类型        | 说明                                                         | +| 数据类型         | 说明                                                         | 
-| --------------- | ------------------------------------------------------------ | +---------------- | ------------------------------------------------------------ | 
-| int             | 整数(50)                                                   +`int`            | 整数(`50`)                                                 
-| float           | 浮点小数(1.0f或1.1234)                                     +`float`          | 浮点小数(`1.0``1.1234`)                                  
-| double          | 双精度浮点小数(1.00000001)                                 +`double`         | 双精度浮点小数(`1.00000001`)                               
-| string          | 字符串("文本")                                             +`string`         | 字符串(`"Hello"`)                                          
-| char            | 单个字符                                                     | +`char`           | 单个字符                                                     | 
-| boolboolean) | 布尔逻辑型(真、假)                                         +`bool boolean| 布尔逻辑型                                                   
-| any             非硬性检查的类型,会根据具体逻辑进行装箱拆箱<br />虽然没有固定的限制,但是开发者有必要确保传递数据类型合法 |+`any`            通用类型,自动进行装箱\拆箱<br />虽然没有固定的限制,但是开发者应该确保数据类型正确 |
  
-```javascript +在本分类下的API文档中,可能会看到类似`TypeScript`格式**代码样式**但通常并不能直接开箱即用请使用最终`JavaScript`支持的标准格 
-//本API文档中,可能会出现如下案例 +
- +> 例如:`function OnPlayerJoin( player: Player )`,此代码不标准格式仅用于表示**类型参数**等信息。 
-//如果是此类型情况可视性比较高,所以可不会标注player具体是什么类型 +> 
-//因为大概率可以直接看出它是一个player类型 +> 实际应用时,应该是类似以下的代码标准格式 
-function OnPlayerJoin( player ) +
- +> ```javascript 
-//如果是此类型情况对参数名定义相对模糊,可能会注告知开发者具体类型(以TypeScript类型方 +function OnPlayerJoin( player ) 
-function OnPlayerChat( player, textstring //参数名后面": "跟随的就此参数类型,表示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 +
-```+
  
  
行 355: 行 196:
 ### 脚本API文档标题 ### 脚本API文档标题
  
-由于引擎主要功能是由World、Client两个主类下的众多子类实现控制的,所以脚本文档采用了XXX 类参考的命名方式。+由于引擎主要功能是由`World``Client`两个主类下的众多子类实现控制的,所以**脚本文档**采用了**XXX 类参考**的命名方式。
  
-也就是说,例如你想查找一个关于世界下的玩家方法,那么应该前往[世界脚本——Player 类参考](scripting/world/player "世界脚本——Player 类参考")进行查找。+也就是说,例如查找关于**世界脚本**下的玩家方法,那么应该前往[世界脚本——Player 类参考](scripting/world/player "世界脚本——Player 类参考")进行查找。
  
 **除此之外,每个类型参考文档内拥有多个H1、H2标题进行分类,通常会按照以下命名进行区分。** **除此之外,每个类型参考文档内拥有多个H1、H2标题进行分类,通常会按照以下命名进行区分。**
行 374: 行 215:
 沙盘引擎使用`JavaSciprt`作为开发脚本语言,所以开发者**可通过任何文本编辑器进行脚本编写**。 沙盘引擎使用`JavaSciprt`作为开发脚本语言,所以开发者**可通过任何文本编辑器进行脚本编写**。
  
-脚本开发时并**不需要专门的IDE以及编译器**,只需要进行**增删查改**及修改后的**保存“文本”**即可,待下次引擎入脚本时将会**自动编译**。+脚本开发时并**不需要专门的IDE以及编译器**,只需要进行**增删查改**及修改后的**保存“文本”**即可,重新建立世界(重连、重时将会**自动动态编译**。
  
  
行 380: 行 221:
 ## 脚本开发基础入门 ## 脚本开发基础入门
  
-> 注意:此处指的**基础入门**并==不是==`JavaScript`语言的基础教程,而是指引已有编程逻辑基础的开发者,快速进行了解脚本代码的一些差异和规范。+> 注意:此处指的**基础入门**并==不是==`JavaScript`语言的基础教程,而是指引已有**编程基础**的开发者,快速进行了解脚本代码的一些**差异和规范**
 > >
-> 如果你是一位编程初学者或`JavaScript`初学者,可以考虑参考以下视频教程。+> 如果你是一位编程初学者或`JavaScript`初学者,可以考虑参考以下**视频教程**
 > >
 > 四十分钟JavaScript快速入门:https://www.bilibili.com/video/BV15L4y1a7or > 四十分钟JavaScript快速入门:https://www.bilibili.com/video/BV15L4y1a7or
行 389: 行 230:
 > >
  
-《沙盘引擎》的开发初衷就是为了**精简开发流程以及代码的复杂程度**,所以实际上对引擎模组脚本的基础开发**并不需要非常过硬的编程技术**+《沙盘引擎》的开发初衷就是为了**精简开发流程以及代码的复杂程度**,所以实际上对**模组脚本**的基础开发并不需要非常过硬的编程技术。
  
-如果开发者曾对`Pawn`、`Squirrel`或`其他高级编程语言`有相关经验,甚至不需要完全学习`JavaScript`,只需掌握基础的语法使用即可尝试使用+如果开发者曾对`Pawn`、`Squirrel`或**其他编程语言**有相关经验,甚至不需要完全学习`JavaScript`,只需掌握**基础的语法**使用即可开始体验
  
  
行 410: 行 251:
 } }
 ``` ```
- 
- 
- 
-### 类名区分及使用 
- 
-在沙盘引擎的函数方法设计中,都是以Class类的方式来实现的,也就是无论任何功能调用,均是从某个`Class类`来激活使用的。 
- 
-**由此得知,绝大多数情况下类名的前缀是必须的。(除World.js下的SE.World分类等例子,因为本来就是在类下)** 
- 
-```javascript 
-//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是一个“原生通用代码”(详情参考文档:脚本开发——原生通用代码),此类代码可不写“类前缀” 
-``` 
- 
-如果你能**比较清晰**的理解以上的代码示例,那么你在开发引擎脚本方面就没有太大问题啦! 
  
  
行 449: 行 256:
 ### 默认类空间 ### 默认类空间
  
-关于脚本编写时**“类名的使用”**在上方已经介绍过,实际上并不需要将此问题考虑过于复杂。 +为了脚本开发思路更清晰,所以引擎采用了**必要类名前缀**的调用方式,实际上类于**命名空间**的概念,。
- +
-为了脚本开发思路和可视性更清晰,所以引擎采用了**优先必要类名前缀**的调用方式,实际上某些是存在**“默认工作空间”**的,这种情况下是不需要进行前缀调用的(虽然也可以写,但是可以忽略更精简一些)。 +
- +
-| 脚本环境  | 默认类    | 说明                                                         | +
-| --------- | --------- | ------------------------------------------------------------ | +
-| Client.js | SE.Client | 因为Client类本身就基SE.Client下<br />所以在**Client.js**内可以省略`Client.XXXX`,而直接使用`XXXX()` | +
-| World.js  | SE.World  | 因为World类本身就基于SE.World下<br />所以在**World.js**内可以省略`World.XXXX`,而直接使用`XXXX()` | +
-| 通用原生  | SE.Native | 在SE.Native下的类成员可直接使用<br />例如:`DLog()`或`Vector(0, 0, 0)`等均属于原生通用内容,不需要任何前缀<br />详情参考可见==脚本开发——原生通用代码== | +
- +
-**注意:`Client.js`和`World.js`是完全不同的两个类,即使有些内容(如Sound类)命名相同,但不代表在底层是基于同一个类。** +
- +
-> 例如:Sound类看起来名称一样,实际上在底层的表现可能为`SE.Client.Sound`与`SE.World.Sound`,而不是真正意义上的`SE.Sound`(这只是一种说明方式)。 +
-+
-> ==**这也就导致尽管类名相同,但在Client和World环境下具体用法可能有差异(因为根本严格意义上不是一个类),有些功能是客户端专用,有些是世界端专用,也有部分是通用的功能。**== +
- +
- +
- +
-## 脚本提示(VSCode) +
-> 在实际代码开发过程中,开发者可能更喜欢有自动补全的`JavaScript`编写体验,这可以使用【VSCode编辑器+TS声明文件+JSDOC声明】的方式来实现。 +
-+
-> 例如:输入`Chara`时,编辑器会自动帮助补全`Character`,以及相关参数、属性、返回值的类型和说明。 +
- +
-[note2] +
-最新声明文件下载:https://gitcode.net/Bibiboxs/SandtableEngine/-/blob/master/Sample/index.d.ts +
-[/note] +
- +
-**使用方法:**放置在要编写的模组根目录,使用VSCode以模组目录作为工作空间即可自动识别,模组默认工程通常会附带声明文件(`index.d.ts`)。 +
- +
-**注意事项:**声明文件集合了全部已知API、属性等自动补全功能同时支持World\Client两个脚本空间,开发者在补全使用时需要根据注释区分World\Client类型归属(例如:有些代码两个脚本可能有相似之处,但可能参数\返回值有差异,不能完全兼容使用) +
- +
-### 参数补充(JSDOC) +
-对于多数可被VSCode识别的类型,开发者可直接进行编写使用,但对于**函数方法&参数类型**编辑器可能无法准确识别,这通常需要开发者自行安装VSCode插件,或使用[JSDOC声明](https://www.jsdoc.com.cn/tags-param "JSDOC声明")来实现(推荐)。 +
- +
-```javascript +
-/** +
- * @param {Player} player +
- */ +
-function OnPlayerJoin( player ) { +
-  DLog(player.Name); +
-+
-``` +
- +
- +
- +
- +
- +
- +
  
 +| 脚本环境         | 默认类   | 示例                                                     |
 +| ---------------- | -------- | -------------------------------------------------------- |
 +| `Client.js`      | `Client` | `Client.Core.XXXXX()`                                    |
 +| `World.js`       | `World`  | `World.Core.XXXXX()`                                     |
 +| 通用(`Native`) | `-`      | `CreateHost()` <br />`Random.Range()`<br />`Debug.Log()` |
  
 +> 注意:`Client.js`和`World.js`是**完全不同**的两个**程序空间**,即使有些内容(如`Player`类)命名相同,但不代表在底层是基于同一个类。