meta data for this page
  •  

📚 差别

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

到此差别页面的链接

两侧同时换到之前的修订记录前一修订版
后一修订版
前一修订版
scripting [2023/08/14 23:18] bibiboxsscripting [2025/07/25 18:03] (当前版本) bibiboxs
行 2: 行 2:
 # 脚本开发及API # 脚本开发及API
  
-在《沙盘引擎》开发流程中,脚本(JavaScript)是赋予世界声明最关键的部分。 +文档篇幅较长且相当重要,开发之前务必预览**至少一次**!
-只有通过编写脚的方式才可以实现开发者所想象的绝大多数事情。+
  
-《沙盘擎》框架将脚本开发细分成了两个部分——==**“客户端”和“世界端(服务端)”**==。+ 
 + 
 +## 索
  
 <script type="text/x-dokuwiki"> <script type="text/x-dokuwiki">
 {{indexmenu>:scripting|js}} {{indexmenu>:scripting|js}}
 </script> </script>
- 
-> **本文档篇幅较长且比较重要,建议开发者务必至少预览一次。** 
  
  
行 17: 行 16:
 ## 前置概念 ## 前置概念
  
-经过一些版本迭代和设计改变,沙盘引擎最终保留了`“客户端&世界端”二合一`脚本开发方式。+沙盘引擎 脚本采用**世界脚本&客户端脚本”二合一**的开发方式。
  
-**每次加载世界场景时将加载一个整体组(Client.js + World.js)**,前者负责执行客户端逻辑,后者负责执行世界主要逻辑(服务端)。+引擎每次**加载世界场景**将加载一个整体**脚本**`World.js + Client.js`),前者负责实现世界主要逻辑(服务端),后者负责实现客户端逻辑
  
-模组内所有脚本文件将被放置在`模组目录/Script`目录,并以**【脚本组目录】**方式来命名管理,**==《沙盘引擎》默认入口脚本名为“Main”==**+组加载完成后,引擎会默认加载`Main`脚本组内脚本,即加载`Mod/Script/Main`目录`Client.js``World.js`脚本。
  
-在一个正确的【脚本组】目录中,通常应该至少存在`Client.js`和`World.js`两个脚本入口文件(分别为客户端脚本、世界端脚本)。 
  
-> 当任意模组加载完成后,会由引擎默认加载到【Main脚本组】,也就是加载`Mod/Script/Main`目录下的Client.js和World.js脚本。 
-> 
-> 除此之外,开发者也可使用`LoadWorld()`等代码完成相同的操作。 
  
- ```javascript +### 脚本分配指引
- //Client +
- function some() +
- { +
-     //LoadWorld("newMapName", "newScriptName"); +
-     CreateHost("testmap", "MyWorld1"); //加载一个【地图为testmap,脚本为MyWorld1】的新游戏世界 +
- } +
- ```+
  
-如果地图文件及脚本组文件没有异常在执行`LoadWorld()`代码后,引擎将会断开现有的世界和连接,根据所填参数建立【指定地图+逻辑脚本新世界场景+在设计脚本应该做好**世界脚本及客户端脚本**功能责任分配,避免出现**过度相互依赖**的情况,有助于后续的版本开发和日常维护
  
-通过世界脚本组机制,开发者可以利用其实现很多灵活的功能扩展更多有关内容请访问[【世界空间及场景机制】](developer/mod/scene)+常规情况下,应优先考虑**世界脚本API**来实现主要逻辑需要**客户端本地**相内容,才在**客户端脚本**进行设计和实现
  
 +> **正确举例:**制作一个载具的进度条(或仪表盘),首先在客户端创建一个进度条GUI,然后创建一个更新函数,在客户端每帧进行更新,直接从客户端获取`Vehicle.Speed`进行进度条赋值,这样每帧更新且没有卡顿。
  
 +> **错误举例:**同上,制作一个GUI进度条,在服务端判断玩家上车后,在帧循环事件中给玩家发送**自定义客户端数据(ServerToClientData)**,参数附带`Character.Vehicle.Speed`,客户端收到后把参数赋值给进度条Progress。这样虽然能实现**相同的数据效果**,但进度条会因**网络延迟**等导致**显示不流畅**(虽然可以通过**插值方式**处理,但有些功能并不适合服务端开发,通常应该避免这种**快速每帧、定时循环**发送客户端数据的情况),并且造成了不必要的**网络通讯**开销浪费。
  
-### 脚本分配指引 
  
-在准备开始进行脚本开发及设计时,一定要**做好客户端和世界端的功能分配**,进行合理设计功能API交互解耦,避免出现**过度相互依赖、粘合**的情况。 
  
-常规情况下,建议开发者**优先考虑世界端API来实现主要逻辑**,可能需要帧同步或者确实需要客户端执行内容,才放到客户端脚本内进行编写或接收交互。+### 功能开发的常规步骤
  
-> **正举例:**制作一个载具进度条(或仪表盘)首先在客户端创建一个进度条GUI,然后创建一个更新函数,在客户端Update Event进行调用,直接从客户端获取Vehicle.Speed进行进度条赋值,这样每帧更新且没有卡顿。+1. 需求分析:定想要实现功能对其进行简单的描述
  
-> **错误举例:**同上制作一个GUI进度条,然后在服务端判断玩家上后,帧循环给家发送**自定义客户端数据(ServerToClientData)**参数附带Character.Vehicle.Speed,客户端收到后把参数赋值给进度条Progress。这样虽然能实现相同的数据效果进度条会因网络等原因不流畅(虽然可以通过Lerp方式处理但这个例子说明有功能并不适合服务端脚本开发甚至应该避免这种快速每帧、定时循环发送客户端据的为)。+   想要设计和实现一个需要清楚规则是什么路线什么车参赛怎么安排,比赛怎么进
  
 +2. 设计:根据需求分析,在开发层面进行抽象、分类和归纳,总结出需要存在哪些数据,功能,流程
  
 +   例如:设计赛车的路线由一个个点连接组成,参赛者用列表统一管理,参赛车有什么属性、功能,人数限制的功能描述,什么时候在什么地方会发生什么事(流程)等
  
-### 脚本逻辑指引+3. 实现:用具体的代码和环境等来实现以上的设计(也就是编写代码)
  
-沙盘引擎脚本功能设计偏向于==【世界端主逻辑】==。+   例如:赛车的路线用Checkpoint(检查点)来实现,参赛者用数组的方式实现存储,以及自定义的各种函数来实现具体的功能等等
  
-也就是说**能在World端实的功,不要在其他脚本执**,Client端的API比较有限(更多是客户端本地内容,而不是客户端世界内容),**主要的世界玩法功能API应该在World端得到体现**。+4. 调试:开发的最后一步及时发异常,确保项目正常运
  
-> **例如:**开发希望实现“当色手中的物体变成了【马桶】时在此玩家视角里弹出一个GUI窗口上面可能写着一些文本或按钮” +   例如:从使用者角对各个功能进行实际上手测试黑盒测试;从开发者角度对代码可能存在的隐患和边界条进行实际测试白盒测试
-+
-> 在沙盘引擎的脚本开发设计下**正确做法应该是:应该首先World端找到`当角色手中武器被改变`的事,然后检查角色是否为玩家、角色手中物品是否为【马桶】,然后发送一条自定义网络消息(或使用客户端反射API)告知客户端打开指定GUI界面。(关闭GUI逻辑相同,只需判定中物品不再是马桶)** +
-+
-> **反例补充:**同样,你不应该在Client脚本中也可能根本没有类似的事件实现这一检查逻辑,绝大多数**游戏世界功能**都应优先考虑World脚本,而不是Client脚本,同时相对避免出现Server&Client双核大脑开发逻辑冲突耦合等问题。+
  
  
行 72: 行 58:
 ## 脚本文件 ## 脚本文件
  
-沙盘引擎的**所有模组脚本**均在`模组目录/Script/XXX脚本组`目录。+沙盘引擎的**所有模组脚本**均在模组目录下`Script/XXX`目录。
  
-其中入口脚本主要分为两个组成,分别是**Client.js****World.js**两个脚本+其中**入口脚本**有两个,分别是`World.js``Client.js`
  
-| 脚本名称  | 说明                                                         |+| 脚本      | 说明                                                         |
 | --------- | ------------------------------------------------------------ | | --------- | ------------------------------------------------------------ |
 +| World.js  | 主动入口脚本<br />负责一切与主世界相关的内容(服务端权威,如世界时间、天气、其他服务端及玩法类逻辑) |
 | Client.js | 主动入口脚本<br />负责一切与本地客户端相关的内容(如GUI、相机视角、本地事件、本地世界事件等) | | Client.js | 主动入口脚本<br />负责一切与本地客户端相关的内容(如GUI、相机视角、本地事件、本地世界事件等) |
-| World.js  | 主动入口脚本<br />负责一切与主世界相关的内容(服务端权威,如世界时间、天气、其他服务端及玩法类逻辑) | 
- 
-**需要特别注意,因为客户端和世界端(服务端)逻辑同步有很多不同的地方,所以两种脚本的Event、Function并非是通用的。** **(两个脚本分别是不同的工作空间)** 
  
-具体Client、World脚本开发API,请参考**脚本开发**文档下子分类有些API可能很相似,除非完全一样,否则是不能通用的。+特别注意:两种脚本的作用空间**不是互通**的,只在各自代码空间生效
 > >
-> 除此之外,也有一些**原生通用Native**的代码API,这种通常是可以在客户端和主世界通用的代码+> 除此之外,也有一些**原生通用Native**的代码API,此分类下的代码可以在两种脚本空间通用,但数据通常不互通
  
  
行 91: 行 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进行**加载脚本**,否则没有作用(抛砖引玉通过此机制实现模块化开发)。
  
  
行 114: 行 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`分别有各自的激活场景和作用。
行 168: 行 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端更多是在有【创建本地服务器\主机游戏】需求时才必须存在的。* 
  
  
行 184: 行 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场景加载后执行,所以也被习惯性称之为世界端。 +
- +
-世界端代码包含大量与世界可见内容息息相关的部分,通常想调整世界内玩法相关的内容,均需要在世界端进行处理。 +
- +
-==世界端脚本只在主机玩家(或单人游戏本地玩家)执行,所以是服务器权威(网络同步概念)==+
  
  
行 234: 行 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文档将被分成几个**主要部分**,分别是**原生及通用代码**、**客户端代码(Client)**、**世界端代码(World)**。
  
-关脚本API文档将被分成3个主要部分,分别是`原生及通用代码`、`客户端代码Client`、`世界端代码World`+公开范围内所`Event`(事件)、`Property`(属性)、`Function`(方法)等代码都将在**以上三个文档内**记录呈现
  
-公开范围内所有的Event(事件)、Property(属性)、Function(方法)等代码都将在**以上三个文档内**记录呈现,部分没有被详细介绍的API开发者可自行探索,技多不压身。 +> 文档中部分代码(类)实际上可能是**静态类名**,这里需要根据API文档内**首字母大小写**进行区分(`Timer`是一个静态类,而`timer`则是一个生成的实例)。
- +
-> 文档中部分代码(类)实际上可能是**静态类名**,这里需要根据API文档内**【代码首字母大小写**进行区分(如`Timer`是一个静态类,而`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 +
-```+
  
  
行 301: 行 196:
 ### 脚本API文档标题 ### 脚本API文档标题
  
-由于引擎主要功能是由World、Client两个主类下的众多子类实现控制的,所以脚本文档采用了XXX 类参考的命名方式。+由于引擎主要功能是由`World``Client`两个主类下的众多子类实现控制的,所以**脚本文档**采用了**XXX 类参考**的命名方式。
  
-也就是说,例如你想查找一个关于世界下的玩家方法,那么应该前往[世界脚本——Player 类参考](scripting/world/player "世界脚本——Player 类参考")进行查找。+也就是说,例如查找关于**世界脚本**下的玩家方法,那么应该前往[世界脚本——Player 类参考](scripting/world/player "世界脚本——Player 类参考")进行查找。
  
 **除此之外,每个类型参考文档内拥有多个H1、H2标题进行分类,通常会按照以下命名进行区分。** **除此之外,每个类型参考文档内拥有多个H1、H2标题进行分类,通常会按照以下命名进行区分。**
行 320: 行 215:
 沙盘引擎使用`JavaSciprt`作为开发脚本语言,所以开发者**可通过任何文本编辑器进行脚本编写**。 沙盘引擎使用`JavaSciprt`作为开发脚本语言,所以开发者**可通过任何文本编辑器进行脚本编写**。
  
-脚本开发时并**不需要专门的IDE以及编译器**,只需要进行**增删查改**及修改后的**保存“文本”**即可,待下次引擎入脚本时将会**自动编译**。+脚本开发时并**不需要专门的IDE以及编译器**,只需要进行**增删查改**及修改后的**保存“文本”**即可,重新建立世界(重连、重时将会**自动动态编译**。
  
  
行 326: 行 221:
 ## 脚本开发基础入门 ## 脚本开发基础入门
  
-> 注意:此处指的**基础入门**并==不是==`JavaScript`语言的基础教程,而是指引已有编程逻辑基础的开发者,快速进行了解脚本代码的一些差异和规范。+> 注意:此处指的**基础入门**并==不是==`JavaScript`语言的基础教程,而是指引已有**编程基础**的开发者,快速进行了解脚本代码的一些**差异和规范**
 > >
-> 如果你是一位编程初学者或`JavaScript`初学者,可以考虑参考以下视频教程。+> 如果你是一位编程初学者或`JavaScript`初学者,可以考虑参考以下**视频教程**
 > >
 > 四十分钟JavaScript快速入门:https://www.bilibili.com/video/BV15L4y1a7or > 四十分钟JavaScript快速入门:https://www.bilibili.com/video/BV15L4y1a7or
 > >
-> 哔哩哔哩JavaScript入门教程搜索:[https://search.bilibili.com/all?keyword=javascript 入门](https://search.bilibili.com/all?keyword=javascript 入门)+> 哔哩哔哩JavaScript入门教程搜索:https://search.bilibili.com/all?keyword=javascript
 > >
  
-《沙盘引擎》的开发初衷就是为了**精简开发流程以及代码的复杂程度**,所以实际上对引擎模组脚本的基础开发**并不需要非常过硬的编程技术**+《沙盘引擎》的开发初衷就是为了**精简开发流程以及代码的复杂程度**,所以实际上对**模组脚本**的基础开发并不需要非常过硬的编程技术。
  
-如果开发者曾对`Pawn`、`Squirrel`或`其他高级编程语言`有相关经验,甚至不需要完全学习`JavaScript`,只需掌握基础的语法使用即可尝试使用+如果开发者曾对`Pawn`、`Squirrel`或**其他编程语言**有相关经验,甚至不需要完全学习`JavaScript`,只需掌握**基础的语法**使用即可开始体验
  
  
行 359: 行 254:
  
  
-### 类名区分及使用+### 默认空间
  
-在沙盘引擎的函数方法设计中,都是以Class类的方式的,也就是无论任何功能调用,均是从某个`Class类`来激活使用的+为了脚本开发思路更加清晰,所以引擎采用了**必要名前缀**调用方式际上类似于**命名空间**概念,。
  
-**由此得知,绝大多数情况下名的前缀是必须的。(除World.js下的SE.World分类等例子,因为本来就是在类下**+| 脚本环境         | 默认类   | 示例                                                     | 
 +| ---------------- | -------- | -------------------------------------------------------- | 
 +| `Client.js`      | `Client` | `Client.Core.XXXXX()`                                    | 
 +| `World.js`       | `World`  | `World.Core.XXXXX()`                                     | 
 +| 通用(`Native`) | `-`      | `CreateHost()` <br />`Random.Range()`<br />`Debug.Log()` |
  
-```javascript +> 注意:`Client.js``World.js`是**完全不同**的两个**程序空间**,即使有些内容(如`Player`类)命名相同,但不代表在底层是基于同一个类。
-//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下<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环境下具体用法可能有差异(因为根本严格意义上不是一个类),有些功能是客户端专用,有些是世界端专用,也有部分是通用的功能。**== 
  
  
  
 </markdown> </markdown>