7月31日同步更新
0
mcguide/27-手机网络游戏/课程5:插件教学/README.md
Normal file
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chajian_01.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chajian_02.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chajian_03.png
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chajian_04.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chajian_05.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chajian_06.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chat_01.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chat_02.png
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chat_03.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chat_04.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chat_05.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chat_06.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/chat_07.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_01.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_02.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_03.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_04.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_05.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_06.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_07.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_08.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_09.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_10.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_11.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_12.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_13.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_14.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_15.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_16.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_17.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_18.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_19.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_20.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_21.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_22.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_23.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_24.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_25.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_26.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_27.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/ditu_28.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/image-20220411162929246.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/image-20220411163053963.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/image-20220411163207919.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/image-20220411163546702.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/image-20220411163641320.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/image-20220411163922869.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
mcguide/27-手机网络游戏/课程5:插件教学/images/plugin_guide.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
32
mcguide/27-手机网络游戏/课程5:插件教学/第0节:使用工作台新建插件.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/chajian_01.bad9625d.png
|
||||
hard: 基础
|
||||
time: 5分钟
|
||||
---
|
||||
|
||||
# 使用工作台新建插件
|
||||
这个文档描述了如何使用工作台新建插件。
|
||||
|
||||
1. 在工作台的“创作”分页,点击新建基岩版服务器
|
||||
|
||||

|
||||
|
||||
2. 点击空白插件的新建按钮,以打开新建插件的弹窗。
|
||||
|
||||

|
||||
|
||||
3. 空白插件的说明是新建插件的简单介绍,下面的详细说明是插件的B站视频教程的链接。
|
||||
|
||||

|
||||
|
||||
4. 在基岩版服务器 - 插件分页里也有新建插件按钮,点击这个也可以弹出弹窗。
|
||||
|
||||

|
||||
|
||||
5. 新建插件的弹窗如下,输入团队名称,插件名称等,并进行勾选,点击创建即可完成一个空白插件的创建。
|
||||
|
||||

|
||||
|
||||
6. 创建完成的插件会直接显示在工作台中。
|
||||
|
||||

|
||||
509
mcguide/27-手机网络游戏/课程5:插件教学/第1节:官网插件规范.md
Normal file
@@ -0,0 +1,509 @@
|
||||
---
|
||||
front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/chajian_01.bad9625d.png
|
||||
hard: 进阶
|
||||
time: 20分钟
|
||||
---
|
||||
|
||||
# 官网插件规范
|
||||
这个文档描述了官网公开插件(即公开下载并许可任意服主使用的插件)的开发规范,自用插件也建议参照此规范。
|
||||
|
||||
## 规范主旨
|
||||
* 减少插件间的冲突问题(如界面重叠、目录或文件冲突、无法加载等)
|
||||
* 统一规范,增加可读性
|
||||
* 便于使用和二次开发
|
||||
|
||||
## 重要名词解释
|
||||
|
||||
### 团队名
|
||||
* 每个官网公开插件的开发团队,都需要一个全局唯一的团队名,比如官方的团队名为**netease**
|
||||
* 团队名仅能使用小写英文字符+数字,并且不能以数字开头,团队名限制最大字符数为10
|
||||
### 插件名
|
||||
* 每个插件都需要有一个与插件具体实现的功能相关的插件名,比如官方的公告插件的插件名为**announce**
|
||||
* 插件名仅能使用小写英文字符+数字,并且不能以数字开头,插件名限制最大字符数为20
|
||||
|
||||
|
||||
## 插件目录命名规范
|
||||
### 根目录命名
|
||||
插件【强制要求】按照运行的具体服务器类型(游戏服、功能服等)分目录,【强制要求】每种服务器都需要创建一个目录,目录【强制要求】使用驼峰法命名,【强制要求】以**团队名**开头,具体规范如下:
|
||||
|
||||
- 大厅服/游戏服 插件目录名规范:团队名(全小写)+ 插件名(首字母大写)
|
||||
- 控制服 插件目录名规范:团队名(全小写)+ 插件名(首字母大写) + Master
|
||||
- 功能服 插件目录名规范:团队名(全小写)+ 插件名(首字母大写) + Service
|
||||
|
||||
假设**团队名**为**lovecraft**,**插件名**为**guild**,功能涉及到master、service和lobby,目录结构如下所示:
|
||||
|
||||
```
|
||||
|
||||
├─lovecraftGuildMaster
|
||||
├─lovecraftGuild
|
||||
├─lovecraftGuildService
|
||||
|
||||
```
|
||||
### 大厅服/游戏服目录命名
|
||||
【强制要求】大厅服/游戏服的目录结构类似。以**团队名**为**lovecraft**,**插件名**为**guild**为例介绍目录结构,其目录结构如下所示:
|
||||
```
|
||||
lovecraftGuild
|
||||
│ mod.sql
|
||||
│ readme.txt
|
||||
│ server.properties
|
||||
│
|
||||
├─behavior_packs
|
||||
├─developer_mods
|
||||
├─resource_packs
|
||||
├─worlds
|
||||
└─studio_res
|
||||
|
||||
```
|
||||
各个目录和文件的说明如下:
|
||||
名字|含义|是否必须
|
||||
---|:---|---:
|
||||
mod.sql|记录需要执行的sql语句,使用utf-8编码|否
|
||||
readme.txt|mod介绍,使用utf-8编码|是
|
||||
server.properties|mc server配置|否
|
||||
behavior_packs|存放行为包|否
|
||||
developer_mods|存放服务端mod|否
|
||||
resource_packs|存放资源包|否
|
||||
worlds|存放地图|否
|
||||
studio_res|存放美术资源工程|否
|
||||
#### behavior_packs目录
|
||||
* 行为包使用的根目录【强制要求】以**团队名**+**插件名**开头,并以**Behavior**(或**behavior**)结尾、中间可以添加任意英文字符+数字,【建议】以驼峰法区隔(首字符大写),【建议】在非必须的情况下,直接以**团队名**(全小写)+**插件名**(首字母大写)+**Behavior**命名。
|
||||
* 代码的根目录,【强制要求】以**团队名**+**插件名**开头,并以**Script**(或**script**)结尾、中间可以添加任意英文字符+数字,【建议】以驼峰法区隔(首字符大写),【建议】在非必须的情况下,直接以**团队名**(全小写)+**插件名**(首字母大写)+**Script**命名。
|
||||
|
||||
假设**团队名**为**lovecraft**,**插件名**为**guild**,示例如下:
|
||||
```
|
||||
behavior_packs
|
||||
└─lovecraftGuildBehavior
|
||||
│ manifest.json
|
||||
│
|
||||
└─lovecraftGuildScript
|
||||
```
|
||||
各个目录和文件的说明如下:
|
||||
名字|含义
|
||||
---|:---
|
||||
lovecraftGuildBehavior|行为包的根目录。
|
||||
manifest.json|行为包manifest配置
|
||||
lovecraftGuildScript|代码文件的根目录,从此目录开始import模块。
|
||||
#### developer_mods目录
|
||||
* 根目录【强制要求】以**团队名**+**插件名**开头,并以**Dev**(或**dev**)结尾、中间可以添加任意英文字符+数字,【建议】以驼峰法区隔(首字符大写),【建议】在非必须的情况下,直接以**团队名**(全小写)+**插件名**(首字母大写)+**Dev**命名。
|
||||
* 代码的根目录,【强制要求】以**团队名**+**插件名**开头,并以**Script**(或**script**)结尾、中间可以添加任意英文字符+数字,【建议】以驼峰法区隔(首字符大写),【建议】在非必须的情况下,直接以**团队名**(全小写)+**插件名**(首字母大写)+**Script**命名。
|
||||
|
||||
假设**团队名**为**lovecraft**,**插件名**为**guild**,示例如下:
|
||||
```
|
||||
developer_mods
|
||||
└─lovecraftGuildDev
|
||||
│ mod.json
|
||||
└─lovecraftGuildScript
|
||||
```
|
||||
名字|含义
|
||||
---|:---
|
||||
lovecraftGuildDev|服务端mod的根目录。
|
||||
lovecraftGuildScript|代码文件的根目录,从此目录开始import模块。
|
||||
mod.json|记录插件的全部配置,该配置能被开发者改动。
|
||||
#### resource_packs目录
|
||||
* 根目录【强制要求】以**团队名**+**插件名**开头,并以**Res**(或**res**)结尾、中间可以添加任意英文字符+数字,【建议】以驼峰法区隔(首字符大写),【建议】在非必须的情况下,直接以**团队名**(全小写)+**插件名**(首字母大写)+**Res**命名。
|
||||
```
|
||||
resource_packs
|
||||
└─lovecraftGuildRes
|
||||
| manifest.json
|
||||
└─ui
|
||||
└─textures
|
||||
```
|
||||
名字|含义
|
||||
---|:---
|
||||
lovecraftGuildRes|资源包的根目录。
|
||||
manifest.json|资源包manifest配置
|
||||
ui|UI的JSON文件的根目录。
|
||||
textures|各种贴图资源的根目录。
|
||||
#### worlds目录
|
||||
* 【强制要求】在**worlds**目录下创建命名为**level**的目录
|
||||
* 【强制要求】在level目录下,如果有行为包则必须有**worlds/level/world_behavior_packs.json**;如果有资源包则必须有**worlds/level/world_resource_packs.json**
|
||||
```
|
||||
worlds
|
||||
└─level
|
||||
| world_behavior_packs.json
|
||||
| world_resource_packs.json
|
||||
```
|
||||
* 【强制要求】world_behavior_packs.json 文件中定义必须与行为包中 manifest.json 的modules对应
|
||||
```
|
||||
[
|
||||
{
|
||||
"pack_id" : "d09c6ae2-e9b7-4640-b850-942678294b72",
|
||||
"version" : [ 0, 0, 1122]
|
||||
}
|
||||
]
|
||||
```
|
||||
* 【强制要求】world_resource_packs.json 文件中定义必须与资源包中 manifest.json 的modules对应
|
||||
```
|
||||
[
|
||||
{
|
||||
"pack_id" : "bab4cb59-b369-45b1-abbd-3ab43b6f86fa",
|
||||
"version" : [ 0, 0, 1122]
|
||||
}
|
||||
]
|
||||
```
|
||||
#### studio_res目录
|
||||
* 【强制要求】这个目录对服务器或客户端MOD运行没有任何影响,只是用于保存当前插件所使用到的资源工程导出文件
|
||||
* 【强制要求】其目录结构如下,直接放置对应的资源工程导出文件
|
||||
```
|
||||
studio_res
|
||||
├─主菜单插件.zip
|
||||
|
||||
```
|
||||
|
||||
### 控制服/功能服目录命名
|
||||
只会包含developer_mods,与大厅服/游戏服的developer_mods目录规范相同
|
||||
|
||||
### mod.sql文件编写规范
|
||||
* 【强制要求】若插件功能涉及多个服务器,则mod.sql只用放到一种类型服务器的下面,mod.sql存放优先级是:功能服>大厅服/游戏服>控制服。
|
||||
* 【强制要求】请使用Mysql数据库实现长时效信息存储
|
||||
* 【强制要求】使用InnoDB,编码为utf8mb4
|
||||
* 【建议】每个字段必须有注释。
|
||||
* 【强制要求】文件要求是utf-8编码。
|
||||
* 表名【强制要求】**团队名**+**插件名**开头,中间可以使用**下划线**连接,但【建议】以驼峰法区隔(首字符大写);【建议】比如说**团队名**为**lovecraft**,**插件名**为**guild**的插件,数据库表名均应类似于**lovecraftGuildXx**
|
||||
* 【强制要求】所有版本sql语句都放到mod.sql中,用注释分割不同mod版本的使用到的sql。一个示例如下:
|
||||
```sql
|
||||
-- ###########################version1.0.0####################
|
||||
create table lovecraftGuild(
|
||||
id int unsigned not null COMMENT '唯一id'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
-- ###########################version1.0.1####################
|
||||
create table lovecraftGuild2(
|
||||
id int unsigned not null COMMENT '唯一id'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
### mod.json文件编写规范
|
||||
通常需要包含下面配置:
|
||||
属性|含义
|
||||
---|:---
|
||||
netgame_mod_name|插件名字
|
||||
netgame_mod_version|插件版本,版本要求从“1.0.0”开始
|
||||
min_app_version|最低引擎版本
|
||||
max_app_version|最高版本引擎,没配置则表示不限
|
||||
support_server_type|列表,支持的服务器类型,包括game/lobby/master/service几种类型
|
||||
author|团队名
|
||||
group|可以不配置,表示插件隶属的功能。若一个功能涉及多个插件,则建议将多个插件group设置成一样的。
|
||||
module_names(service mod需要)|配置当前service mod的模块名,要求与插件名一样,并且能够在deploy.json中service下module_names配置中找到。用于Service的RPC调用,对应RegisterRpcMethodForMod和RequestToService中的参数module。
|
||||
|
||||
【强制要求】所有能被改动属性都配置到该文件,可以用"_comment"给字段加注释。
|
||||
假设**团队名**为**lovecraft**,**插件名**为**guild**,一个简单示例如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"netgame_mod_name":"lovecraftGuild",
|
||||
"netgame_mod_version":"1.0.0",
|
||||
"min_app_version":"1.15.0",
|
||||
"support_server_type":["lobby"],
|
||||
"author":"lovecraft",
|
||||
"group":"lovecraftGuild",
|
||||
|
||||
"_comment":"公会名称最多支持多少个字符",
|
||||
"guild_name_limit":20,
|
||||
...
|
||||
}
|
||||
```
|
||||
### readme.txt文件编写规范
|
||||
【强制要求】"type"用法规范:它是python变量的类型名,统一使用下列类型名 bool、int、float、double、str、dict、tuple、list(str)、list(int)、object、function、User Defined Class Name。
|
||||
|
||||
【强制要求】文件格式:缩进用空格,不用tab;要求用utf-8格式。
|
||||
|
||||
【强制要求】文件构成如下:
|
||||
|
||||
- 插件介绍
|
||||
- 插件构成
|
||||
- 使用步骤
|
||||
- 插件api
|
||||
```
|
||||
(1)api功能描述
|
||||
适用范围:客户端/大厅服/游戏服/控制服/功能服
|
||||
函数:funcname(arg1,args2...)
|
||||
参数:
|
||||
(如果没有参数,也写 无 )
|
||||
arg1: type,描述
|
||||
arg2: type,描述
|
||||
返回:
|
||||
(如果没有返回,也写 无 )
|
||||
int, test的返回值
|
||||
示例:
|
||||
a = funcname(1,2)
|
||||
```
|
||||
- 插件event
|
||||
```
|
||||
(1)event名字
|
||||
适用范围:客户端/大厅服/游戏服/控制服/功能服
|
||||
命名空间:namespace = 'xxx', systemname = 'xxx'
|
||||
描述:event的描述
|
||||
参数:
|
||||
(如果没有参数,也写 无 )
|
||||
arg1: type, arg1描述
|
||||
```
|
||||
- 运营指令
|
||||
```
|
||||
运营指令功能介绍
|
||||
post url: http:masterip:masterport/baseurl
|
||||
post body:{
|
||||
"key" : value#注释
|
||||
}
|
||||
response:
|
||||
{
|
||||
"code": 1, #code=1表示成功,其他表示失败
|
||||
"entity": {
|
||||
"key": value #注释
|
||||
},
|
||||
"message": ""
|
||||
}
|
||||
```
|
||||
- 更新记录
|
||||
|
||||
假设**团队名**为**lovecraft**,**插件名**为**guild**,一个示例如下:
|
||||
```
|
||||
插件介绍:这是个公会插件
|
||||
|
||||
插件构成:
|
||||
(1)lovecraftGuild: 部署于大厅服或游戏服。
|
||||
(2)lovecraftGuildMaster: 部署于控制服。
|
||||
(3)lovecraftGuildService: 部署于功能服。
|
||||
|
||||
使用步骤:
|
||||
1、请在mysql中执行mod.sql
|
||||
2、配置mod.json,请按照文件mod.json中"_comment"注释配置对应内容。
|
||||
3、MCStudio把lovecraftGuild添加到大厅服或游戏服。
|
||||
4、MCStudio把lovecraftGuildMaster添加到控制服。
|
||||
5、MCStudio把lovecraftGuildService添加到功能服。
|
||||
|
||||
插件api:
|
||||
(1)获取玩家昵称api
|
||||
适用范围:大厅服/游戏服
|
||||
函数:GetNickname(uid)
|
||||
参数:
|
||||
uid: int,玩家id
|
||||
返回:
|
||||
string,玩家的昵称
|
||||
示例:
|
||||
import server.extraServerApi as serverApi
|
||||
system = serverApi.GetSystem("lovecraftGuild", "lovecraftGuildDev")
|
||||
system.GetNickname(123)#获取玩家的昵称
|
||||
|
||||
插件event:
|
||||
(1)GuildEvent
|
||||
适用范围:大厅服/游戏服
|
||||
命名空间:namespace = 'lovecraftGuild', systemname = 'lovecraftGuildDev'
|
||||
描述:测试的event
|
||||
参数:
|
||||
uid: int,玩家id
|
||||
nickname: str,玩家昵称
|
||||
示例:
|
||||
def __init__(self, namespace, systemName):
|
||||
self.ListenForEvent('lovecraftGuild', 'lovecraftGuildDev', 'GuildEvent', self, self.OnGuildEvent)
|
||||
|
||||
def OnGuildEvent(self, data):
|
||||
uid = data['uid']
|
||||
nickname = data['nickname']
|
||||
|
||||
运营指令:
|
||||
(1)test指令
|
||||
post url: http:masterip:masterport/test
|
||||
post body:{
|
||||
"uid" : 123#玩家id
|
||||
}
|
||||
response:
|
||||
{
|
||||
"code": 1,
|
||||
"entity": {
|
||||
"name": "player_name" #玩家昵称
|
||||
},
|
||||
"message": ""
|
||||
}
|
||||
|
||||
更新列表:
|
||||
1.0.1版本:
|
||||
新增指令/test
|
||||
```
|
||||
|
||||
### server.properties文件编写规范
|
||||
|
||||
原版minecraft server中含有一个同名的设置文件,里面可配置的字段可参考[wiki文档](https://zh.minecraft.wiki/w/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F)
|
||||
|
||||
可以在插件中放置一个server.properties,在部署时会自动拼接到服务器的server.properties中,这意味着您可以只写一部分所需的属性
|
||||
|
||||
需要注意在文件的**末尾需要有一个空行**
|
||||
|
||||
需要特别提一下client-side-chunk-generation-enabled这个属性:这是2.5版本后新增的字段,用来控制是否开启客户端本地的地图生成。如果您的服务器地图大部分都不是自然生成的(除了生存服应该都是这种情况),请把这个属性配置为false
|
||||
|
||||
以下是一个server.properties示例(注意最后有一个空行):
|
||||
|
||||
```yml
|
||||
client-side-chunk-generation-enabled=false
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 美术资源命名规范
|
||||
|
||||
由于Windows的操作系统,文件名与路径名是无视大小写的,但是手机的操作系统,文件名和路径名都是区分大小写的,考虑到studio的开发环境和最终手机环境的一致性,所以在美术资源的命名中,在目录和文件名命名规则中不使用驼峰法,【强制要求】使用**小写英文字符**并以**下划线**连接。
|
||||
### UI界面
|
||||
#### JSON文件
|
||||
* 【强制要求】务必使用studio自带的界面编辑器生成UI的JSON文件,并保留UI的工程文件
|
||||
* 【强制要求】所有的UI的JSON文件,均需要以**团队名**(全小写)+**下划线**+**插件名**(全小写)+**下环线**开头
|
||||
|
||||
假设**团队名**为**lovecraft**,**插件名**为**guild**,示例如下:
|
||||
```
|
||||
resource_packs
|
||||
└─lovecraftGuildRes
|
||||
| manifest.json
|
||||
└─ ui
|
||||
| _ui_defs.json
|
||||
| lovecraft_guild_create.json
|
||||
| lovecraft_guild_info.json
|
||||
└─ ui
|
||||
└─ textures
|
||||
```
|
||||
名字|含义
|
||||
---|:---
|
||||
_ui_defs.json|记录了一共有哪些json文件,由studio的界面编辑器自动生成。
|
||||
lovecraft_guild_create.json|界面的json描述文件,对应编辑器中的【lovecraft_guild_create】界面。
|
||||
lovecraft_guild_info.json|界面的json描述文件,对应编辑器中的【lovecraft_guild_info】界面。
|
||||
#### 界面贴图资源
|
||||
【强制要求】所有UI使用到的贴图资源,均需要放置在以**团队名**(全小写)+**下划线**+**插件名**(全小写)命名的子目录下
|
||||
|
||||
假设**团队名**为**lovecraft**,**插件名**为**guild**,示例如下:
|
||||
```
|
||||
resource_packs
|
||||
└─lovecraftGuildRes
|
||||
| manifest.json
|
||||
└─ ui
|
||||
└─ textures
|
||||
└─ ui
|
||||
└─ lovecraft_guild
|
||||
| btn01@3x.png
|
||||
| ...
|
||||
└─ ...
|
||||
```
|
||||
名字|含义
|
||||
---|:---
|
||||
lovecraft_guild|插件UI贴图的根目录。
|
||||
btn01@3x.png|某个按钮用到的图片。
|
||||
|
||||
### 模型与特效资源
|
||||
* 【强制要求】模型资源经过编辑器导出之后,会分解到多个目录且各个文件中有一定的相关性,命名依旧要求以**团队名**(全小写)+**下划线**+**插件名**(全小写)+**下环线**开头。
|
||||
* 【强制要求】特效资源同样要经过编辑器导出,命名依旧要求以**团队名**(全小写)+**下划线**+**插件名**(全小写)+**下环线**开头。
|
||||
|
||||
假设**团队名**为**lovecraft**,**插件名**为**guild**,那么要求的前缀为**lovecraft_guild_**,示例如下:
|
||||
```
|
||||
resource_packs
|
||||
└─lovecraftGuildRes
|
||||
| manifest.json
|
||||
└─ ui
|
||||
└─ textures
|
||||
└─ effects
|
||||
| lovecraft_guild_guanghuan_01.json
|
||||
└─ ...
|
||||
└─ models
|
||||
└─ animation
|
||||
| lovecraft_guild_chibang_animation_idle.json
|
||||
└─ ...
|
||||
└─ mesh
|
||||
| lovecraft_guild_chibang_mesh.json
|
||||
└─ ...
|
||||
└─ skeleton
|
||||
| lovecraft_guild_chibang_skeleton.json
|
||||
└─ ...
|
||||
netease_models.json
|
||||
```
|
||||
名字|含义
|
||||
---|:---
|
||||
lovecraftGuildRes/effects|放置序列帧特效与粒子特效文件的根目录
|
||||
lovecraft_guild_guanghuan_01.json|描述一个序列帧特效的json文件
|
||||
lovecraftGuildRes/effects/models/animation| 放置模型动作文件的根目录
|
||||
lovecraft_guild_chibang_animation_idle.json|描述一个翅膀模型的idle动作的json文件
|
||||
lovecraftGuildRes/effects/models/mesh|放置模型mesh文件的根目录
|
||||
lovecraft_guild_chibang_mesh.json|描述一个翅膀模型的mesh的json文件
|
||||
lovecraftGuildRes/effects/models/skeleton|放置模型骨骼文件的根目录
|
||||
lovecraft_guild_chibang_skeleton.json|描述一个翅膀模型的骨骼的json文件
|
||||
netease_models.json|模型信息统合文件,由studio编辑器生成和维护
|
||||
|
||||
## 自定义物品与实体命名规范
|
||||
* 自定义物品/实体的namespace,【强制要求】使用**团队名**+**插件名**的方式命名,中间可以使用**下划线**连接,但【建议】以驼峰法区隔(首字符大写);【建议】假设**团队名**为**lovecraft**,**插件名**为**guild**,那么此插件的自定义物品/实体的**identifier**必须类似于**lovecraftGuild:xx**
|
||||
* 自定义物品/实体的各种json配置文件,【强制要求】以**团队名**(全小写)+**下划线**+**插件名**(全小写)+**下环线**开头;【强制要求】假设**团队名**为**lovecraft**,**插件名**为**guild**,那么描述此插件的自定义物品/实体的json文件,必须类似于**lovecraft_guild_xx.json**
|
||||
|
||||
## 界面开发注意事项
|
||||
### 注意隐藏/显示界面的方式
|
||||
【强制要求】需要使用``uiNode.SetScreenVisible(True/False)``的方式,显示/隐藏整个json界面,而不是使用SetVisible界面中的全部控件的方式
|
||||
### 注意界面分层
|
||||
* 【强制要求】插件的所有**弹出**界面(即会阻挡游戏默认的摇杆、攻击操作的界面)都需要根据所处的位置和用途,分类到不同界面层中,并在初始化后使用``uiNode.SetLayer("", int)``设置自身的所在的层次
|
||||
* 界面层次的宏定义位于minecraftEnum.py中
|
||||
```python
|
||||
class UiBaseLayer(object):
|
||||
"""
|
||||
@description 自定义UI界面的层次宏定义,用于在多个插件之间协调UI界面的遮挡关系
|
||||
@author xltang
|
||||
@version 1.21
|
||||
@state 1.21 新增 xltang 自定义UI界面的层次宏定义
|
||||
"""
|
||||
Desk = 0 # 主界面常驻,无需SetLayer
|
||||
DeskFloat = 15000 # 主界面浮动提示(浮动提示信息),无需SetLayer,使用编辑器设置layer的方式调整层次
|
||||
PopUpLv1 = 25000 # 一级弹出界面,必须SetLayer
|
||||
PopUpLv2 = 45000 # 二级弹出界面,必须SetLayer
|
||||
PopUpModal = 60000 # 模态弹出界面(弹出提示),必须SetLayer
|
||||
PopUpFloat = 75000 # 模态弹出之上的浮动提示(大喇叭),无需SetLayer,使用编辑器设置layer的方式调整层次
|
||||
```
|
||||
* 界面创建的示例代码
|
||||
```python
|
||||
import client.extraClientApi as clientApi
|
||||
clientApi.RegisterUI("neteaseAppear","shop","neteaseAppearScript.appearShopUi.ShopScreen","netease_appear_shopUI.main")
|
||||
clientApi.CreateUI("neteaseAppear", "shop", {"isHud" : 1})
|
||||
shopUI = clientApi.GetUI("neteaseAppear", "shop")
|
||||
shopUI.SetLayer("", clientApi.GetMinecraftEnum().UiBaseLayer.PopUpLv1)
|
||||
shopUI.SetScreenVisible(False)
|
||||
```
|
||||
#### 界面所属层次示例
|
||||
* 界面到底应该属于哪一个层和具体的需求、界面交互逻辑相关,并没有唯一的标准。
|
||||
* 二级弹出界面与模态弹出界面,并没有明确的区分标准,一般来说只在某个特定一级弹出界面上出现的是二级弹出界面,而在多种情况下都可能显示的通用提示确认界面是模态弹出界面
|
||||
* 二级弹出界面可以与对应的一级弹出界面位于同一个JSON上,而不是独立的一个JSON,具体是否要分离,需要考虑具体界面的复杂度与易用性。
|
||||
* 以下是一些常见界面的所属界面层次示例
|
||||
##### 主界面常驻
|
||||
主菜单插件中的按钮面板
|
||||

|
||||
|
||||
##### 主界面浮动提示
|
||||
默认聊天提示信息
|
||||

|
||||
##### 一级弹出界面
|
||||
公会插件的创建公会界面
|
||||

|
||||
##### 二级弹出界面
|
||||
公会插件的创建公会输入公会名、确认的子界面
|
||||

|
||||
##### 模态弹出界面
|
||||
组队插件的离开队伍确认界面
|
||||

|
||||
##### 浮动提示界面
|
||||
物品属性提示tips
|
||||

|
||||
|
||||
## 建议规范
|
||||
以下规范为建议采用,不强制要求
|
||||
### 编码规范
|
||||
【建议】名字命名尽量用驼峰法命名。具体如下:
|
||||
* mod中所有目录和文件都是用驼峰法命名,首字母小写,比如目录modClient。
|
||||
* 所有类名都是用驼峰法命名,首字母大写,比如类 GameObjectType。
|
||||
* 常量都使用驼峰法命名,首字母大写,比如 ModVersion = "0.0.1"
|
||||
* 类非静态成员函数使用驼峰法命名,以“m”开头,例如 mLevel。
|
||||
* 类非静态成员函数使用驼峰法命名,首字母大写,例如 Init()。
|
||||
* event使用驼峰法命名,首字母大写,例如“PlayerTransactionFromClientEvent”
|
||||
* 统一用tab而不是四个空格缩进。
|
||||
* system中namespace+systemName要求是唯一,namespace要求与插件名一样,systemName根据自己要求定义。
|
||||
* service使用到的module name要求与插件名一样。
|
||||
示例:
|
||||
```python
|
||||
# 公告插件service system定义
|
||||
class AnnounceServiceSystem(ServiceSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
for moduleName in serviceConf.get_module_names():
|
||||
# ModNameSpace = "neteaseAnnounce"
|
||||
if moduleName.startswith(announceConsts.ModNameSpace):
|
||||
mgr = self.CreateAnnounceMgr(moduleName)
|
||||
else:
|
||||
continue
|
||||
self.mActionMgrs[moduleName] = mgr
|
||||
```
|
||||
27
mcguide/27-手机网络游戏/课程5:插件教学/第2节:地图插件调整(概述).md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
front:
|
||||
hard: 进阶
|
||||
time: 5分钟
|
||||
---
|
||||
|
||||
# 地图插件调整(概述)
|
||||
## 概述
|
||||
|
||||
教学系列从官方的地图插件出发,介绍如何直接使用或者是微调后使用。并以此为例子说明网络服的框架结构以及事件侦听、回调概念。系列目录简介如下,
|
||||
|
||||
《地图插件调整(上)》包含以下内容:
|
||||
|
||||
* **了解与试用插件**:此部分为入门难度,主要介绍**如何使用**官方插件。
|
||||
* **插件架构分析**:此部分为入门难度,主要介绍网络服插件的**目录结构**。
|
||||
|
||||
《地图插件调整(中)》包含以下内容:
|
||||
|
||||
* **插件核心代码解析**:此部分为进阶难度,详细**解析**每个目录下的代码,也详细说明了**事件侦听**、**回调**的概念。(高能预警:内容较多!)
|
||||
* **多端协作逻辑**:此部分为困难难度,详细解析**客户端**与**不同服务器类型**之间的数据流。
|
||||
|
||||
《地图插件调整(下)》包含以下内容:
|
||||
|
||||
* **微调地图插件**:此部分为入门难度,主要介绍如何在官方插件基础上拓展**新功能**。本例子中,修改后的地图插件可额外阻止以下行为——破坏耕地、触发压力板、踩红石矿、踩拌线钩。
|
||||
|
||||
* **部署修改后的插件**:此部分为入门难度,主要介绍**如何使用**修改后的插件。
|
||||
|
||||
144
mcguide/27-手机网络游戏/课程5:插件教学/第3节:地图插件调整(上).md
Normal file
@@ -0,0 +1,144 @@
|
||||
---
|
||||
front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/ditu_04.3a07fe10.png
|
||||
hard: 进阶
|
||||
time: 15分钟
|
||||
---
|
||||
|
||||
# 地图插件调整(上)
|
||||
## 了解与试用插件
|
||||
|
||||
* 打开studio,选择【新建】-->【基岩版网络服】
|
||||

|
||||
* 修改网络服名称为【地图属性插件微调】
|
||||
* 账号、用户名、机器配置等信息,请根据申请获取到的开发服务器信息填写
|
||||

|
||||
* 选择【游戏配置】-->【大厅服】-->【Mod的下拉菜单】-->【获取公共Mod】
|
||||

|
||||
* 选择**地图属性插件**,点击【全部下载】,等待下载完成后关闭此弹出界面
|
||||

|
||||
* 再次点击【Mod的下拉菜单】,选中【neteaseMapAttrs】插件
|
||||

|
||||
* 点击【下一步】开始配置游戏服
|
||||

|
||||
* 存粹为了体验**地图属性插件**的部分功能,不需要部署游戏服,直接点击【下一步】,开始配置控制服
|
||||

|
||||
* **地图属性插件**包含控制服插件,点击【Mod的下拉菜单】,选中【neteaseMapAttrsMaster】插件
|
||||

|
||||
* 控制服配置完成,点击下一步配置功能服
|
||||

|
||||
* **地图属性插件**不包含控制服插件,直接点击【下一步】,配置代理服
|
||||

|
||||
* 代理服不支持插件功能,直接点击【完成】,结束【游戏配置】
|
||||
* 【数据库】分页的配置信息,请根据申请获取到的开发服务器信息填写
|
||||
* 【更多】分页的配置信息,在开发阶段直接使用默认即可,无需修改
|
||||

|
||||
* 在studio选择【基岩版服务器】-->【网络服开发】-->选中【地图属性插件微调】-->点击【部署】,等待服务器部署完成
|
||||

|
||||
* 点击【开发测试】即可启动客户端体验**地图属性插件**的功能
|
||||

|
||||
|
||||
## 插件架构分析
|
||||
### 获取插件的源码
|
||||
* 下载完成的插件,一般可以在[C:\MCStudioDownload\ApolloMod\{开发者账号}]目录中找到
|
||||
* 此外,也可以从studio中跳转到插件下载的目录
|
||||
* 在studio选中【基岩版服务器】-->【服务器Mod】-->选中【neteaseMapAttrsMaster】or 【neteaseMapAttrs】-->点击【更多】
|
||||

|
||||
* 在菜单中点击【打开目录】
|
||||

|
||||
* 此时会进入到目标Mod的下载目录,点击【上一层】,即可定位到全部Mod的下载目录
|
||||

|
||||
* 【neteaseMapAttrs】和【neteaseMapAttrsMaster】就是**地图属性插件**的源码目录
|
||||

|
||||
* 把源码复制到自己本地的代码目录,方便随时添加注释与修改调试
|
||||

|
||||
### 从readme开始
|
||||
* 查看**地图属性插件**的readme文件,仔细查看实现的功能点
|
||||
* 其中并没有需要多个服务器进程协作的功能,再往下发现**地图属性插件**支持一个运营指令,那么大致上可以判断,【neteaseMapAttrsMaster】插件仅仅是作为运营指令的一个入口和驱动,和实际的游戏相关功能并没有特别强的联系。
|
||||
```
|
||||
地图属性插件,地图属性插件用于设置整个地图的一些通用属性,包括:
|
||||
1、是否开启主城保护
|
||||
2、是否禁止藤蔓生长
|
||||
3、是否禁止流体流动
|
||||
4、是否定时清理掉落物 与 定时清理掉落物间隔
|
||||
5、在地图指定位置设置浮空文字
|
||||
6、设置地图边界(玩家走出边界会被传送回最近离开的合法位置)
|
||||
7、设置玩家是否可丢弃物品
|
||||
8、玩家是否可捡起物品
|
||||
9、可以根据针对地图编辑器导出的地图文件替换游戏地图
|
||||
...
|
||||
...
|
||||
...
|
||||
运营指令:
|
||||
(1)设置指定服务器地图边界。
|
||||
post url: http:masterip:masterport//mapAttrs/set-area-limit
|
||||
post body:{
|
||||
"type": "gameA", # 目标服务器类型。每种类型只有一个服务器,通过服务器类型区分不同服务器。
|
||||
"minPos" : [-50,0,-50], # 地图边界(x, y, z)坐标的最小值
|
||||
"maxPos": [50,20,50] # 地图边界(x, y, z)坐标的最大值
|
||||
}
|
||||
response:
|
||||
{
|
||||
"message": "",
|
||||
"code": 1, #1表示成功,2表示失败
|
||||
"entity": ""
|
||||
}
|
||||
```
|
||||
### 基于目录结构分析
|
||||
* **地图属性插件**,从最外层看,一共有两个Mod
|
||||
```
|
||||
├─neteaseMapAttrs
|
||||
├─neteaseMapAttrsMaster
|
||||
```
|
||||
* 展开各个目录,基于插件的结构,代码文件命名和大小,以及结合**地图属性插件**实现的具体功能,大致可以对每个代码文件有一些定位
|
||||
```
|
||||
├─neteaseMapAttrs
|
||||
| md5 # 下载用的对比文件,与插件逻辑无关,本地代码目录中可以删除
|
||||
| readme.txt # 功能说明文件,是非常好的信息来源
|
||||
├─behavior_packs # 行为包总目录
|
||||
└─neteaseMapAttrsBehavior # 插件只有一个行为包
|
||||
| manifest.json # 行为包的UDID
|
||||
├─neteaseMapAttrsScript # 客户端Mod脚本根目录
|
||||
| __init__.py
|
||||
| mapAttrsClientSys.py # 客户端Mod的system类,具体分析见下文
|
||||
| mapAttrsConsts.py # 客户端Mod宏定义
|
||||
| modMain.py # 客户端Mod入口
|
||||
| textBoardMgr.py # 客户端Mod中管理浮空文字的类,具体分析见下文
|
||||
└─util.py # 客户端Mod的一些功能函数
|
||||
└─structures
|
||||
└─mapStructure # 结合readme可知,此目录用来放置【功能9】需要的mcstructure文件(由编辑器生成)
|
||||
├─developer_mods # 服务端Mod总目录
|
||||
└─neteaseMapAttrsDev # 插件只有一个服务端Mod
|
||||
| mod.json # 插件配置信息文件
|
||||
├─neteaseMapAttrsScript # 服务端Mod脚本根目录
|
||||
| __init__.py
|
||||
| coroutineMgrGas.py # 服务端Mod中的利用yield实现的管理延时执行函数的类,具体分析见下文
|
||||
| mapAttrsConsts.py # 服务端Mod宏定义
|
||||
| mapAttrsServerSys.py # 服务端Mod的system类,具体分析见下文
|
||||
| modMain.py # 服务端Mod入口
|
||||
| playerMgr.py # 服务端Mod中管理玩家的类,主要用于实现【功能6】,具体分析见下文
|
||||
└─util.py # 服务端Mod的一些功能函数
|
||||
└─mapStructureConfig
|
||||
└─neteaseMapStructueConfig.json # 此文件是与客户端Mod中mcstructure文件对应的配置文件(内容由编辑器生成)
|
||||
├─resource_packs # 资源包总目录,插件没有美术资源,所以是空的
|
||||
├─worlds
|
||||
├─level
|
||||
| world_behavior_packs.json # 行为包的UDID描述
|
||||
└─world_resource_packs.json # 资源包的UDID描述(资源包是空的,所以文件的内容也是空的)
|
||||
├─neteaseMapAttrsMaster
|
||||
| md5 # 下载用的对比文件,与插件逻辑无关,本地代码目录中可以删除
|
||||
| readme.txt # 功能说明文件,内容与【neteaseMapAttrs】中一样
|
||||
└─developer_mods # 控制服Mod总目录
|
||||
└─neteaseMapAttrsDev # 插件只有一个控制服Mod
|
||||
| mod.json # 插件配置信息文件
|
||||
└─neteaseMapAttrsScript # 控制服Mod脚本根目录
|
||||
| __init__.py
|
||||
| mapAttrsConsts.py # 控制服Mod宏定义
|
||||
| mapAttrsMasterSys.py # 控制服Mod,具体分析见下文
|
||||
| modMain.py # 控制服Mod入口
|
||||
└─util.py # 控制Mod的一些功能函数
|
||||
```
|
||||
### 小结
|
||||
* 插件的总体结构并不复杂,每个端的Mod(客户端、服务端、控制服)都仅有一个system文件与一两个自定义的管理类
|
||||
* 结合readme的功能、运营指令描述,插件的控制服Mod功能仅仅是接收和反馈运营指令
|
||||
* 插件的客户端Mod中,仅有一个管理浮空文字的类,和【功能5】实现强相关
|
||||
* 插件的服务端Mod中,有一个使用python的yield
|
||||
441
mcguide/27-手机网络游戏/课程5:插件教学/第4节:地图插件调整(中).md
Normal file
@@ -0,0 +1,441 @@
|
||||
---
|
||||
front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/ditu_27.6f168b1c.png
|
||||
hard: 进阶
|
||||
time: 25分钟
|
||||
---
|
||||
|
||||
# 地图插件调整(中)
|
||||
|
||||
|
||||
## 插件核心代码解析
|
||||
* 阅读源码之前,需要对python脚本语言有基础的了解。
|
||||
* 插件中使用了yield关键字实现了一个延时函数执行机制,具体yield的用法可以自助查询,但是不了解也不影响对逻辑的理解,简单地当成一个延时的Timer就行了。
|
||||
* 默认对插件的事件、回调机制比较了解,相关基础知识详见[事件简介](../../20-玩法开发/10-基本概念/1-我的世界基础概念.md)。
|
||||
|
||||
### mapAttrsClientSys.py
|
||||
#### 初始化
|
||||
* 初始化时,生成了一个【textBoardMgr.py】中定义的类【TextBoardMgr】的实例
|
||||
```python
|
||||
class MapAttrsClientSys(ClientSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
...
|
||||
self.mTextMgr = TextBoardMgr(self.mPlayerId, offset=50.0)
|
||||
```
|
||||
#### 处理引擎事件【UiInitFinished】
|
||||
* 回调函数【OnUiInitFinished】中向服务端发送一个自定义事件,用于提示服务端本客户端准备就绪。
|
||||
* 服务端接收到此事件后会回应客户端自定义事件【ServerEvent.LoginResponse】,事件中会带有客户端玩家初始所在的维度信息与【功能5、在地图指定位置设置浮空文字】相关的配置信息
|
||||
```python
|
||||
class MapAttrsClientSys(ClientSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
...
|
||||
util.ListenClientEngineEvent("UiInitFinished", self, self.OnUiInitFinished)
|
||||
|
||||
def OnUiInitFinished(self, data):
|
||||
print 'OnUiInitFinished', data
|
||||
requestData = {
|
||||
"playerId": self.mPlayerId,
|
||||
}
|
||||
util.NotifyToServer(ClientEvent.PlayerEnter, requestData)
|
||||
```
|
||||
#### 处理引擎事件【DimensionChangeClientEvent】
|
||||
* 回调函数【OnDimChange】中更新当前自己所在的维度
|
||||
```python
|
||||
class MapAttrsClientSys(ClientSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
...
|
||||
util.ListenClientEngineEvent("DimensionChangeClientEvent", self, self.OnDimChange)
|
||||
|
||||
def OnDimChange(self, data):
|
||||
self.mPlayerDim = data["toDimensionId"]
|
||||
self.mTextMgr.OnDimChange(data)
|
||||
|
||||
def OnLoginResponse(self, data):
|
||||
print 'OnLoginResponse', data
|
||||
self.mPlayerDim = data["dim"]
|
||||
```
|
||||
#### 处理服务端自定义事件【ServerEvent.LoginResponse】
|
||||
* 回调函数【OnLoginResponse】中,从服务端返回的信息中,初始化自己当前所在的维度,并提交【功能5、在地图指定位置设置浮空文字】所需的浮空文字配置信息给【TextBoardMgr】实例
|
||||
```python
|
||||
class MapAttrsClientSys(ClientSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
...
|
||||
util.ListenServerEvent(ServerEvent.LoginResponse, self, self.OnLoginResponse)
|
||||
|
||||
def OnLoginResponse(self, data):
|
||||
print 'OnLoginResponse', data
|
||||
self.mPlayerDim = data["dim"]
|
||||
self.mTextMgr.Init(data["configData"])
|
||||
```
|
||||
### textBoardMgr.py
|
||||
* 管理浮空文字,实现【功能5】的类
|
||||
* 浮空文字的创建,依赖于浮空文字所在位置的区块已经成功加载进客户端的内存中,否则无法创建成功
|
||||
* 需要一个管理类通过时钟触发的检查逻辑,动态判定哪些浮空文字进入了玩家的【逻辑视野】,并且尝试创建之。
|
||||
* 需要处理和维度跳转相关的清理、重新创建的逻辑
|
||||
```python
|
||||
class TextBoardMgr(object):
|
||||
# 创建对象时,初始化参数为玩家自己的entityId,以及逻辑视野(当浮空文字距离玩家小于多少时,便会被创建)
|
||||
def __init__(self, playerId, offset=5.0):
|
||||
...
|
||||
self.mCheckOffset = offset
|
||||
|
||||
def Init(self, configData):
|
||||
# 通过服务端自定义事件【ServerEvent.LoginResponse】中的浮空文字配置信息,生成等待创建的浮空文字字典。
|
||||
textWithPlaceList = configData.get("text_with_place_list", None)
|
||||
...
|
||||
# 注册每3秒钟执行一次的timer,触发检查是否有浮空文字需要被创建
|
||||
self.mAddTimer = comp.AddRepeatedTimer(3.0, self.CheckAddTextBoard)
|
||||
|
||||
def Destroy(self):
|
||||
...
|
||||
# 当玩家所处维度变化时,删除位于旧维度的全部浮空文字实例
|
||||
def OnDimChange(self, data):
|
||||
...
|
||||
# 检查是否有浮空文字需要被创建
|
||||
def CheckAddTextBoard(self):
|
||||
...
|
||||
for idx, conf in self.mTextWithPlaceMap.iteritems():
|
||||
# 跳过已经创建完成的浮空文字
|
||||
if self.mTextIdxToEntityId.has_key(idx):
|
||||
continue
|
||||
# 跳过维度不一致的浮空文字
|
||||
if dim != conf["dim"]:
|
||||
continue
|
||||
# 跳过逻辑视野之外的浮空文字
|
||||
if self.IsFar(pos, conf["pos"]):
|
||||
continue
|
||||
...
|
||||
# 创建浮空文字
|
||||
entityId = util.GetSystem().CreateEngineTextboard(
|
||||
conf["text"], self.mPlayerId, conf["pos"], conf["textColor"],
|
||||
conf["tagColor"], conf["size"], conf["depthTest"])
|
||||
if entityId is None or entityId <= 0:
|
||||
continue
|
||||
# 缓存浮空文字的实体ID,记录已经创建成功了
|
||||
self.mTextIdxToEntityId[idx] = entityId
|
||||
```
|
||||
### mapAttrsServerSys.py
|
||||
#### 初始化
|
||||
* 读取mod.json的配置信息
|
||||
* 生成【coroutineMgrGas.py】中定义的类【CoroutineMgr】的实例
|
||||
* 生成【playerMgr.py】中定义的类【PlayerMgr】的实例,假如配置中激活了【功能6、设置地图边界】的话。
|
||||
```python
|
||||
class MapAttrsServerSys(ServerSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
...
|
||||
util.LoadModConf()
|
||||
...
|
||||
self.Init()
|
||||
self.mCoroutineMgr = CoroutineMgr()
|
||||
|
||||
def Update(self):
|
||||
self.mCoroutineMgr.Tick()
|
||||
|
||||
def Init(self):
|
||||
...
|
||||
self.mPlayerMgr = None
|
||||
if util.GetModConfByField("map_area_limit"):
|
||||
self.mPlayerMgr = PlayerMgr()
|
||||
self.mPlayerMgr.Init(util.GetModConfByField("map_area_limit"))
|
||||
```
|
||||
#### 功能1、是否开启主城保护
|
||||
* 直接使用组件API【OpenCityProtect】实现
|
||||
```python
|
||||
gameComp = self.CreateComponent(extraServerApi.GetLevelId(), 'Minecraft', 'game')
|
||||
if util.GetModConfByField("open_map_protect"):
|
||||
suc = gameComp.OpenCityProtect()
|
||||
```
|
||||
#### 功能2、是否禁止藤蔓生长
|
||||
* 直接使用组件API【DisableVineBlockSpread】实现
|
||||
```python
|
||||
gameComp = self.CreateComponent(extraServerApi.GetLevelId(), 'Minecraft', 'game')
|
||||
if util.GetModConfByField("forbid_vine_spread"):
|
||||
gameComp.DisableVineBlockSpread(True)
|
||||
```
|
||||
#### 功能3、是否禁止流体流动
|
||||
* 直接使用组件API【ForbidLiquidFlow】实现
|
||||
```python
|
||||
gameComp = self.CreateComponent(extraServerApi.GetLevelId(), 'Minecraft', 'game')
|
||||
if util.GetModConfByField("forbid_liquid_flow"):
|
||||
gameComp.ForbidLiquidFlow(True)
|
||||
```
|
||||
#### 功能4、是否定时清理掉落物 与 定时清理掉落物间隔
|
||||
* 使用组件API【AddRepeatedTimer】注册一个定时执行的清理函数
|
||||
* 使用【/kill @e[type=item]】指令清理道具实体
|
||||
```python
|
||||
gameComp = self.CreateComponent(extraServerApi.GetLevelId(), 'Minecraft', 'game')
|
||||
self.mCleanDropTimer = None
|
||||
if util.GetModConfByField("enable_clean_drop_item"):
|
||||
interval = util.GetModConfByField("clean_drop_item_interval")
|
||||
if interval > 0:
|
||||
self.mCleanDropTimer = gameComp.AddRepeatedTimer(interval, self.CleanDropItem)
|
||||
|
||||
def CleanDropItem(self):
|
||||
comp = extraServerApi.CreateComponent(extraServerApi.GetLevelId(), "Minecraft", "command")
|
||||
comp.SetCommand("/kill @e[type=item]")
|
||||
```
|
||||
#### 功能7、设置玩家是否可丢弃物品
|
||||
* 直接使用组件API【SetDisableDropItem】实现
|
||||
* 在玩家登录完成后(客户端的初始化完成消息),使用组件API【EnableKeepInventory】保证死亡物品也不掉落
|
||||
```python
|
||||
gameComp = self.CreateComponent(extraServerApi.GetLevelId(), 'Minecraft', 'game')
|
||||
if util.GetModConfByField("forbid_drop_item"):
|
||||
gameComp.SetDisableDropItem(True)
|
||||
|
||||
if util.GetModConfByField("forbid_drop_item"):
|
||||
comp = self.CreateComponent(playerId, "Minecraft", "player")
|
||||
suc = comp.EnableKeepInventory(True)
|
||||
print "EnableKeepInventory for player=%s suc=%s" % (playerId, suc)
|
||||
```
|
||||
#### 功能8、玩家是否可捡起物品
|
||||
* 注册引擎事件【ServerPlayerTryTouchEvent】和【ServerPlayerGetExperienceOrbEvent】
|
||||
* 回调函数【OnPlayerPickItem】中设置cancel=True,pickupDelay=100000(取消本次拾取,并设置下次拾取的cd大于97813帧,视作无法拾取)
|
||||
* 回调函数【ServerPlayerGetExperienceOrbEvent】中设置cancel=True(取消本次拾取)
|
||||
```python
|
||||
gameComp = self.CreateComponent(extraServerApi.GetLevelId(), 'Minecraft', 'game')
|
||||
if util.GetModConfByField("forbid_pickup_item"):
|
||||
util.ListenServerEngineEvent("ServerPlayerTryTouchEvent", self, self.OnPlayerPickItem)
|
||||
util.ListenServerEngineEvent("ServerPlayerGetExperienceOrbEvent", self, self.OnPlayerPickOrb)
|
||||
|
||||
def OnPlayerPickItem(self, data):
|
||||
data["cancel"] = True
|
||||
data["pickupDelay"] = 100000
|
||||
|
||||
def OnPlayerPickOrb(self, data):
|
||||
data["cancel"] = True
|
||||
```
|
||||
#### 功能9、可以根据针对地图编辑器导出的地图文件替换游戏地图
|
||||
* 只有对应区块加载进内存的前提下,才能对区块对应的地图方块进行修改,所以需要一个用于【开地图】的玩家,在配置中需要指定一个特定的uid。
|
||||
* 利用mCoroutineMgr实现的延时执行函数,每隔一定时间执行一块地图区域的覆盖工作
|
||||
* 单次地图区域的覆盖逻辑:先把【开地图】的玩家传送到需要进行地图覆盖的位置,然后使用【PlaceStructure】函数对地图区域进行覆盖
|
||||
```python
|
||||
def SetMapStructure(self, playerId, playerUid):
|
||||
if playerUid != util.GetModConfByField("change_uid"):
|
||||
return
|
||||
needChangeDimension = util.GetModConfByField("change_map_dimension")
|
||||
neteaseMapStructureConfigPath = commonNetgameApi.GetModScriptRootDir("neteaseMapAttrsScript") + "/mapStructureConfig" + "/neteaseMapStructueConfig.json"
|
||||
neteaseMapStructureConfig = util.read_json(neteaseMapStructureConfigPath)
|
||||
print "SetMapStructure",neteaseMapStructureConfigPath, neteaseMapStructureConfig
|
||||
if neteaseMapStructureConfig is None:
|
||||
return
|
||||
for oneConfig in neteaseMapStructureConfig:
|
||||
self.mCoroutineMgr.StartCoroutine(self.RealSetMapStructure(playerId, needChangeDimension, oneConfig, neteaseMapStructureConfig.index(oneConfig) + 1))
|
||||
|
||||
def RealSetMapStructure(self, playerId, needChangeDimension, oneConfig, delayFrame):
|
||||
yield -1 * delayFrame * 60
|
||||
tpPos = oneConfig.get("pos")
|
||||
#changeDimension = oneConfig.get("change_map_dimension")
|
||||
dimensionComp = self.CreateComponent(playerId, "Minecraft", "dimension")
|
||||
# playerDimension = dimensionComp.GetEntityDimensionId()
|
||||
print "tpPos", tpPos, needChangeDimension
|
||||
dimensionComp.ChangePlayerDimension(needChangeDimension, (tpPos[0], tpPos[1] + 3, tpPos[2]))
|
||||
strucFile = oneConfig.get("file")[:-12]# 去掉后缀
|
||||
strucFile = "mapStructure:" + strucFile
|
||||
print "RealSetMapStructureFile", strucFile, tuple(tpPos)
|
||||
gameComp = self.CreateComponent(extraServerApi.GetLevelId(), "Minecraft", "game")
|
||||
suc = gameComp.PlaceStructure(playerId, tuple(tpPos), strucFile.encode("utf-8"))
|
||||
print "RealSetMapStructure", suc
|
||||
```
|
||||
#### 处理客户端自定义事件【ClientEvent.PlayerEnter】
|
||||
* 回调函数【OnClientEnter】中,获取目标客户端的玩家所在的维度,玩家的uid(这两个信息当前仅能通过服务器API获取),以及配置信息中的浮空文字配置(浮空文字显示逻辑由客户端实现),并返回给目标客户端(利用服务器自定义事件【ServerEvent.LoginResponse】)
|
||||
* 在【OnClientEnter】中,才调用【EnableKeepInventory】保证死亡物品也不掉落,是因为在【AddServerPlayerEvent】中,其实引擎层的player实体还没有彻底创建完毕,有些组件API是没法正常生效的。
|
||||
```python
|
||||
util.ListenClientEvent(ClientEvent.PlayerEnter, self, self.OnClientEnter)
|
||||
|
||||
def OnClientEnter(self, data):
|
||||
...
|
||||
playerId = data['playerId']
|
||||
uid = netgameApi.GetPlayerUid(playerId)
|
||||
responseData = {
|
||||
"uid": uid,
|
||||
"dim": util.GetEntityDimensionId(playerId),
|
||||
"configData": {},
|
||||
}
|
||||
responseData["configData"]["text_with_place_list"] = self.mCheckedTextWithPlaceList
|
||||
util.NotifyToClient(playerId, ServerEvent.LoginResponse, responseData)
|
||||
if util.GetModConfByField("forbid_drop_item"):
|
||||
comp = self.CreateComponent(playerId, "Minecraft", "player")
|
||||
suc = comp.EnableKeepInventory(True)
|
||||
print "EnableKeepInventory for player=%s suc=%s" % (playerId, suc)
|
||||
```
|
||||
#### 实现运营指令(1)设置指定服务器地图边界。
|
||||
* 注册控制服自定义事件【MasterEvent.SetMapArea】
|
||||
* 回调函数【OnSetMapArea】中修改实际生效的地图边界,并返回结果给控制服
|
||||
* 传递新的地图边界信息给【mPlayerMgr】对象,【功能6、设置地图边界】的逻辑有此对象负责管理。
|
||||
```python
|
||||
util.ListenMasterEvent(MasterEvent.SetMapArea, self, self.OnSetMapArea)
|
||||
|
||||
def OnSetMapArea(self, data):
|
||||
bSuc, reason = self.TryChangeAreaLimit(data.get("minPos", None), data.get("maxPos", None))
|
||||
...
|
||||
self.NotifyToMaster(ServerEvent.HttpResponse, httpRes)
|
||||
|
||||
def TryChangeAreaLimit(self, minPos, maxPos):
|
||||
if not self.mPlayerMgr:
|
||||
return False, "not support area limit this server"
|
||||
if minPos is None or maxPos is None:
|
||||
return False, "must set minPos and maxPos first"
|
||||
try:
|
||||
for i in xrange(3):
|
||||
if minPos[i] >= maxPos[i]:
|
||||
return False, "minPos must larger than maxPos"
|
||||
except:
|
||||
return False, "minPos and maxPos must like (x, y, z)"
|
||||
self.mPlayerMgr.UpdateAreaLimit(minPos, maxPos)
|
||||
return True, ""
|
||||
```
|
||||
### playerMgr.py
|
||||
* 管理玩家的对象
|
||||
* 用于实现【功能6、设置地图边界(玩家走出边界会被传送回最近离开的合法位置)】
|
||||
#### SinglePlayer类
|
||||
* 保留最近一段时间内的,玩家的移动轨迹(以定时坐标采样的形式)
|
||||
* 当玩家走出边界,需要被传送时,从近期的移动轨迹中找出最近的合法位置,并强制传送
|
||||
```python
|
||||
class SinglePlayer(object):
|
||||
def __init__(self, playerId):
|
||||
...
|
||||
# 缓存玩家的移动轨迹且去重
|
||||
# 当缓存数据超过30个点的时候,一次性的裁剪到剩余最新的15个点
|
||||
def CacheLastSafePos(self, x, y, z):
|
||||
if self.mLastSafePos and self.mLastSafePos[0] == x and self.mLastSafePos[1] == y and self.mLastSafePos[2] == z:
|
||||
return
|
||||
pos = (x, y, z)
|
||||
self.mLastSafePos = pos
|
||||
self.mCanUseSafePosList.insert(0, pos)
|
||||
if len(self.mCanUseSafePosList) > 30:
|
||||
self.mCanUseSafePosList = self.mCanUseSafePosList[:15]
|
||||
# 从移动轨迹中,获取最近离开的合法位置
|
||||
# 由于地图边界是可以动态设置的,过去缓存的轨迹现在就不一定合法了,所以最终用地图范围的中心点保底
|
||||
def FindUsableSafePos(self):
|
||||
for pos in self.mCanUseSafePosList:
|
||||
if util.IsInArea(pos, AABB_MIN, AABB_MAX):
|
||||
return pos
|
||||
return GetLimitAreaCenter()
|
||||
# 强制传送玩家到最近离开的合法位置
|
||||
# 强制传送前,需要强制玩家下坐骑
|
||||
def SyncToSafePos(self):
|
||||
# 如果找不到可以放置的点,就不重新设置了
|
||||
safePos = self.FindUsableSafePos()
|
||||
if safePos is None:
|
||||
return
|
||||
rideComp = extraServerApi.CreateComponent(self.mPlayerId, "Minecraft", "ride")
|
||||
if rideComp:
|
||||
riderId = rideComp.GetEntityRider()
|
||||
if riderId != -1:
|
||||
rideComp.StopEntityRiding()
|
||||
riderPosComp = extraServerApi.CreateComponent(riderId, "Minecraft", "pos")
|
||||
riderPosComp.SetPos(safePos)
|
||||
posComp = extraServerApi.CreateComponent(self.mPlayerId, "Minecraft", "pos")
|
||||
posComp.SetPos(safePos)
|
||||
```
|
||||
#### PlayerMgr类
|
||||
* 注册一个每秒执行一次的循环函数【CheckSafePos】
|
||||
* 每次执行【CheckSafePos】,都会遍历当前全部在线玩家,判定玩家当前的位置,假如位置没有超出地图边界,那么就缓存,假如维持超出地图边界,那么就触发强制传送
|
||||
```python
|
||||
class PlayerMgr(object):
|
||||
def __init__(self):
|
||||
...
|
||||
|
||||
def Init(self, map_area_limit):
|
||||
gameComp = extraServerApi.CreateComponent(extraServerApi.GetLevelId(), 'Minecraft', 'game')
|
||||
self.mCheckTimer = gameComp.AddRepeatedTimer(1.0, self.CheckSafePos)
|
||||
...
|
||||
|
||||
def CheckSafePos(self):
|
||||
for playerId, obj in self.mPlayerMap.iteritems():
|
||||
...
|
||||
if util.IsInArea((x, y, z), AABB_MIN, AABB_MAX):
|
||||
obj.CacheLastSafePos(x, y, z)
|
||||
else:
|
||||
obj.SyncToSafePos()
|
||||
```
|
||||
### coroutineMgrGas.py
|
||||
* 利用python的yield关键字与迭代器机制,实现的延时执行函数的管理器
|
||||
* 当yield的返回值为负数时,每个tick都会加1,直到为0时开始执行yield后面的语句,等价于**多少帧后执行**
|
||||
* 当yield的返回值为正数时,在当前时间+返回值的时刻之后的第一帧,开始执行yield后面的语句,等价于**多少秒后执行**
|
||||
```python
|
||||
class CoroutineMgr(object):
|
||||
...
|
||||
@classmethod
|
||||
def Tick(cls):
|
||||
if cls.addCoroutines:
|
||||
for c,v in cls.addCoroutines.iteritems():
|
||||
cls.coroutines[c] = v
|
||||
cls.addCoroutines = {}
|
||||
if cls.globalEnd:
|
||||
for c in cls.globalEnd:
|
||||
if cls.coroutines.get(c):
|
||||
del cls.coroutines[c]
|
||||
cls.globalEnd = []
|
||||
ended = []
|
||||
for c, v in cls.coroutines.iteritems():
|
||||
try:
|
||||
if v < 0:
|
||||
v += 1
|
||||
cls.coroutines[c] = v
|
||||
if v == 0 or (v > 0 and time.time() >= v):
|
||||
newv = c.next()
|
||||
if newv > 0:
|
||||
newv = newv + time.time()
|
||||
cls.coroutines[c] = newv
|
||||
except StopIteration:
|
||||
ended.append(c)
|
||||
for c in ended:
|
||||
del cls.coroutines[c]
|
||||
|
||||
def DelayDoByFrame(self, delayFrame):
|
||||
yield -1 * delayFrame
|
||||
|
||||
def DelayDoBySecond(self, delaySec):
|
||||
yield delaySec
|
||||
```
|
||||
### mapAttrsMasterSys.py
|
||||
* 仅用于实现【运营指令(1)设置指定服务器地图边界】
|
||||
* 分析http请求的参数,转发运营指令中的地图边界信息到指定类型的服务器进程(当时设计时,仅支持一种服务器类型单独一个进程)
|
||||
* 监听来自其他服务器进程的自定义事件【ServerEvent.HttpResponse】,返回最终处理结果给运营指令的请求方
|
||||
#### 监听http请求【/mapAttrs/set-area-limit】
|
||||
* 回调函数【OnChangeAreaLimit】中,通过服务器类型定位到对应的服务器ID,并转发新的地图边界信息给对应服务器ID的进程
|
||||
```python
|
||||
class MapAttrsMasterSys(MasterSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
...
|
||||
masterHttp.RegisterMasterHttp("/mapAttrs/set-area-limit", self, self.OnChangeAreaLimit)
|
||||
|
||||
def OnChangeAreaLimit(self, clientId, requestBody):
|
||||
eventData = json.loads(requestBody)
|
||||
...
|
||||
serverId = self.GetServerIdByType(eventData['type'])
|
||||
...
|
||||
eventData['clientId'] = clientId
|
||||
self.NotifyToServerNode(serverId, MasterEvent.SetMapArea, eventData)
|
||||
|
||||
def GetServerIdByType(self, typeKey):
|
||||
...
|
||||
```
|
||||
#### 处理服务端自定义事件【ServerEvent.HttpResponse】
|
||||
* 回调函数【OnHttpResponse】中,直接打包返回结果给http请求的发起方
|
||||
```python
|
||||
class MapAttrsMasterSys(MasterSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
...
|
||||
util.ListenServerEvent(ServerEvent.HttpResponse, self, self.OnHttpResponse)
|
||||
|
||||
def OnHttpResponse(self, args):
|
||||
res = self.makeResponse(args['code'], args['message'], args['entity'])
|
||||
masterHttp.SendHttpResponse(args['clientId'], res)
|
||||
```
|
||||
## 多端协作逻辑
|
||||
* 整个地图属性插件的逻辑都比较简单和原子化,仅少量的逻辑涉及到多端协作
|
||||
### 客户端登录
|
||||
* 【功能5、在地图指定位置设置浮空文字】,由每个客户端自主创建浮空文字实现
|
||||
* 但是对应需要的浮空文字的维度和坐标配置信息,都在服务端的mod.json中,需要从服务端获取
|
||||
* 此外,创建和维护浮空文字,需要知道当前客户端所在的维度信息,客户端能够通过引擎事件【DimensionChangeClientEvent】,监听到切换维度的事件,但是没有获取刚登录时,初始维度的信息,也需要从服务端获取
|
||||
* 一般而言,考虑到客户端启动需要加载场景、加载人物、初始化UI等一系列工作,服务端也需要客户端主动给出一个明确的信号,确认客户端已经准备完毕,可以正常运行脚本逻辑。
|
||||
#### 客户端登录时的多端数据流如下图:
|
||||

|
||||
|
||||
### 运营指令(1)设置指定服务器地图边界
|
||||
* 运营指令以http请求的方式发送给控制服
|
||||
* 控制服解析请求参数后定位到具体实现功能的服务器进程,并发送事件给目标游戏服/大厅服
|
||||
* 服务器根据事件的参数,调整地图边界,并以事件的方式返回结果给控制服
|
||||
* 控制服返回运营指令执行结果给http的请求方
|
||||

|
||||
59
mcguide/27-手机网络游戏/课程5:插件教学/第5节:地图插件调整(下).md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/ditu_19.81564cc2.png
|
||||
hard: 进阶
|
||||
time: 20分钟
|
||||
---
|
||||
|
||||
# 地图插件调整(下)
|
||||
|
||||
|
||||
## 微调地图插件
|
||||
### 阻止破坏耕地
|
||||
* 查询ModSDK的事件,发现可以用【MobGriefingBlockServerEvent】实现目标功能
|
||||

|
||||
* 查询到耕地的identifier为【minecraft:farmland】
|
||||
* 由于是服务端事件,所以对应代码中应该加入到【mapAttrsServerSys.py】中
|
||||
```python
|
||||
def Init(self):
|
||||
...
|
||||
util.ListenServerEngineEvent("MobGriefingBlockServerEvent", self, self.OnMobGriefingBlock)
|
||||
...
|
||||
|
||||
def OnMobGriefingBlock(self, data):
|
||||
# print "OnMobGriefingBlock", data
|
||||
if data["blockName"] in ("minecraft:farmland", ):
|
||||
data["cancel"] = True
|
||||
```
|
||||
### 阻止触发压力板、踩红石矿、踩拌线钩
|
||||
* 查询ModSDK的事件,发现可以用【MobGriefingBlockServerEvent】实现目标功能
|
||||

|
||||
* 由于是服务端事件,所以对应代码中应该加入到【mapAttrsServerSys.py】中
|
||||
```python
|
||||
def Init(self):
|
||||
...
|
||||
util.ListenServerEngineEvent("StepOnBlockServerEvent", self, self.OnStepOnBlock)
|
||||
...
|
||||
|
||||
def OnStepOnBlock(self, data):
|
||||
# print "OnStepOnBlock", data
|
||||
data["cancel"] = True
|
||||
```
|
||||
|
||||
## 部署修改后的插件
|
||||
* 打开自定义目录的【neteaseMapAttrs】插件,放置一个【server.properties】文件(调整游戏的模式,方便调试修改后的效果)
|
||||

|
||||
* 修改【level-type】为【FLAT】(地形超平坦)
|
||||
* 修改【gamemode】为【1】(创造模式)
|
||||

|
||||
* 为了能够执行创建耕地、放置压力板等操作,需要修改【neteaseMapAttrs】插件中的mod.json,把【open_map_protect】配置修改为false
|
||||

|
||||
* 点开【服务器配置】-->【选择文件夹】,修改【Mod目录】为放置修改后插件的自定义目录
|
||||

|
||||
* 点击【游戏配置】-->【大厅服】-->【Mod的下拉菜单】,勾选【neteaseMapAttrs(自定义目录)】
|
||||

|
||||
* 点击【游戏配置】-->【控制服】-->【Mod的下拉菜单】,勾选【neteaseMapAttrsMaster(自定义目录)】
|
||||

|
||||
* 选择【基岩版服务器】-->【网络服开发】-->【地图属性插件微调】-->点击【部署】,等待服务器部署完成
|
||||

|
||||
* 点击【开发测试】即可启动客户端,体验微调后的**地图属性插件**的功能
|
||||

|
||||
626
mcguide/27-手机网络游戏/课程5:插件教学/第6节:聊天插件调整.md
Normal file
@@ -0,0 +1,626 @@
|
||||
---
|
||||
front:
|
||||
hard: 进阶
|
||||
time: 45分钟
|
||||
---
|
||||
|
||||
# 聊天插件调整
|
||||
|
||||
## 概述
|
||||
|
||||
本教学文档从官方的聊天插件触发,介绍网络服插件的目录结构,事件侦听、回调流程,功能服(Service)概念,以及微调插件代码增加聊天前缀。
|
||||
|
||||
教学文档目录简介如下:
|
||||
|
||||
- 插件架构分析:主要说明官方插件的目录结构、功能,以及在具体运行中,事件是如何侦听与回调。
|
||||
- 功能服概念:功能服(Service)作为连接众多game服、lobby服的中心,若需要在这些服务器之间通信,必须要引入功能服(Service)。
|
||||
- 聊天插件调整:介绍如何调整插件代码,实现聊天前缀增加服务器名称的功能。
|
||||
|
||||
|
||||
|
||||
## 插件架构分析
|
||||
|
||||
### 目录结构
|
||||
|
||||
一共有两个mod,`neteaseChat`和`neteaseChatService`,目录结构如下
|
||||
|
||||
```
|
||||
neteaseChat ------------------------------- 聊天插件(不属于大厅服或游戏服)
|
||||
│ readme.txt ----------------------------- 说明文件
|
||||
│
|
||||
├─behavior_packs -------------------------- 行为包总目录
|
||||
│ └─neteaseChatBehavior ------------------ 插件行为包
|
||||
│ │ manifest.json ------------------- 行为包记录文件,用于标识行为包
|
||||
│ │
|
||||
│ └─neteaseChatScript ---------------- 客户端Mod脚本根目录
|
||||
│ │ chatConsts.py --------------- 常量定义
|
||||
│ │ chatManager.py -------------- 管理聊天信息的类,用于不同channel的聊天管理
|
||||
│ │ modMain.py ------------------ Mod入口文件
|
||||
│ │ neteaseChatClientSystem.py -- Mod的system类
|
||||
│ │ __init__.py ----------------- 初始化文件
|
||||
│ │
|
||||
│ └─ui --------------------------- 客户端Mod的UI脚本根目录
|
||||
│ neteaseChatUI.py ------- 聊天界面脚本,实现具体逻辑
|
||||
│ uiDef.py --------------- UI定义脚本
|
||||
│ uiMgr.py --------------- UI管理脚本,用于管理各个界面的接口
|
||||
│ __init__.py ------------ 初始化文件
|
||||
│
|
||||
├─developer_mods --------------------------- 服务端Mod总目录
|
||||
│ └─neteaseChatDev ------------------------ 插件开发包
|
||||
│ │ mod.json ------------------------- 插件配置信息文件
|
||||
│ │
|
||||
│ └─neteaseChatScript ----------------- 服务端Mod脚本根目录
|
||||
│ chatConsts.py --------------- 常量定义
|
||||
│ chatManager.py -------------- 管理聊天的类
|
||||
│ modMain.py ------------------ Mod入口文件
|
||||
│ neteaseChatServerSystem.py -- Mod的system类
|
||||
│ __init__.py ----------------- 初始化文件
|
||||
│
|
||||
├─resource_packs --------------------------- 资源包总目录
|
||||
│ └─neteaseChatResource ------------------- 插件资源包
|
||||
│ │ manifest.json -------------------- 行为包记录文件,用于标识资源包
|
||||
│ │
|
||||
│ ├─textures -------------------------- 贴图资源
|
||||
│ │ └─ui ----------------------------- UI贴图目录
|
||||
│ │ └─netease_chat --------------- 聊天界面UI贴图路径
|
||||
│ │
|
||||
│ └─ui -------------------------------- UI配置目录
|
||||
│ neteaseChatUI.json ---------- 聊天界面UI配置文件
|
||||
│ _ui_defs.json --------------- UI配置文件,用到的界面,目前只用到聊天界面
|
||||
│
|
||||
└─worlds ----------------------------------- 存档目录
|
||||
└─level -------------------------------- 存档目录
|
||||
world_behavior_packs.json ------ 记录用到的行为包
|
||||
world_resource_packs.json ------ 记录用到的资源包
|
||||
|
||||
neteaseChatService ------------------------- 聊天插件(部署于功能服)
|
||||
│ readme.txt ------------------------------ 说明文件
|
||||
│
|
||||
└─developer_mods --------------------------- 服务端Mod总目录
|
||||
└─neteaseChatDev ----------------------- 插件开发包
|
||||
│ mod.json ------------------------ 插件配置信息文件
|
||||
│
|
||||
└─neteaseChatScript ---------------- 客户端Mod脚本根目录
|
||||
chatConsts.py -------------- 常量定义
|
||||
chatManager.py ------------- 管理聊天信息的类,用于不同channel的聊天管理
|
||||
modMain.py ----------------- Mod入口文件
|
||||
neteaseChatServiceSystem.py Mod的system类
|
||||
__init__.py ---------------- 初始化文件
|
||||
```
|
||||
|
||||
### 主要模块
|
||||
|
||||
聊天插件主要分为三个部分,各部分说明如下
|
||||
|
||||
- 服务端Mod
|
||||
|
||||
- Server System: 服务端Mod System类,负责各节点通信和事件响应,定义于脚本
|
||||
|
||||
`neteaseChat\developer_mods\neteaseChatDev\neteaseChatScript\neteaseChatServerSystem.py`
|
||||
|
||||
- Server Chat Manager: 服务端Mod聊天消息管理类,负责各频道消息管理,定义于脚本
|
||||
|
||||
`neteaseChat\developer_mods\neteaseChatDev\neteaseChatScript\chatManager.py`
|
||||
|
||||
- 客户端Mod
|
||||
|
||||
- Client System: 客户端Mod System类,负责与客户端通信与事件响应,定义于脚本
|
||||
|
||||
`neteaseChat\behavior_packs\neteaseChatBehavior\neteaseChatScript\neteaseChatClientSystem.py`
|
||||
|
||||
- Client Chat Manager: 客户端Mod聊天消息管理,负责各频道消息管理,每个频道一个Chat Manager,定义于脚本
|
||||
|
||||
`neteaseChat\behavior_packs\neteaseChatBehavior\neteaseChatScript\chatManager.py`
|
||||
|
||||
- Client UI Manager: 客户端Mod UI管理类,负责客户端UI界面管理,定义于脚本`neteaseChat\behavior_packs\neteaseChatBehavior\neteaseChatScript\ui\uiMgr.py`
|
||||
|
||||
- Client Chat UI: 聊天界面,负责聊天消息显示与按钮响应,定义于脚本
|
||||
|
||||
`neteaseChat\behavior_packs\neteaseChatBehavior\neteaseChatScript\ui\neteaseChatUI.py`
|
||||
|
||||
- Service Mod
|
||||
|
||||
- Service System: 功能服Mod System类,负责各节点通信与事件响应,定义于脚本
|
||||
|
||||
`neteaseChatService\developer_mods\neteaseChatDev\neteaseChatScript\neteaseChatServiceSystem.py`
|
||||
|
||||
- Service Chat Manager: 功能服Mod聊天消息管理,负责转发各频道消息,定义于脚本
|
||||
|
||||
`neteaseChatService\developer_mods\neteaseChatDev\neteaseChatScript\chatManager.py`
|
||||
|
||||
|
||||
|
||||
### 运行流程
|
||||
|
||||
|
||||
|
||||
#### 初始化
|
||||
|
||||

|
||||
|
||||
初始化流程如上图所示,主要步骤为:
|
||||
|
||||
1. **System 与 Chat Manager初始化**:
|
||||
|
||||
1.1 Server System 初始化,创建世界频道和本地频道,本地频道为服务器id,世界频道为0
|
||||
|
||||
```python
|
||||
# ChatServerSystem
|
||||
def Init(self):
|
||||
self.modConfig = commonNetgameApi.GetModJsonConfig('neteaseChatScript')
|
||||
self.mServerid = netgameApi.GetServerId()
|
||||
self.mChatManagers[self.mServerid] = chatManager.ChatManager(self, self.mServerid)
|
||||
self.mChatManagers[chatConsts.ALL_SERVER_CHANNEL] = chatManager.ChatManager(self, chatConsts.ALL_SERVER_CHANNEL)
|
||||
self.mChatIntervalCD = {
|
||||
self.mServerid: self.modConfig['localeCD'],
|
||||
chatConsts.ALL_SERVER_CHANNEL: self.modConfig['worldCD']
|
||||
}
|
||||
self.mChatCD = {
|
||||
self.mServerid: {},
|
||||
chatConsts.ALL_SERVER_CHANNEL: {}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
1.2 Client System 初始化,创建UI Manager
|
||||
|
||||
```python
|
||||
class ChatClientSystem(ClientSystem):
|
||||
def __init__(self, namespace, systemName):
|
||||
ClientSystem.__init__(self, namespace, systemName)
|
||||
# 省略一些代码
|
||||
self.mUIMgr = uiMgr.UIMgr()
|
||||
```
|
||||
|
||||
|
||||
|
||||
1.3 Service System 初始化,响应`NetGameCommonConfChangeEvent`,创建各服频道和世界频道,服务器列表中的每个服务器创建一个频道,频道id为各服务器id,世界频道id为0
|
||||
|
||||
```python
|
||||
# ChatServiceSystem
|
||||
def OnNetGameCommonConfChangeEvent(self, args):
|
||||
serverIds = set()
|
||||
ccfg = netServiceApi.GetCommonConfig()
|
||||
print "OnNetGameCommonConfChangeEvent", ccfg
|
||||
for conf in ccfg.get("serverlist", []):
|
||||
if conf.get("app_type") in ("game", "lobby"):
|
||||
serverid = conf.get('serverid')#用serverid来区分频道
|
||||
serverIds.add(serverid)
|
||||
if self.mChatManagers.has_key(serverid) == False:
|
||||
self.mChatManagers[serverid] = chatManager.ChatManager(self, serverid)
|
||||
if self.mChatManagers.has_key(chatConsts.ALL_SERVER_CHANNEL) == False:
|
||||
self.mChatManagers[chatConsts.ALL_SERVER_CHANNEL] = chatManager.ChatManager(self, chatConsts.ALL_SERVER_CHANNEL)
|
||||
|
||||
self.mCommonConfig = serverIds
|
||||
```
|
||||
|
||||
|
||||
|
||||
2. **客户端响应 `UiInitFinished` 事件**
|
||||
|
||||
2.1 初始化客户单UI管理器并创建聊天界面
|
||||
|
||||
2.2 发送 `ClientUiInitFinished` 事件到服务端
|
||||
|
||||
```python
|
||||
# ChatClientSystem
|
||||
def OnUiInitFinished(self, args):
|
||||
self.mUIMgr.Init(self)
|
||||
data = self.CreateEventData()
|
||||
data["entityId"] = clientApi.GetLocalPlayerId()
|
||||
self.NotifyToServer("ClientUiInitFinished", data)
|
||||
```
|
||||
|
||||
|
||||
|
||||
2.3 服务端System响应`ClientUiInitFinished`,获取玩家uid和昵称,发送`ModConfigResponseFromServerEvent`事件和`TellYourPlayerUidAndSidEvent`给客户端
|
||||
|
||||
```python
|
||||
# ChatServerSystem
|
||||
def OnClientUiInitFinished(self, args):
|
||||
playerId = args.get("entityId")
|
||||
playerUid = netgameApi.GetPlayerUid(playerId)
|
||||
nickName = lobbyGameApi.GetPlayerNickname(playerId)
|
||||
self.NotifyToClient(playerId, "ModConfigResponseFromServerEvent", self.modConfig)
|
||||
self.NotifyToClient(playerId, "TellYourPlayerUidAndSidEvent", {"playerId":playerId, "playerUid":playerUid, "nickName":nickName, "serverid":self.mServerid, 'exBtnList': self.modConfig.get('exBtnList')})
|
||||
```
|
||||
|
||||
|
||||
|
||||
2.4 客户端响应 `ModConfigResponseFromServerEvent`事件,更新聊天插件配置
|
||||
|
||||
```python
|
||||
# ChatClientSystem
|
||||
def OnModConfigResponseFromServerEvent(self, modConfig):
|
||||
self.modConfig = modConfig
|
||||
```
|
||||
|
||||
|
||||
|
||||
2.5 客户端响应`TellYourPlayerUidAndSidEvent`,创建世界频道和本地频道,本地频道id为连接服务器的id,世界频道id为0
|
||||
|
||||
```python
|
||||
# ChatClientSystem
|
||||
def OnTellYourPlayerUidAndSidEvent(self, args):
|
||||
self.mMyPlayerUid = args.get("playerUid")
|
||||
self.mNickName = args.get("nickName")
|
||||
self.mServerid = args.get("serverid")
|
||||
self.mChatManagers[self.mServerid] = chatManager.ChatManager(self, self.mServerid)
|
||||
self.mChatManagers[chatConsts.ALL_SERVER_CHANNEL] = chatManager.ChatManager(self, chatConsts.ALL_SERVER_CHANNEL)
|
||||
self.mCurrentChannel = self.mServerid
|
||||
|
||||
exBtnList = args.get('exBtnList')
|
||||
if isinstance(exBtnList, list) and exBtnList != self.mExBtnList:
|
||||
self.mExBtnList = exBtnList
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### 主要流程
|
||||
|
||||

|
||||
|
||||
主要流程如上图所示,说明如下:
|
||||
|
||||
1. **显示聊天主界面**
|
||||
|
||||
1.1 在普通聊天框输入数字1
|
||||
|
||||
1.2 服务端响应 `ServerChatEvent`,检测到数字1,发送 `OpenChatList` 到客户端
|
||||
|
||||
```python
|
||||
# ChatServerSystem
|
||||
def OnServerChatEvent(self, args):
|
||||
playerId = args.get("playerId")
|
||||
if args["message"] == "1":
|
||||
self.NotifyToClient(playerId, "OpenChatList", {})
|
||||
```
|
||||
|
||||
|
||||
|
||||
1.3 客户端响应 `OpenChatList`,显示聊天主界面
|
||||
|
||||
```python
|
||||
# ChatClientSystem
|
||||
def OnOpenChatList(self, args = None):
|
||||
ui = self.mUIMgr.GetUI(uiDef.UIDef.UIChatMain)
|
||||
if ui:
|
||||
ui.Show(True)
|
||||
```
|
||||
|
||||
|
||||
|
||||
2. **切换频道**
|
||||
|
||||
2.1 在聊天主界面点击本地频道或者世界频道
|
||||
|
||||
2.2 调用Client System的ChangeChannel接口,更新频道信息,并广播`LocalChannelChange`
|
||||
|
||||
```python
|
||||
# ChatMainScreen
|
||||
def C0(self, args):
|
||||
touchEvent = args["TouchEvent"]
|
||||
touch_event_enum = extraClientApi.GetMinecraftEnum().TouchEvent
|
||||
if touchEvent == touch_event_enum.TouchUp:
|
||||
if self.mClientSystem.mServerid is not None:
|
||||
self.SetVisible(self.mMenuPanel, False)
|
||||
self.SetVisible(self.mClsBtn, False)
|
||||
self.SetVisible(self.mClsMainBtn, False)
|
||||
if self.mClientSystem.GetPlayerCurrentChannel() != 0:
|
||||
self.SetSprite(self.mChannelBar + '/c0/default', "textures/ui/netease_chat/btn01_select")
|
||||
self.SetSprite(self.mChannelBar + '/c1/default', "textures/ui/netease_chat/btn01")
|
||||
self.mClientSystem.ChangeChannel(0)
|
||||
|
||||
# ChatClientSystem
|
||||
def ChangeChannel(self, channel):
|
||||
self.mCurrentChannel = channel
|
||||
self.BroadcastEvent('LocalChannelChange', {"chatChannel": self.mCurrentChannel})
|
||||
```
|
||||
|
||||
|
||||
|
||||
2.3 响应`LocalChannelChange`,刷新频道聊天消息
|
||||
|
||||
```python
|
||||
# ChatMainScreen
|
||||
def OnLocalChannelChange(self, args):
|
||||
# 请查看源代码
|
||||
```
|
||||
|
||||
|
||||
|
||||
3. **发送消息**
|
||||
|
||||
3.1 聊天主界面发送消息
|
||||
|
||||
3.2 发送 `PlayerChatFromClientEvent`到服务端
|
||||
|
||||
```python
|
||||
# ChatMainScreen
|
||||
def OnSendButton(self, args):
|
||||
touchEvent = args["TouchEvent"]
|
||||
touch_event_enum = extraClientApi.GetMinecraftEnum().TouchEvent
|
||||
if touchEvent == touch_event_enum.TouchUp:
|
||||
# 省略一些代码
|
||||
self.mClientSystem.NotifyToServer("PlayerChatFromClientEvent", {"playerId":self.mLocalPlayerId, "message":s, "chatChannel":currentChannel})
|
||||
# 省略一些代码
|
||||
```
|
||||
|
||||
|
||||
|
||||
3.3 服务端响应`PlayerChatFfromClientEvent`,发送`ChatFromServerEvent`到功能服
|
||||
|
||||
```python
|
||||
# ChatServerSystem
|
||||
def OnPlayerChatFromClientEvent(self, args):
|
||||
# 省略一些代码
|
||||
self.RequestToService(chatConsts.ModNameSpace, "ChatFromServerEvent", chatDict)
|
||||
# 省略一些代码
|
||||
```
|
||||
|
||||
|
||||
|
||||
3.4 功能服响应`ChatFromServerEvent`,插入消息到对应频道
|
||||
|
||||
```python
|
||||
# ChatServiceSystem
|
||||
def OnChatFromServerEvent(self, serverId, callbackId, args):
|
||||
chatChannel = args["chatChannel"]
|
||||
print "OnChatFromServerEvent", self.mChatManagers.keys()
|
||||
if self.mChatManagers.has_key(chatChannel):
|
||||
self.mChatManagers[chatChannel].InsertChatMes(args)
|
||||
|
||||
# ChatManager
|
||||
def InsertChatMes(self, args):
|
||||
print "serviceInsertChatMes", args
|
||||
playerUid = args["playerUid"]
|
||||
chatDict = self.GenChatDict(args)
|
||||
# if self.mChatRecords.has_key(playerUid) == False:
|
||||
# self.mChatRecords[playerUid] = []
|
||||
self.mChatRecords.append(chatDict)
|
||||
self.TellServerNewChat(chatDict)
|
||||
```
|
||||
|
||||
|
||||
|
||||
3.5 功能服Chat Manager发送消息到频道所在服务器,如果是世界频道的消息,则发到所有服务器,通过发送`newChatFromServiceEvent`实现
|
||||
|
||||
```python
|
||||
# ChatManager
|
||||
def TellServerNewChat(self, chatDict):
|
||||
if self.mChatChannel != chatConsts.ALL_SERVER_CHANNEL:
|
||||
self.system.NotifyToServerNode(self.mChatChannel, "newChatFromServiceEvent", chatDict)
|
||||
else:
|
||||
serverlist = self.system.GetCommonConfig()
|
||||
for serverid in serverlist:
|
||||
self.system.NotifyToServerNode(serverid, "newChatFromServiceEvent", chatDict)
|
||||
```
|
||||
|
||||
|
||||
|
||||
3.6 服务端响应`newChatFromServiceEvent`,插入消息到对应频道
|
||||
|
||||
```python
|
||||
# ChatServerSystem
|
||||
def OnNewChatFromServiceEvent(self, chatDict):
|
||||
print "OnNewChatFromServiceEvent", chatDict
|
||||
chatChannel = chatDict["chatChannel"]
|
||||
if self.mChatManagers.has_key(chatChannel):
|
||||
self.mChatManagers[chatChannel].InsertChatMes(chatDict)
|
||||
|
||||
# ChatManager
|
||||
def InsertChatMes(self, chatDict):
|
||||
print "ServerInsertChatMes", chatDict
|
||||
playerUid = chatDict["playerUid"]
|
||||
# if self.mChatRecords.has_key(playerUid) == False:
|
||||
# self.mChatRecords[playerUid] = []
|
||||
self.mChatRecords.append(chatDict)
|
||||
self.TellClientNewChat(chatDict)
|
||||
```
|
||||
|
||||
|
||||
|
||||
3.7 服务端Chat Manager发送消息到所有客户端,通过发送`newChatFromServerEvent`实现
|
||||
|
||||
```python
|
||||
# ChatManager
|
||||
def TellClientNewChat(self, chatDict):
|
||||
self.system.BroadcastToAllClient("newChatFromServerEvent", chatDict)
|
||||
print "newChatFromServerEvent", chatDict
|
||||
```
|
||||
|
||||
|
||||
|
||||
3.8 客户端响应`newChatFromServerEvent`,插入消息到对应频道
|
||||
|
||||
```python
|
||||
# ChatClientSystem
|
||||
def OnNewChatFromServerEvent(self, chatDict):
|
||||
#print "OnNewChatFromServerEvent", chatDict
|
||||
#print "self.mChatManagers", self.mChatManagers
|
||||
chatChannel = chatDict["chatChannel"]
|
||||
if self.mChatManagers.has_key(chatChannel):
|
||||
self.mChatManagers[chatChannel].InsertChatMes(chatDict)
|
||||
# ChatManager
|
||||
def InsertChatMes(self, chatDict):
|
||||
print "ClientInsertChatMes", chatDict
|
||||
playerUid = chatDict["playerUid"]
|
||||
# if self.mChatRecords.has_key(playerUid) == False:
|
||||
# self.mChatRecords[playerUid] = []
|
||||
self.mUnReadChatRecords.append(chatDict)
|
||||
if len(self.mUnReadChatRecords) > chatConsts.MAX_CHAT_LEN:
|
||||
self.mUnReadChatRecords.pop(0)
|
||||
self.mAllChatRecords.append(chatDict)
|
||||
if len(self.mAllChatRecords) > chatConsts.MAX_CHAT_LEN:
|
||||
self.mAllChatRecords.pop(0)
|
||||
self.mDirty = True
|
||||
```
|
||||
|
||||
|
||||
|
||||
3.9 客户端Chat Manager定时刷新本频道的未读消息,广播事件`LocalNewChatRecord`
|
||||
|
||||
```python
|
||||
# ChatManager
|
||||
def RepeatedTellClientNewChat(self):
|
||||
if self.mDirty == True:
|
||||
self.system.BroadcastEvent('LocalNewChatRecord', {"chatChannel":self.mChatChannel})
|
||||
self.mDirty = False
|
||||
```
|
||||
|
||||
|
||||
|
||||
3.10 Chat UI响应`LocalNewChatRecord`,刷新当前频道消息
|
||||
|
||||
```python
|
||||
# ChatMainScreen
|
||||
def OnLocalNewChatRecord(self, args):
|
||||
# 请查看源代码
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 功能服概念
|
||||
|
||||
调整之前,需要先了解一下功能服(Service)的概念。
|
||||
|
||||
功能服连接所有服务器和数据库,能够获取所有服务器的信息,可以处理一些全服操作。比如在聊天插件中,如果在本地频道发消息,不需要经过功能服转发,但如果要在世界频道发消息,就需要在功能服中进行转发,把消息发到所有大厅服和游戏服。
|
||||
|
||||
|
||||
|
||||
## 聊天插件调整
|
||||
|
||||
通过一个例子,展示如何调整聊天插件。需求为:聊天内容增加前缀(所属服务器类型)
|
||||
|
||||
### 代码调整
|
||||
|
||||
代码调整主要有:
|
||||
|
||||
- 修改功能服 `neteaseChatServoice` 代码,转发消息时加上服务器类型
|
||||
- 修改大厅服和游戏服 `neteaseChat` 代码,增加切服逻辑,用来验证服务器类型
|
||||
- 修改大厅服和游戏服`neteaseChat`代码,消息前缀改为服务器类型
|
||||
|
||||
具体步骤为:
|
||||
|
||||
1. 转发消息时加上服务器类型
|
||||
|
||||
```python
|
||||
# ChatServiceSystem 修改
|
||||
# 创建Chat Manager时传入服务器类型
|
||||
def OnNetGameCommonConfChangeEvent(self, args):
|
||||
serverIds = set()
|
||||
ccfg = netServiceApi.GetCommonConfig()
|
||||
print "OnNetGameCommonConfChangeEvent", ccfg
|
||||
for conf in ccfg.get("serverlist", []):
|
||||
if conf.get("app_type") in ("game", "lobby"):
|
||||
serverid = conf.get('serverid')#用serverid来区分频道
|
||||
serverIds.add(serverid)
|
||||
if self.mChatManagers.has_key(serverid) == False:
|
||||
# 修改此处,增加服务器类型
|
||||
self.mChatManagers[serverid] = chatManager.ChatManager(self, serverid, conf['app_type'])
|
||||
if self.mChatManagers.has_key(chatConsts.ALL_SERVER_CHANNEL) == False:
|
||||
self.mChatManagers[chatConsts.ALL_SERVER_CHANNEL] = chatManager.ChatManager(self, chatConsts.ALL_SERVER_CHANNEL)
|
||||
|
||||
self.mCommonConfig = serverIds
|
||||
|
||||
# ChatManager修改
|
||||
# 创建ChatManager时保存 服务器类型
|
||||
class ChatManager(object):
|
||||
def __init__(self, system, channel, channelType = ''):
|
||||
import weakref
|
||||
self.system = weakref.proxy(system)
|
||||
self.mChatChannel = channel
|
||||
self.mChatRecords = []
|
||||
# 修改此处
|
||||
self.mType = channelType
|
||||
|
||||
# 拼接聊天消息时加入服务器类型
|
||||
def GenChatDict(self, args):
|
||||
chatDict = {
|
||||
"playerUid": args["playerUid"],
|
||||
"nickName": args["nickName"],
|
||||
"playerLevel": args["playerLevel"],
|
||||
"chatType": args["chatType"],
|
||||
"mes": args["mes"],
|
||||
"infoDict": args.get("infoDict", {}),
|
||||
"chatChannel": args["chatChannel"],
|
||||
"chatTime": time.time(), #用于客户端排序
|
||||
# 修改此处
|
||||
"serverType": self.mType
|
||||
}
|
||||
return chatDict
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
2. 增加切服逻辑
|
||||
|
||||
```python
|
||||
# 修改监听聊天消息处理,加入切服功能,输入"switch game"切到游戏服,输入 "switch lobby"切到大厅服
|
||||
# ChatServerSystem
|
||||
def OnServerChatEvent(self, args):
|
||||
playerId = args.get("playerId")
|
||||
if args["message"] == "switch game":
|
||||
import lobbyGame.netgameApi as lobbyGameApi
|
||||
lobbyGameApi.TransferToOtherServer(playerId, 'game')
|
||||
elif args["message"] == "switch lobby":
|
||||
import lobbyGame.netgameApi as lobbyGameApi
|
||||
lobbyGameApi.TransferToOtherServer(playerId, 'lobby')
|
||||
elif args["message"] == "1":
|
||||
self.NotifyToClient(playerId, "OpenChatList", {})
|
||||
```
|
||||
|
||||
3. 消息前缀改为服务器类型
|
||||
|
||||
```python
|
||||
# ChatMainScreen
|
||||
# OnLocalNewChatRecord 里调用 SetChatRichText
|
||||
# 用富文本显示聊天消息,需要修改富文本拼接逻辑,即richText赋值
|
||||
def SetChatRichText(self, richItem, chatData):
|
||||
serverType = chatData["serverType"]
|
||||
# 省略一些代码
|
||||
if chatType == chatConsts.ChatType.Item:
|
||||
# 省略一些代码
|
||||
richText = "§e[%s]§r%s 说: " % (serverType, playerText) + re.sub(r"/\[item ([0-9]{1,2})\]", "<link>%s</link>" % linkText.replace('\n', '\\n'), mes)
|
||||
elif chatType == chatConsts.ChatType.Common:
|
||||
richText = "§e[%s]§r%s 说: " % (serverType, playerText) + mes
|
||||
# 省略一些代码
|
||||
elif chatType == chatConsts.ChatType.Team:
|
||||
richText = "§e[%s]§r%s 邀请大家一起加入队伍。%s" % (serverType, playerText, mes)
|
||||
# 省略一些代码
|
||||
```
|
||||
|
||||
|
||||
### 部署
|
||||
|
||||
1. 大厅服和游戏服配置聊天插件
|
||||
|
||||
- 大厅服部署聊天服插件
|
||||
|
||||

|
||||
|
||||
- 游戏服部署聊天服插件
|
||||
|
||||

|
||||
|
||||
2. 部署服务器
|
||||
|
||||

|
||||
|
||||
3. 进入游戏测试
|
||||
|
||||
- 先进入大厅服,聊天主界面输入消息`hello world`,显示的消息带上了服务器类型 `lobby`
|
||||
|
||||

|
||||
|
||||
- 聊天框输入`switch game`切到游戏服,聊天主界面输入消息`hello world`,显示的消息带上了服务器类型 `game`
|
||||
|
||||

|
||||
|
||||
**插件调整完成!**
|
||||
|
||||
|
||||
|
||||
|
||||
32
mcguide/27-手机网络游戏/课程5:插件教学/第7节:插件系列教程.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
front:
|
||||
hard: 进阶
|
||||
time: 120分钟
|
||||
---
|
||||
|
||||
# 插件系列教程
|
||||
|
||||
为了便于开发者从零开始学习插件编写,我们将相关的知识点整理成教程,可根据以下目录导读与跳转链接进行阅读。
|
||||
|
||||
|
||||
|
||||
### 目录导读
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
- 01准备知识——主要介绍网络服、数据库、插件的基础概念,开发所需测试机器的申请与链接方式。建议刚拿到测试机的开发者阅读本章内容。
|
||||
|
||||
- 02初识插件——主要介绍如何使用下载、使用官方插件,并以管理员(OP)身份,调试官方插件。建议可通过本章内容,熟悉插件的使用方式。
|
||||
|
||||
- 03插件知识进阶——主要介绍开服工具框架,不同类型服务器之间的通信方式以及插件的目录结构。阅读本章后,可从原理上了解开服工具,为后续的插件开发提供理论基础。
|
||||
|
||||
- 04插件制作——本章以实战内容为主,先介绍插件开发的规范,再以官方插件为例拆解相关知识点,最后以一个全新插件为例,从需求分析、架构规划到代码编写,详细说明插件开发。开发者可根本本章的说明与代码,从实战中学习插件开发。
|
||||
|
||||
|
||||
|
||||
### 跳转链接
|
||||
|
||||
[课程链接](https://mc.163.com/dev/mcmanual/mc-dev/mconline/30-网络服插件教程/1-准备知识/0-插件的概念.html)
|
||||
|
||||