meta data for this page
📚 差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版 | |||
scripting [2025/04/30 02:57] – bibiboxs | scripting [2025/07/25 18:03] (当前版本) – bibiboxs | ||
---|---|---|---|
行 2: | 行 2: | ||
# 脚本开发及API | # 脚本开发及API | ||
- | 脚本,在《沙盘引擎》衍生作品的开发流程中,用来实现开发者的设计。目前引擎支持的脚本语言为`JavaScript`(TypeScript在计划中)。 | + | 本文档篇幅较长且相当重要,开发之前务必预览**至少一次**! |
- | 《沙盘引擎》框架将脚本开发细分成了两个部分——==**“世界端(服务端)”和“客户端”**==。 | ||
## 索引 | ## 索引 | ||
行 11: | 行 11: | ||
{{indexmenu>: | {{indexmenu>: | ||
</ | </ | ||
- | |||
- | > 本文档篇幅较长且相当重要,开发之前务必预览**至少一次**! | ||
- | |||
- | |||
- | |||
- | ## 名词解释:类名 | ||
- | |||
- | | 名称 | ||
- | | -------------- | ------------------------------------------------------------ | | ||
- | | **`Client/ | ||
- | | **`World/ | ||
- | | **`.../ | ||
- | | | | | ||
- | | `Audio` | ||
- | | `Camera` | ||
- | | `GUI` | 游戏用户交互、界面UI、FairyGUI、输入输出…… | ||
- | | | | | ||
- | | `AI` | 游戏寻路、智能逻辑…… | ||
- | | `NativeMenu` | ||
- | | | | | ||
- | | `Helper` | ||
- | | `Random` | ||
- | | `Socket` | ||
- | | `SQLite` | ||
- | | `Timer` | ||
- | | `Tween` | ||
- | | | | | ||
- | | `Entity` | ||
- | | `Character` | ||
- | | `Checkpoint` | ||
- | | `Model` | ||
- | | `Pickup` | ||
- | | `Player` | ||
- | | `Prop` | ||
- | | `Vehicle` | ||
- | | | | | ||
- | | `Billboard` | ||
行 53: | 行 16: | ||
## 前置概念 | ## 前置概念 | ||
- | 经过重新设计与版本迭代,沙盘引擎最终保留了`“世界端& | + | 沙盘引擎 脚本采用**世界脚本&客户端脚本”二合一**的开发方式。 |
- | + | ||
- | **引擎每次加载世界场景时,将加载一个整体组(World.js + Client.js)**,前者负责实现世界主要逻辑(服务端),后者负责实现客户端逻辑。 | + | |
- | + | ||
- | 模组的所有脚本文件放置在`模组目录/ | + | |
- | + | ||
- | > **==注意:《沙盘引擎》默认入口脚本名为“Main”,入口场景默认使用随机端口!==**。 | + | |
- | + | ||
- | 一个正确的【脚本组目录】结构,至少存在`World.js`和`Client.js`两个脚本==入口文件==(World为世界端脚本;Client为客户端脚本)。 | + | |
- | + | ||
- | > 进入任意模组时,引擎会默认加载【Main脚本组】,即加载`Mod/ | + | |
- | + | ||
- | | + | |
- | // | + | |
- | | + | |
- | { | + | |
- | | + | |
- | | + | |
- | } | + | |
- | ``` | + | |
- | 若地图文件及脚本组文件无异常,在执行`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& | + | |
行 126: | 行 58: | ||
## 脚本文件 | ## 脚本文件 | ||
- | 沙盘引擎的**所有模组脚本**均在`模组目录/Script/XXX脚本组`目录。 | + | 沙盘引擎的**所有模组脚本**均在模组目录下`Script/ |
- | 其中入口脚本有两个,分别是**World.js**与**Client.js**。 | + | 其中**入口脚本**有两个,分别是`World.js`与`Client.js`。 |
- | | 脚本名称 | + | | 脚本 |
| --------- | ------------------------------------------------------------ | | | --------- | ------------------------------------------------------------ | | ||
| World.js | | World.js | ||
| Client.js | 主动入口脚本< | | Client.js | 主动入口脚本< | ||
- | **需要特别注意,因为世界端(服务端)和客户端逻辑同步有很多不同的地方,所以两种脚本的Event、Function并非是通用的。** **(两个脚本分别是不同的工作空间)** | + | > 特别注意:两种脚本的作用空间**不是互通**的,只在各自的代码空间生效。 |
- | + | ||
- | > 具体Client、World脚本开发API,请参考**脚本开发**文档下的子分类,有些API可能很相似,除非完全一样,否则是不能通用的。 | + | |
> | > | ||
- | > 除此之外,也有一些**原生通用Native**的代码API,这种通常是可以在客户端和主世界通用的代码。 | + | > 除此之外,也有一些**原生通用(Native)**的代码API,此分类下的代码可以在两种脚本空间通用,但数据通常不互通。 |
行 145: | 行 75: | ||
### 加载子脚本 | ### 加载子脚本 | ||
- | 实际开发过程中,特别是针对游戏设定复杂的游戏,显然只有两个脚本文件是不利于开发者便利的。 | + | 实际开发过程中,特别是针对**设定复杂**的游戏,显然只有两个**脚本文件**是不利于开发者**逻辑清晰**的。 |
- | **沙盘引擎脚本**允许开发者使用API加载其他脚本文件(或叠加的意思),这样开发者就可以根据模组情况自行分配多个子脚本。 | + | 模组允许开发者**使用API加载其他脚本**,开发者可以根据情况**自行分配**多个**子脚本**,帮助开发者提高开发效率。 |
- | > 针对设定复杂的游戏模组,分配好合理易懂的脚本分类可以让开发更清晰,帮助开发者提高开发效率。 | + | > 成功加载**子脚本**后,实际相当于将代码**叠加在同一个作用空间**,并不代表真正意义分割为**“其他脚本”**。 |
- | + | > | |
- | 加载并成功注册子脚本后,实际上相当于**保存在同一个开发工作空间**,并不代表真正意义上“其他脚本”或“其他类”,只有脚本分割美观的意义。 | + | > 当开发者需要使用**子脚本**的方法时,直接正常执行即可,不需要增加文件名前缀等。 |
```javascript | ```javascript | ||
- | // | ||
LoadScript(" | LoadScript(" | ||
LoadScript(" | LoadScript(" | ||
- | |||
- | // | ||
- | // | ||
``` | ``` | ||
- | 新建后的子脚本必须放置在Script目录内(也可以放置到新建子目录),放置后的脚本必须通过API进行**加载脚本**,否则没有作用(但也可以举一反三,通过此机制实现模块化开发)。 | + | 任何新建的**子脚本**必须放置在`Script`目录内(或子目录),放置后的**子脚本**必须通过API进行**加载脚本**,否则没有作用(抛砖引玉,可通过此机制实现模块化开发)。 |
行 168: | 行 94: | ||
### 脚本加密 | ### 脚本加密 | ||
- | 在沙盘引擎模组开发中,无论是客户端还是世界端脚本,都将在模组打包时被附带到模组目录中,因此**模组开发源代码、脚本是透明的**。 | + | 在 沙盘引擎 模组开发中,无论是**世界脚本**还是**客户端脚本**,都将被放置在模组目录中,因此模组的**脚本源代码是透明的**。 |
- | 由于**JavaScript脚本**的透明性,如果不希望别人查看自己的脚本,可以通过网络上的混淆、加密工具进行加密,但是==**【务必确保你手里有源文件的备份,否则可能是不可逆的】==**,建议只在确认打包发布之前进行一次混淆,本地测试阶段并不需要混淆和加密。 | + | 由于`JavaScript`脚本的透明性,如果不希望别人查看自己的脚本,可以通过网络上的**混淆、加密工具**进行加密,但是==**务必确保源文件的备份,否则可能是不可逆的==**,建议只在**确认打包**之前进行一次混淆,本地测试阶段并不需要混淆和加密。 |
- | ==***注意:混淆工具并不是完全不可逆的,只能起到加密和阻止完全修改的作用。***== | + | 注意:由于 沙盘引擎 |
- | + | ||
- | > 网络联机情况下,会自动验证加入客户端的脚本同步性,避免客户端脚本被修改后的错误同步或作弊(如果与服务端下的Client.js最终无法同步,将会被禁止连接)。 | + | |
- | + | ||
- | **出于脚本和资源的开放和部分透明性,《沙盘引擎》官方将尽可能保护原创模组的版权,对于受到盗版争议、未经允许或恶意破解修改已有模组的内容(非法修改版模组),引擎官方将可能在合理范围内对问题模组进行屏蔽阻断和下架处理。** | + | |
> JavaScript在线加密工具:https:// | > JavaScript在线加密工具:https:// | ||
- | |||
- | |||
- | |||
- | ### 脚本加密延伸:模组定位 | ||
- | |||
- | 开发者在建立模组初期就应该对“自己模组”进行一个属性定位,究竟要作为一个**衍生游戏**还是一个**开放性联机游戏**? | ||
- | |||
- | 例如《战地1942》与《战地2》,以及和《战地5》进行一个对比,就可以解释这件事情。 | ||
- | |||
- | > 《战地1942& | ||
> | > | ||
- | > 反之 | + | > 混淆工具并**不是完全不可逆**的,只能起到加密和阻止完全修改的作用。*** |
- | > | + | |
- | > 《战地5》游戏本体没有任何“可以直接开服并修改”的内容,也就是说玩家只能体验开发者允许玩家体验的内容,这就是传统意义上的**“衍生游戏”**,玩家自然也无法通过常规手段对游戏进行二次创作。 | + | |
- | 关于此定位完全决定模组发布时是否要进行指定脚本的加密。 | ||
- | 结论:如果制作**“衍生游戏”**,则建议发布时加密全部脚本;如果制作的是**“开放性联机游戏”**,则建议只加密`Client.js`等客户端脚本,`World.js`服务端脚本可留给玩家进行二次创作。 | ||
- | > 注意:通常来讲,`World.js`服务端脚本包含着游戏玩法的核心代码,如果公开可能会造成修改、魔改版本,甚至脚本改编成新模组的情况出现,开发者在发布时应考虑周全,或者应该在模组根目录**写入许可说明**(https:// | + | ### 本体思维扩展 |
+ | [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直接修改其未加密的代码,添加、开放了许多达到作弊效果的功能和指令,破坏了模组作者设计的初衷。 | + | > **举例:**开发者制作了解谜小游戏,玩家直接修改其**未加密**的代码,添加了许多达到**作弊效果**的功能和指令,破坏了模组作者设计的初衷。 |
- | 考虑到类似这样的情况,尽管官方会按照**初版发布时间**和**大众意愿评定**来进行区分和保护正版模组,但如果开发者抛开混淆和加密仍然有困扰,可以考虑[开发思维扩展](# | + | 考虑到类似这样的情况,尽管官方会按照**初版发布时间**和**大众意愿评定**来进行区分和保护正版模组,但如果开发者抛开混淆和加密仍然有困扰,可以考虑本文其他的扩展方法,综合判定是否要进行**“独立服务端”**的开发方式等。 |
- | + | ||
- | + | ||
- | + | ||
- | ### 联机脚本同步匹配 | + | |
- | + | ||
- | 由于脚本在Native的支持下会有很多“危险”的指令(如引擎内文件操作、数据库操作等),这有可能会产生部分恶意脚本影响正常游戏。 | + | |
- | + | ||
- | 无论是作为客户端玩家还是服务端“开服者”,都不建议使用**完全未知且不可信任**的第三方脚本,除非你知道自己在做什么。 | + | |
- | + | ||
- | > 当玩家作为“服务端主机“开启联机服务器时(单机游戏、本地游戏除外),服务器会在每次玩家加入时**验证玩家`Client.js`的脚本是否与服务端同步**,如果出现文件MD5等数值不匹配,则被认定为文件不同(哪怕只有微不足道的改动),==服务端将会以`Client.World.ConnectResult == 5`(版本、协议不匹配)拒绝玩家连接到服务器==。 | + | |
- | > | + | |
- | > 换句话说,无论是玩家或者服务端主机还是出于版权保护,都不应该修改`Client.js`文件(通常也是被模组开发者加密混淆)。 | + | |
- | + | ||
- | + | ||
- | + | ||
- | ## 客户端(Client) | + | |
- | + | ||
- | > 客户端的操作范围:一切适合在客户端做的事情(如本地数据及复杂运算),以及只为玩家“本地”出现的内容。 | + | |
- | > | + | |
- | > 客户端的内容是默认保存在本地的,且运算代码也将在本地进行,所以不用担心网络延迟等问题(但要适当考虑漏洞,避免利用本地客户端作弊)。 | + | |
- | + | ||
- | 客户端代码除了允许的世界Event(例如onTimeChange、onCharacterAction)外,还包括一些世界端没有的独有逻辑(例如:**打开客户端的某个内置界面**,也可以通过**自定义网络数据**以实现服务端命令的类似效果)。 | + | |
- | + | ||
- | + | ||
- | + | ||
- | ## 世界端(World \ Server) | + | |
- | + | ||
- | > 服务端的操作范围:一切需要安全保存的事情(如玩家血量、经验值等),以及需要网络同步的内容(例如一个操作需要让全部玩家都同步看到的)。 | + | |
- | > | + | |
- | > 服务端的内容是仅保存在“主机玩家”设备中,运算代码都将在“主机玩家”设备进行,并且通常会在运算处理结束后同步给其他玩家,这就造成了无论是多小的运算,都会有网络延迟和影响,不建议频繁运算的功能使用服务端来进行(例如:线性改变玩家视角,虽然服务端API可以做到,但是应该用客户端API来做,因为每次服务端发送到玩家“改变视角”数据是至少需要时间的,看起来会不平滑)。 | + | |
- | + | ||
- | 世界端严格意义上来讲**可以被称为“服务端”**,但是由于它只在World场景加载后执行,所以也被习惯性称之为世界端。 | + | |
- | + | ||
- | 世界端代码包含大量与世界可见内容息息相关的部分,通常想调整世界内玩法相关的内容,均需要在世界端进行处理。 | + | |
- | + | ||
- | ==世界端脚本只在主机玩家(或单人游戏本地玩家)执行,所以是服务器权威(网络同步概念)。== | + | |
行 288: | 行 145: | ||
## 脚本框架及来源 | ## 脚本框架及来源 | ||
- | 沙盘引擎游戏本体是基于[Unity](https:// | + | 沙盘引擎 游戏本体是基于[Unity](https:// |
尽管引擎脚本是以`JavaScript`作为编译基础的,但引擎代码的设计思维来自于[松鼠语言脚本](http:// | 尽管引擎脚本是以`JavaScript`作为编译基础的,但引擎代码的设计思维来自于[松鼠语言脚本](http:// | ||
- | |||
- | > 如果开发者曾了解过[SA-MP](https:// | ||
- | > | ||
- | > *《沙盘引擎》部分代码开发者曾在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 | + | | `int` |
- | | float | + | | `float` |
- | | double | + | | `double` | 双精度浮点小数(`1.00000001`) |
- | | string | + | | `string` | 字符串(`"Hello"`) | |
- | | char | 单个字符 | + | | `char` | 单个字符 |
- | | bool(boolean) | 布尔逻辑型(真、假) | + | | `bool | boolean` | 布尔逻辑型 |
- | | any | + | | `any` |
- | ```javascript | + | > 在本分类下的API文档中,您可能会看到类似`TypeScript`格式的**代码样式**,但通常并不能直接开箱即用,请使用最终`JavaScript`支持的标准格式。 |
- | //在脚本API文档中,可能会出现如下的案例 | + | > |
- | + | > 例如:`function | |
- | // | + | > |
- | // | + | > 实际应用时,应该是类似以下的代码(标准格式): |
- | function OnPlayerJoin( player ) | + | > |
- | + | > ```javascript | |
- | // | + | > function |
- | function | + | > { |
- | + | > | |
- | // | + | > } |
- | World.SetPassword( pass: string ) //表示参数pass需要是一个字符串类型 | + | > ``` |
- | + | ||
- | //如果是此类型情况,表示此Event、Function拥有返回值(或要求返回值) | + | |
- | function | + | |
- | { | + | |
- | + | ||
- | } | + | |
- | + | ||
- | World.GetPassword(): | + | |
- | + | ||
- | // | + | |
- | World.CreateMapMarker(icon: | + | |
- | ``` | + | |
行 355: | 行 196: | ||
### 脚本API文档标题 | ### 脚本API文档标题 | ||
- | 由于引擎主要功能是由World、Client两个主类下的众多子类实现控制的,所以脚本文档采用了【XXX 类参考】的命名方式。 | + | 由于引擎主要功能是由`World`、`Client`两个主类下的众多子类实现控制的,所以**脚本文档**采用了**XXX 类参考**的命名方式。 |
- | 也就是说,例如你想查找一个关于世界端下的玩家方法,那么就应该前往【[世界脚本——Player 类参考](scripting/ | + | 也就是说,例如查找关于**世界脚本**下的玩家方法,那么应该前往[世界脚本——Player 类参考](scripting/ |
**除此之外,每个类型参考文档内拥有多个H1、H2标题进行分类,通常会按照以下命名进行区分。** | **除此之外,每个类型参考文档内拥有多个H1、H2标题进行分类,通常会按照以下命名进行区分。** | ||
行 374: | 行 215: | ||
沙盘引擎使用`JavaSciprt`作为开发脚本语言,所以开发者**可通过任何文本编辑器进行脚本编写**。 | 沙盘引擎使用`JavaSciprt`作为开发脚本语言,所以开发者**可通过任何文本编辑器进行脚本编写**。 | ||
- | 脚本开发时并**不需要专门的IDE以及编译器**,只需要进行**增删查改**及修改后的**保存“文本”**即可,待下次引擎载入脚本时将会**自动编译**。 | + | 脚本开发时并**不需要专门的IDE以及编译器**,只需要进行**增删查改**及修改后的**保存“文本”**即可,重新建立世界(重连、重载)时将会**自动动态编译**。 |
行 380: | 行 221: | ||
## 脚本开发基础入门 | ## 脚本开发基础入门 | ||
- | > 注意:此处指的**基础入门**并==不是==`JavaScript`语言的基础教程,而是指引已有编程逻辑基础的开发者,快速进行了解脚本代码的一些差异和规范。 | + | > 注意:此处指的**基础入门**并==不是==`JavaScript`语言的基础教程,而是指引已有**编程基础**的开发者,快速进行了解脚本代码的一些**差异和规范**。 |
> | > | ||
- | > 如果你是一位编程初学者或`JavaScript`初学者,可以考虑参考以下视频教程。 | + | > 如果你是一位编程初学者或`JavaScript`初学者,可以考虑参考以下**视频教程**。 |
> | > | ||
> 四十分钟JavaScript快速入门:https:// | > 四十分钟JavaScript快速入门:https:// | ||
行 389: | 行 230: | ||
> | > | ||
- | 《沙盘引擎》的开发初衷就是为了**精简开发流程以及代码的复杂程度**,所以实际上对引擎模组脚本的基础开发**并不需要非常过硬的编程技术**。 | + | 《沙盘引擎》的开发初衷就是为了**精简开发流程以及代码的复杂程度**,所以实际上对**模组脚本**的基础开发并不需要非常过硬的编程技术。 |
- | 如果开发者曾对`Pawn`、`Squirrel`或`其他高级编程语言`有相关经验,甚至并不需要完全学习`JavaScript`,只需掌握基础的语法使用即可尝试使用。 | + | 如果开发者曾对`Pawn`、`Squirrel`或**其他编程语言**有相关经验,甚至不需要完全学习`JavaScript`,只需掌握**基础的语法**使用即可开始体验。 |
行 410: | 行 251: | ||
} | } | ||
``` | ``` | ||
- | |||
- | |||
- | |||
- | ### 类名区分及使用 | ||
- | |||
- | 在沙盘引擎的函数方法设计中,都是以Class类的方式来实现的,也就是无论任何功能调用,均是从某个`Class类`来激活使用的。 | ||
- | |||
- | **由此得知,绝大多数情况下类名的前缀是必须的。(除World.js下的SE.World分类等例子,因为本来就是在类下)** | ||
- | |||
- | ```javascript | ||
- | //World.js | ||
- | |||
- | // | ||
- | World.SetWeather(0); | ||
- | SetWeather(0); | ||
- | |||
- | // | ||
- | Vehicle.Create(0, | ||
- | vehicle.Create(0, | ||
- | Create(0, Vector(0, 0, 0)); // | ||
- | |||
- | // | ||
- | let vehicle = Vehicle.Find(100); | ||
- | if(vehicle) vehicle.Health = 500; // | ||
- | |||
- | Vehicle.Heatlh = 500; // | ||
- | |||
- | // | ||
- | World.DLog(" | ||
- | SEngine.DLog(" | ||
- | DLog(" | ||
- | ``` | ||
- | |||
- | 如果你能**比较清晰**的理解以上的代码示例,那么你在开发引擎脚本方面就没有太大问题啦! | ||
行 449: | 行 256: | ||
### 默认类空间 | ### 默认类空间 | ||
- | 关于脚本编写时**“类名的使用”**在上方已经介绍过,实际上并不需要将此问题考虑过于复杂。 | + | 为了脚本开发思路更加清晰,所以引擎采用了**必要类名前缀**的调用方式,实际上类似于**命名空间**的概念,。 |
- | + | ||
- | 为了脚本开发时思路和可视性更清晰,所以引擎内采用了**优先必要时类名前缀**的调用方式,但实际上某些类是存在**“默认工作空间”**的,这种情况下是不需要进行前缀调用的(虽然也可以写,但是可以忽略更精简一些)。 | + | |
- | + | ||
- | | 脚本环境 | + | |
- | | --------- | --------- | ------------------------------------------------------------ | | + | |
- | | Client.js | SE.Client | 因为Client类本身就基于SE.Client下< | + | |
- | | World.js | + | |
- | | 通用原生 | + | |
- | + | ||
- | **注意:`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:// | + | |
- | [/note] | + | |
- | + | ||
- | **使用方法:**放置在要编写的模组根目录,使用VSCode以模组目录作为工作空间即可自动识别,模组默认工程通常会附带声明文件(`index.d.ts`)。 | + | |
- | + | ||
- | **注意事项:**声明文件集合了全部已知的API、属性等自动补全功能,同时支持World\Client两个脚本空间,开发者在补全使用时需要根据注释区分World\Client类型归属(例如:有些代码两个脚本可能有相似之处,但可能参数\返回值有差异,不能完全兼容使用)。 | + | |
- | + | ||
- | ### 参数补充(JSDOC) | + | |
- | 对于多数可被VSCode识别的类型,开发者可直接进行编写使用,但对于**函数方法& | + | |
- | + | ||
- | ```javascript | + | |
- | /** | + | |
- | * @param {Player} player | + | |
- | */ | + | |
- | function OnPlayerJoin( player ) { | + | |
- | DLog(player.Name); | + | |
- | } | + | |
- | ``` | + | |
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
- | + | ||
+ | | 脚本环境 | ||
+ | | ---------------- | -------- | -------------------------------------------------------- | | ||
+ | | `Client.js` | ||
+ | | `World.js` | ||
+ | | 通用(`Native`) | `-` | `CreateHost()` <br /> | ||
+ | > 注意:`Client.js`和`World.js`是**完全不同**的两个**程序空间**,即使有些内容(如`Player`类)命名相同,但不代表在底层是基于同一个类。 | ||