feat:上传mcguide-开发指南部份

This commit is contained in:
Othniel su
2024-12-23 10:57:59 +08:00
parent 7292166c88
commit 0dc59fa4f0
3297 changed files with 63375 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,60 @@
---
front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/1559293031316.8e6c38fc.png
hard: 入门
time: 10分钟
---
# 框架
## 框架介绍
![](./images/1559293031316.png)
- DB是全局存储系统所有游戏服共享可以是redis、mysql或mongodb等。其中redis用于缓存临时数据比如玩家在线状态、当日在线时长等mysql和mongo用于持久化存储游戏数据。开发者根据需求自选。
- proxy是代理服功能包括消息的加密和解密消息的压缩和解压缩登录认证和消息转发。它保持客户端到服务端的连接。 开发者不能对proxy进行开发。
- game是游戏服提供游戏逻辑功能一个在线玩家只存在于一个game或lobby中。 开发者在game上进行游戏玩法开发比如实现酷跑游戏、射击游戏、战斗游戏。
- lobby是大厅服提供大厅各项功能。 开发者在lobby开发大厅的功能比如提供NPC选服入口提供战斗副本入口。
- master是控制服用于管理其他服是全服单点对外提供http服务。http服务是运营指令gm指令入口。 开发者可以在master开发运营指令比如发奖励指令、禁止发言指令。下面通过禁言指令需求介绍master功能
- 需求:某玩家言词不当,需要禁止他聊天。
- 实现在master添加禁言指令。开发者使用http给master发送禁言请求master会把禁言信息记录到db然后给玩家所在服务器发消息禁止玩家发言。玩家下次登陆时从db中读取禁言信息判断是否还可以聊天。
- service是功能服用于提供分布式单点服务。开发者可以在service开发公会、全服boss、全服匹配等功能。下面通过全服匹配需求介绍service功能
- 需求存在lobby1和lobby2两个大厅服两个大厅服内玩家要按照等级、战力等属性进行匹配进入同一个副本游戏。
- 实现玩家申请匹配时lobby1或lobby2向service申请匹配service维护一个匹配队列记录所有正匹配玩家service会定时取出队列玩家按照等级和战力匹法将匹配后玩家分配到指定副本游戏。
## 使用示例
通过一个简单网络游戏需求介绍开服工具框架的使用。可在McStudio——基岩版网络服分页选择“简易网络服”模板点击新建按钮创建该示例。
### 需求
玩家进入大厅服后选择体验三种游戏生存服“钻石服”对战pvp另外游戏提供禁言指令。对战pvp要求一场战斗最多两个玩家。
### 实现
下面介绍一下简易服的功能:
- masterMod实现了一个获取玩家在线状态的运营指令
- serviceMod实现全服匹配。service维护匹配队列记录所有正在匹配玩家接着按照玩家等级匹配将匹配成功的两个玩家分配到pvp服
- AwesomeGameMod实现了一个基础的生存服
- TutorialGameMod玩家在聊天框里面输入"钻石剑""钻石镐""钻石头盔""钻石胸甲""钻石护腿""钻石靴子"会获得相应的装备
- OrdinaryGameMod简单的对战pvp
- lobbyMod提供三个NPC点击不同NPC分别进入不同的game服
### 功能执行过程
说明玩家体验游戏过程中引擎开服工具框架和开发者mod分别完成的功能。
#### 进入钻石服
1. 玩家登陆进入lobby引擎会将玩家分配到lobby。
2. 玩家点击NPC-B玩家切服到的TutorialGameModlobbyMod实现NPC和切服功能
3. 玩家在聊天框输入"钻石剑",会获得一把钻石剑
4. 点击回城NPC玩家可以回到lobby
#### 进入对战pvp
1. 玩家A和玩家B登陆到lobby引擎会将玩家分配到对应lobby
2. 玩家A和玩家B点击NPC-C申请匹配开发者lobbyMod向service申请匹配serviceMod完成匹配将玩家分配到OrdinaryGameMod
3. 点击回城NPC玩家可以回到lobby
#### 进入生存服
1. 玩家登陆进入lobby引擎会将玩家分配到lobby。
2. 玩家点击NPC-A玩家切服到的AwesomeGameModlobbyMod实现NPC和切服功能
3. 点击回城NPC玩家可以回到lobby

View File

@@ -0,0 +1,151 @@
---
front:
hard: 入门
time: 10分钟
---
# 服务器Mod目录
## Mod类型
服务器Mods通常由控制服Mod功能服Mod大厅服Mod与游戏服Mod等组成各种类型的Mod概念如下
- 控制服Mod包含控制服(master)的developer_mods目录下所有mod。
- 功能服Mod包含功能服(service)的developer_mods目录下所有mod。
- 大厅服Mod包含大厅服的 behavior_packs、resource_packs、developer_mods目录下所有的mod以及worlds地图存档
- 游戏服Mod包含游戏服的 behavior_packs、resource_packs、developer_mods目录下所有的mod以及worlds地图存档
一个游戏服Mod的**标准目录格式**如下:
![](./images/image071.png)
## developer_mods
服务端加载的mod目录不会被传送到客户端。
下面以neteaseAnnounce为例介绍其目录结构
neteaseAnnounceDev
netease_require.json
neteaseAnnounceScript
__init__.py
announceConsts.py
announceServerSystem.py
modMain.py
timermanager.py
mod.json
|文件/文件夹|解释|
|--------|----------|
|neteaseAnnounceDev| 顶层neteaseAnnounceDev表示mod的名字第二级neteaseAnnounceScript表示行为包的目录开发者从从该目录开始import module比如import neteaseAnnounceScript.announceConsts as announceConsts |
|`__init__.py`| 是python module的标识表示这是一个可以import的module同时也可以做一些初始化的操作,内容可为空,但是文件必须有。 |
|announceConsts.py| mod中的一些宏定义|
|announceServerSystem.py|mod业务逻辑|
|modMain.py|该文件名称不可以更改用来初始化我们的Mod具体使用参考<a href="../../20-玩法开发/13-模组SDK编程/2-Python脚本开发/0-脚本开发入门.html#modmain-py是什么" target="_blank">mod开发简介</a>|
|timermanager.py|实现了一个定时器提供了一次性定时器和循环定时器当前ModSDK中已经有实现相同功能的组件不再需要自己实现|
|netease_require.json| 用于控制服务器Mod加载顺序的配置文件非必须具体内容见《1-9 控制服务器Mod加载顺序的办法》|
|mod.json| 用于配置mod的具体功能实现细节行为的配置文件非必须|
developer_mods和behavior_packs区别
- developer_mods控制服务端行为behavior_packs控制客户端行为。
- behavior_packs会下载给客户端developer_mods不会。
- developer_mods可以使用Server Mod SDK全部接口和MOD SDK中服务端相关接口behavior_packs使用MOD SDK中客户端相关接口。
- behavior_packs必须包含manifest.json文件且需要在地图目录下world_behavior_packs.json文件中配置pack id和version。
developer_mods支持多个mod每个mod对应一个目录下面是neteaseAnnounce和neteaseAlert两个mod的目录结构
neteaseAnnounceDev
netease_require.json
neteaseAnnounceScript
__init__.py
announceConsts.py
announceServerSystem.py
modMain.py
timermanager.py
mod.json
neteaseAlertDev
neteaseAlertScript
__init__.py
alertConst.py
alertServerSystem.py
modMain.py
mod.json
## resource_packs
- 存放客户端资源
- 资源版本信息存放在manifest.json
```json
{
"format_version": 1,
"header": {
"description": "By tnm",
"name": "tnm_glove_pve",
"uuid": "1c850d23-64f4-46be-aec0-16c8e4618072",
"version": [0, 0, 1]
},
"modules": [
{
"description": "By tnm",
"type": "resources",
"uuid": "637ff742-7003-4a8b-8d99-722a1b704f12",
"version": [0, 0, 1]
}
]
}
```
- 并非所有resource_packs都会被客户端下载
- 需要将`manifest.json`配置header中uuid配置到worlds/level/`world_resource_packs.json`中才会被客户端下载
- `world_resource_packs.json`内容如下:
```python
[
{
"pack_id" : "1c850d23-64f4-46be-aec0-16c8e4618072",
"version" : [ 0, 0, 1 ]
}
]
```
## behavior_packs
- 存放客户端MOD
- manifest.json& world_behavior_packs.json配置与resource_packs类似
- behavior_packs里只允许有一个目录如果有多个则只有第一个会被使用请按*behavior格式命名大小写不敏感
- `*behavior`目录内脚本需要存放在*scripts目录内且只允许有一个
- `*behavior`目录内需包含版本信息配置`manifest.json`
## worlds
- 只允许有一个子目录如demo中的level
- 目录名作为地图名称,不允许使用中文
- 不要忘了配置`world_behavior_packs.json`等两个json
一个示例的目录结构如下:
worlds
level
db
level.dat
levelname.txt
world_behavior_packs.json
world_resource_packs.json
|文件/文件夹|解释|
|--------|----------|
|worlds| 存放服务器地图目录,名字不可以更改|
|level| 存放一个地图存档目录,目录名也就是地图名|
| db| 地图存档目录|
| level.dat| 存储关于地图的全局信息|
|levelname.txt|地图的名字|
|world_behavior_packs.json|配置客户端需要下载的behavior mods|
|world_resource_packs.json|配置客户端需要下载的resource mods|

View File

@@ -0,0 +1,375 @@
---
front:
hard: 进阶
time: 20分钟
---
# 进入和退出游戏
主要包含登录、定时存档、登出、切服等功能。
## 登录
Apollo引擎在登录过程中会处理顶号问题开发者开发过程不用考虑顶号。登录过程主要涉及下面事件
- client 相关事件:
- OnUIInitFinished此时可以创建UI了但是玩家每次切维度都会触发这个事件
- OnLocalPlayerStopLoading出生点地形加载完成时触发切维度不会触发本事件
- master相关事件
- PlayerLoginServerEvent登录master事件可以区分登录和切服
- lobby/game相关事件
- AddServerPlayerEvent登录到lobby/game 事件,可以区分登录和切服
### 服务端登录开发
下面开发LobbyMod服务端登录功能。首先处理登录逻辑监听AddServerPlayerEvent事件初始化玩家信息核心代码如下
```python
class AwesomeServer(ServerSystem):
def __init__(self, namespace, systemName):
ServerSystem.__init__(self, namespace, systemName)
#注册事件
self.ListenForEvent(
serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
modConfig.AddServerPlayerEvent,
self, self.OnAddServerPlayer
)
...
def OnAddServerPlayer(self, args):
'''
添加玩家的监听函数
'''
playerId = args.get('id','-1')
uid = netgameApi.GetPlayerUid(playerId)
self.mPlayerid2uid[playerId] = uid
```
### 读写数据库
玩家游戏数据通常存储到mysql。apollo应该尽量异步读写mysql避免阻塞游戏逻辑导致全服玩家卡顿。apollo提供“mysql连接池”提供各种异步读写mysql接口。
我们以LobbyMod为例介绍“mysql连接池”使用方法。首先我们创建表playerCol用于存储玩家数据创表语句如下
```sql
CREATE TABLE IF NOT EXISTS `playerCol` (
`uid` bigint unsigned NOT NULL,
`nickname` varchar(50) NOT NULL,
`login_time` bigint unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
接着我们读写表playerCol玩家登录时从mysql读取玩家数据若没查到数据则向mysql插入玩家信息记录新玩家信息。核心代码如下
```python
class AwesomeServer(ServerSystem):
def __init__(self, namespace, systemName):
...
self.mysqlMgr = MysqlOperation()
def OnAddServerPlayer(self, args):
'''
添加玩家的监听函数
'''
...
self.mysqlMgr.QueryPlayerData(playerId,uid,lambda data: self.QuerySinglePlayerCallback(playerId, uid, data))
def QuerySinglePlayerCallback(self, player_id, uid, data):
'''
回调函数。若玩家存在,则注册玩家;否则记录玩家信息
'''
# 数据库请求返回时,玩家已经主动退出了
if not self.playerid2uid.has_key(player_id):
return
if not data:# 找不到玩家数据,注册一个新玩家
nickname = netgameApi.GetPlayerNickname(player_id)
data = playerData.PlayerData.getNewPlayerInfo(uid, nickname)
self.InsertPlayerData(player_id, uid)
#记录玩家数据
player = playerData.PlayerData()
if isinstance(data,tuple):
data = player.changeMysqlTupleToPlayerDict(data)
player.initPlayer(player_id, data)
#刷新玩家登录时间
player.refreshLoginTime()
self.player_map[uid] = player
```
### 同步玩家数据
玩家登录后需要从服务端获取玩家信息。我们以LobbyMod为例客户端登录后从服务端获取玩家登录时间和昵称。
#### 客户端登录开发
监听OnOnLocalPlayerStopLoading事件请求获取玩家数据接着监听服务端发过来的LoginResponseEvent事件然后记录玩家数据。客户端监听UiInitFinished事件开始初始化UI。代码如下
```python
class AwesomeClient(ClientSystem):
def __init__(self,namespace,systemName):
ClientSystem.__init__(self,namespace,systemName)
...
# 注册事件
self.ListenForEvent(
modConfig.Minecraft,
modConfig.LobbyServerSystemName,
modConfig.LoginResponseEvent,
self, self.OnLoginResponse
)
self.ListenForEvent(
clientApi.GetEngineNamespace(),
clientApi.GetEngineSystemName(),
OnLocalPlayerStopLoading,
self, self.OnOnLocalPlayerStopLoading
)
self.ListenForEvent(
clientApi.GetEngineNamespace(),
clientApi.GetEngineSystemName(),
'UiInitFinished',
self, self.OnUiInitFinished)
self.mMyPlayerData = None
def OnUiInitFinished(self, args):
'''
初始化UI
'''
print 'OnUiInitFinished', args
self.InitUi()
def OnOnLocalPlayerStopLoading(self,args):
'''
请求登录到服务端,获取玩家数据
'''
logger.info("OnOnLocalPlayerStopLoading : %s", args)
playerId = clientApi.GetLocalPlayerId()
loginData = {}
loginData['id'] = playerId
self.NotifyToServer(modConfig.LoginRequestEvent, loginData)
def OnLoginResponse(self, args):
'''
初始化玩家数据,然后开始客户端逻辑
'''
logger.info("OnLoginResponse : %s", args)
player_info = args
self.mMyPlayerData = playerData.PlayerData()
self.mMyPlayerData.initPlayer(player_info['player_id'], player_info)
self.InitUi()
```
#### 服务端登录开发
监听客户端自定义LoginRequestEvent事件设置玩家出生点和维度将玩家数据返回给客户端。服务端从mysql中读取玩家数据是个异步过程收到LoginRequestEvent事件后可能还没初始化玩家信息因此需要延迟推送。核心代码如下
```python
class AwesomeServer(ServerSystem):
def __init__(self, namespace, systemName):
...
self.ListenForEvent(modConfig.Minecraft, modConfig.LobbyClientSystemName,
modConfig.LoginRequestEvent, self, self.OnLoginRequest)
def OnLoginRequest(self, data):
'''
玩家登录逻辑
'''
player_id = data['id']
uid = netgameApi.GetPlayerUid(player_id)
# 设置玩家位置和维度
comp = serverApi.GetEngineCompFactory().CreateDimension(player_id)
comp.ChangePlayerDimension(4, (1395.664, 5.2, 51.441))
CoroutineMgr.StartCoroutine(self._DoSendLoginResponseData(player_id, uid))
def _DoSendLoginResponseData(self, player_id, uid):
'''
将玩家数据推送给客户端。若还没从db获取玩家数据则延迟5帧再试
'''
if uid in self.player_map:
player = self.player_map[uid]
event_data = player.toSaveDict()
event_data['player_id'] = player_id
self.NotifyToClient(player_id, modConfig.LoginResponseEvent, event_data)
return
yield -5
```
### 验证功能
用MCStudio进入游戏查看相关信息
- 登录到linux开发机切到lobby的logs目录可以看到OnAddServerPlayer方法打印的登录日志
![image-20210224192724141](./images/image-20210224192724141.png)
- 进入mysql可以查看到玩家数据
![image-20210224192541433](./images/image-20210224192541433.png)
- MCStudio中查看玩家登录日志也即OnLoginResponse方法打印的日志
![image-20210224192458475](./images/image-20210224192458475.png)
## 定时存档
通过SetUseDatabaseSave函数打开定时存档功能引擎会定时触发savePlayerDataEvent/savePlayerDataOnShutDownEvent事件。mod监听这两个事件然后执行存档逻辑。定时存档把存档从游戏逻辑中解耦出来让开发者集中于游戏逻辑的开发。
### 服务端mod开发
设置定时存档然后监听savePlayerDataEvent/savePlayerDataOnShutDownEvent事件。核心代码如下
```python
class AwesomeServer(ServerSystem):
def __init__(self, namespace, systemName):
...
netgameApi.SetUseDatabaseSave(True, "awesome", 120)#定时存档时间间隔是120s
netgameApi.SetNonePlayerSaveMode(True)
self.ListenForEvent(
serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
'savePlayerDataEvent',
self, self.OnSavePlayerData
)
self.ListenForEvent(
serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
'savePlayerDataOnShutDownEvent',
self, self.OnSavePlayerData
)
...
def OnSavePlayerData(self, args):
'''
把玩家数据存档。这个函数一定要调用save_player_data_result函数把存档状态告知引擎。
'''
uid = int(args["playerKey"])
cpp_callback_idx = int(args["idx"])
player_data = self.mPlayerMap.get(uid, None)
if not player_data:
#告知引擎存档状态。注意传入回调函数id
netgameApi.SavePlayerDataResult(cpp_callback_idx, True)
def _SavePlayerCb(args):
uid, ret = args
if ret:
netgameApi.SavePlayerDataResult(cpp_callback_idx, True)
else:
netgameApi.SavePlayerDataResult(cpp_callback_idx, False)
self.SavePlayerByUid(uid, _SavePlayerCb)
def SavePlayerByUid(self, uid, cb = None):
'''
保存玩家数据
'''
player = self.mPlayerMap.get(uid, None)
if not player:
return
player_dict = player.toSaveDict()
if self.mDBType == DbType.Mongo:
self.mMongoMgr.SavePlayerByUid(uid,player_dict,cb)
elif self.mDBType == DbType.Mysql:
self.mMysqlMgr.SavePlayerByUid(uid,player_dict,cb)
```
### 验证功能
- 用MCStudio进入游戏在游戏停留2min然后在db中查看玩家数据发现login_time发生了变化。
- 开发者也可以在OnSavePlayerData函数中添加额外日志接着用MCStudio进入游戏不退出然后查看 lobby服日志可以发现lobby会定时打印对应日志。
### 总结
- 通过SetUseDatabaseSave函数开启定时存档。
- 监听savePlayerDataEvent/savePlayerDataOnShutDownEvent事件处理存档逻辑。
## 登出
### 登出过程介绍
登录过程主要涉及下面事件:
- master相关事件
- PlayerLogoutServerEvent玩家退出或切服
- lobby/game 相关事件:
- DelServerPlayerEvent玩家退出或切服可以处理玩家登出逻辑
下面介绍LobbyMod登出逻辑的开发。
### 服务端登出逻辑。
监听DelServerPlayerEvent事件把玩家数据存档并清除玩家数据。代码如下
```python
class AwesomeServer(ServerSystem):
def __init__(self, namespace, systemName):
...
self.ListenForEvent(
serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
'DelServerPlayerEvent',
self, self.OnDelServerPlayer
)
def OnDelServerPlayer(self, args):
'''
清除玩家内存数据。
'''
player_id = args.get('id','-1')
logout.info("OnDelServerPlayer player id=%s"% player_id)
uid = self.mPlayerid2uid.get(player_id, None)
if not uid:
return
self.SavePlayerByUid(uid)
del self.mPlayerid2uid[player_id]
if uid in self.mPlayerMap:
del self.mPlayerMap[uid]
if uid in self.mUid2dimension:
del self.mUid2dimension[uid]
```
### 功能验证
用MCStudio进入游戏后立马退出。打开lobby日志可以查看到登出日志
![image-20210224193054128](./images/image-20210224193054128.png)
### 总结
- 监听OnDelServerPlayer事件将玩家数据存档并清除脚本层中玩家内存数据。
## 切服
玩家切服是从一个服务器退出然后再登录到指定服务器过程实质是退出游戏然后再进入游戏过程。目前登入事件AddServerPlayerEvent是可以区分登录和切服登出事件DelServerPlayerEvent也可以区分切服过程中登出还是退出游戏。
## 服务器关服
服务器关服前会触发ServerWillShutDownEvent事件开发者可以在该事件中处理存档和清理现场的逻辑。
AwesomeGame服务端在退出时需要保存所有在线玩家数据。核心代码如下
```python
class AwesomeServer(ServerSystem):
def __init__(self, namespace, systemName):
...
self.ListenForEvent(
serverApi.GetEngineNamespace(),
serverApi.GetEngineSystemName(),
'ServerWillShutDownEvent',
self, self.OnServerWillShutDown
)
...
def OnServerWillShutDown(self, args):
# 即将关机,先给所有还在线玩家挂一个存档任务
for uid, player in self.mPlayerMap.iteritems():
self.SavePlayerByUid(uid)
# 同步完成所有还挂着的异步数据库操作
if self.mDBType == DbType.Mongo:
self.mMongoMgr.Destroy()
elif self.mDBType == DbType.Mysql:
self.mMysqlMgr.Destroy()
```
总结:
- 监听OnServerWillShutDown事件清理服务端现场。

View File

@@ -0,0 +1,409 @@
---
front:
hard: 进阶
time: 20分钟
---
# 游戏玩法
这里介绍简易网络服模板的玩法实现包括NPC以及匹配两块内容。
简易网络服模板的大厅服中有三个NPC玩家点击NPC可以实现切服功能。点击NPC-A显示GameA在线人数可以跳转到包含AwesomeGameMod的GameAGameA实现的是常规的生存服。点击NPC-B可以显示GameB在线人数可以跳转到包含TutorialGameMod的GameB在GameB的聊天栏中输入“钻石剑”可在背包中获得钻石剑*1。点击NPC-C可以实现简单匹配当匹配中玩家≥2人时将这些玩家传入gameC服。在每个Game服中都有一个返回大厅NPC。
## NPC
### NPC实现
npc由npc插件实现只需要配置插件里的mod.json即可生成对应npc。具体配置如下:
```python
{
"_comment":"mod的名字",
"netgame_mod_name":"neteaseNpcDev",
"_comment":"mod的版本号",
"netgame_mod_version":"1.0.4",
"_comment":"适用最低的引擎版本",
"min_app_version":"1.15.0.release20191226",
"_comment":"该服务器Mod隶属于“功能NPC”插件",
"description":"Npc mod插件",
"_comment":"适用的服务器类型",
"support_server_type":["lobby","game"],
"_comment":"作者名字",
"author": "NetEase",
"_comment":"NPC类型参数配置",
"NPCS_TYPE": {
"_comment":"typeId和NPCS_DISTRIBUTE中的typeId对应",
"1001":{
"name": "NPC-A",
"identifier": "minecraft:npc",
"simpleStyle": false,
"modName": "Minecraft",
"systemName": "AwesomeLobby",
"funcName": "OnNpcTouched",
"funcArgs": ["gameA"]
},
"1002":{
"name": "NPC-B",
"identifier": "minecraft:npc",
"simpleStyle": false,
"modName": "Minecraft",
"systemName": "AwesomeLobby",
"funcName": "OnNpcTouched",
"funcArgs": ["gameB"]
},
"1003":{
"name": "NPC-C",
"identifier": "minecraft:npc",
"simpleStyle": false,
"modName": "Minecraft",
"systemName": "AwesomeLobby",
"funcName": "OnNpcTouched",
"funcArgs": ["gameC"]
},
"1004":{
"name": "返回大厅NPC",
"identifier": "minecraft:npc",
"simpleStyle": false,
"modName": "AwesomeGame",
"systemName": "FpsServerSystem",
"funcName": "OnNpcTouched",
"funcArgs": ["lobby"]
},
"1005":{
"name": "返回大厅NPC",
"identifier": "minecraft:npc",
"simpleStyle": false,
"modName": "Minecraft",
"systemName": "TutorialGame",
"funcName": "OnNpcTouched",
"funcArgs": ["lobby"]
},
"1006":{
"name": "返回大厅NPC",
"identifier": "minecraft:npc",
"simpleStyle": false,
"modName": "Minecraft",
"systemName": "gameMod",
"funcName": "OnNpcTouched",
"funcArgs": ["lobby"]
}
},
"_comment":"NPC分布列表",
"NPCS_DISTRIBUTE": [
{
"typeId" : "1001",
"server" : "lobby",
"pos" : [1396, 4, 57],
"orientations" : [0, 180],
"dimensionId" : 4
},
{
"typeId" : "1002",
"server" : "lobby",
"pos" : [1403, 4, 57],
"orientations" : [0, 180],
"dimensionId" : 4
},
{
"typeId" : "1003",
"server" : "lobby",
"pos" : [1410, 4, 57],
"orientations" : [0, 180],
"dimensionId" : 4
},
{
"typeId" : "1004",
"server" : "gameA",
"pos" : [5, 4, 5],
"orientations" : [0, 180],
"dimensionId" : 0
},
{
"typeId" : "1005",
"server" : "gameB",
"pos" : [5, 4, 5],
"orientations" : [0, 180],
"dimensionId" : 0
},
{
"typeId" : "1006",
"server" : "gameC",
"pos" : [5, 4, 5],
"orientations" : [0, 180],
"dimensionId" : 0
}
]
}
```
### 功能验证
用MCStudio进入游戏可以看到玩家前方有三个NPC
![img](./images/wps11.jpg)
### NPC插件总结
- 创建NPC前用CheckChunkState函数检查chunk状态。
- 推荐用定时器创建NPC。
## 匹配
### 匹配的设计
点击NPC-C后把多个玩家匹配分配到GameC。匹配是把多个玩家分配到另外一个单独服务器的过程它是全服单点逻辑建议在service实现匹配功能。
通常匹配功能设计思路如下:
* lobby向service请求匹配。
* service包含一个待匹配玩家队列。玩家中途退出时需要将该玩家从队列中剔除。
* service每帧遍历所有待匹配玩家根据一定算法将多个玩家分配到指定game服务器。
* service告知game玩家即将进入并告知玩家信息。
* service告知所有玩家切服到指定game。
* 玩家进入game完成匹配过程。
### 简易网络服模板匹配功能开发
匹配过程如下所示:
![img](./images/wps12.jpg)
* lobby服务端开发
服务端监听EntityBeKnockEvent事件处理点击NPC行为根据NPC的种类处理不同的请求。点击NCP-A和NPC-B需要向master查询GameA和GameB的在线人数点击NPC-C需要处理匹配逻辑。核心代码如下
```python
class AwesomeServer(ServerSystem):
...
def OnNpcTouched(self, npc_entity_id, player_entity_id, gameType):
'''
点击npc回调函数。
'''
uid = self.playerid2uid[player_entity_id]
if gameType == 'gameA':
logger.info("%s touch NPC gameA",player_entity_id)
#请求gameA玩家人数
request_data = {'game': 'gameA', 'player_id': player_entity_id,'uid': uid,'client_id':netgameApi.GetServerId()}
self.NotifyToMaster(modConfig.GetPlayerNumOfGameEvent,request_data)
elif gameType == 'gameB':
logger.info("%s touch NPC gameB",player_entity_id)
#请求gameB玩家人数
request_data = {'game': 'gameB', 'player_id': player_entity_id, 'uid': uid,
'client_id': netgameApi.GetServerId()}
self.NotifyToMaster(modConfig.GetPlayerNumOfGameEvent, request_data)
elif gameType == 'gameC':
logger.info("%s touch NPC gameC",player_entity_id)
# 请求gameC匹配队列人数
request_data = {'uid': uid, 'player_id': player_entity_id, 'game': 'gameC'}
self.RequestToService(modConfig.awesome_match, modConfig.RequestMatchNum, request_data)
def OnSureGame(self,args):
'''
切服逻辑如果是gameA和gameB则直接传去对应服如果是gameC则加入匹配队列
'''
logger.info("OnSureGame {}".format(args))
if args['game'] == "gameA":
netgameApi.TransferToOtherServer(args['playerId'], "gameA")
elif args['game'] == "gameB":
netgameApi.TransferToOtherServer(args['playerId'], "gameB")
elif args['game'] == "gameC":
playerId = args['playerId']
uid = self.mPlayerid2uid[playerId]
levelcomp = self.CreateComponent(playerId, modConfig.Minecraft, "lv")
playerLevel = levelcomp.GetPlayerLevel()
if playerLevel >= 0:#大于0级才能匹配
request_data = {'uid': uid, 'player_id': playerId,'game':args["game"]}
self.RequestToService(
modConfig.awesome_match,
modConfig.RequestMatch,
request_data
)
tipData = {'tipType' : TipType.matching} #1匹配中
self.NotifyToClient(playerId, modConfig.MatchResultTip, tipData)
else:
tipData = {'tipType': TipType.levelNotEnough} #0等级不够
self.NotifyToClient(playerId, modConfig.MatchResultTip, tipData)
def OnMatchResultEvent(self, args):
'''
处理匹配结果。切到指定服务器。
'''
logger.info("OnMatchResultEvent {}".format(args))
playerId = args['player_id']
desc_game = args['desc_game']
if args['game'] == 'gameC':
#如果是gameC则延时1S传送
tipData = {'tipType': TipType.toTransfer} # 2 即将传送
self.NotifyToClient(playerId, modConfig.MatchResultTip, tipData)
self.mTransferPlayerQueue.append(playerId)
CoroutineMgr.StartCoroutine(self.Transfer2Server(playerId, desc_game))
def Transfer2Server(self,playerId,descGame):
'''
把玩家传送至对应的服
'''
yield -30
#判断玩家是否在待传送队列里,若玩家中途下线,则不作处理
if playerId in self.mTransferPlayerQueue:
netgameApi.TransferToOtherServerById(playerId, descGame)
self.mTransferPlayerQueue.remove(playerId)
```
- service开发
service监听UpdateServerStatusEvent事件可以获取所有game的状态这些可用game构成了可用资源池。当有玩家请求匹配时则从可用资源池中分配资源也就是匹配算法然后告知玩家。核心代码如下
```python
class AwesomeService(ServiceSystem):
def __init__(self, namespace, systemName):
ServiceSystem.__init__(self, namespace, systemName)
self.mFrameCnt = 0
self.mPlayerServer = {}
self.mGameCMatchingPlayer = []#gameC的匹配玩家
self.mActiveGameServerIds = [] #可用game列表
self.mGameStatus = {}#serverid => server status.server status:
#注册service接口
self.RegisterRpcMethod(modConfig.awesome_match, modConfig.RequestMatch, self.OnRequestMatch)
self.RegisterRpcMethod(modConfig.awesome_match, modConfig.RequestMatchCancel, self.OnRequestMatchCancel)
self.RegisterRpcMethod(modConfig.awesome_match, modConfig.RequestMatchNum, self.OnRequestMatchNum)
def OnRequestMatchCancel(self,server_id, callback_id,args):
logger.info("OnRequestMatchCancel {}".format(args))
player_id = args["player_id"]
if player_id in self.mGameCMatchingPlayer:
self.mGameCMatchingPlayer.remove(player_id)
def OnRequestMatchNum(self,server_id, callback_id, args):
'''
返回匹配队列人数
:return:
'''
logger.info("OnRequestMatchNum {}".format(args))
result_data = {
'uid':args["uid"],'player_id':args["player_id"],
'playernum':len(self.mGameCMatchingPlayer),
"game": args["game"]
}
self.NotifyToServerNode(server_id, modConfig.MatchNumEvent, result_data)
def OnRequestMatch(self, server_id, callback_id, args):
'''
请求匹配进入gameC的游戏
'''
logger.info("OnRequestMatch {}".format(args))
player_id = args['player_id']
self.mPlayerServer[player_id] = server_id
#如果已经在匹配队列,则不加入匹配队列
if player_id in self.mGameCMatchingPlayer:
return
else:
logger.info("%s matching",player_id)
self.mGameCMatchingPlayer.append(player_id)
def GameCMatch(self):
'''
检查匹配队列,匹配成功,清空匹配队列
:return:
'''
if not self.mGameCMatchingPlayer:
return
desc_game = -1
if len(self.mGameCMatchingPlayer) >=2:
desc_game = self.MatchAlgorithm()
if desc_game == -1:
return
for i in range(len(self.mGameCMatchingPlayer)):
playerId = self.mGameCMatchingPlayer[i]
self.NotifyToServerNode(self.mPlayerServer[playerId], modConfig.MatchResultEvent, {'player_id': playerId,'desc_game':desc_game,'game':'gameC'})
self.mGameCMatchingPlayer = []#清空匹配队列
def Update(self):
self.mFrameCnt += 1
if self.mFrameCnt % 10 == 0:#10帧匹配一次
self.GameCMatch()
def MatchAlgorithm(self):
'''
匹配算法
'''
serverid = -1
serverlistConf = serviceConf.netgameConf['serverlist']
for serverConf in serverlistConf:
if serverConf['type'] == "gameC":
serverid = serverConf['serverid']
break
return serverid
def OnUpdateServerStatusEvent(self, args):
'''
记录服务器状态
'''
logger.info("OnUpdateServerStatusEvent {}".format(args))
self.mGameStatus = {}
self.mActiveGameServerIds = []
for server_id, status in args.iteritems():
id = int(server_id)
int_status = int(status)
self.mGameStatus[id] = int_status
if int_status == EServerStatus.OK:
self.mActiveGameServerIds.append(id)
```
- Master开发
Master查询对应游戏玩家人数用。核心代码如下
```python
class AwesomeMaster(MasterSystem):
... ...
def GetPlayerNumOfGame(self,args):
serverlistConf = masterConf.netgameConf['serverlist']
print "OnGetPlayerNumOfGameResponse",args
checkServeridList = []
for serverConf in serverlistConf:
if serverConf['type'] == args["game"]:
serverid = serverConf['serverid']
checkServeridList.append(serverid)
playernum = 0
for serverid in checkServeridList:
playernum += serverManager.GetOnlineNumByServerId(serverid)
request_data = {
'game': args["game"],
'playernum': playernum,
'player_id':args["player_id"]
}
self.NotifyToServerNode(
args["client_id"],
modConfig.GetPlayerNumOfGameRequestEvent,
request_data)
```
用MCStudio进入游戏点击不同的NPC发现切服到对应game。
备注新建一个mod.json于ServiceMod/developer_mods/AwsomeService下面mod.json的内容是
{
"netgame_mod_name": null,
"netgame_mod_version": "1.0.0",
"min_app_version": null,
"author": null,
"module_names": ["awsome_match"],
"support_server_type": null
"unsupport_app_version": null,
"CustomPath": null
}
### 总结
- service监听UpdateServerStatusEvent事件记录可用服务器列表。
- 匹配过程主要包括:请求匹配、匹配算法、玩家迁移。

View File

@@ -0,0 +1,151 @@
---
front: https://mc.res.netease.com/pc/zt/20201109161633/mc-dev/assets/img/wps13.a2434b82.jpg
hard: 进阶
time: 20分钟
---
# 游戏外功能
## 运营指令
运营指令接收外部http请求处理游戏相关逻辑比如给某个玩家发物品公告等。
master是运营执行入口开发者可以根据需要把请求分发到lobby/game/service。下面给AwesomeGame新增一个运营指令功能是打印指定玩家信息。
### 获取玩家数据运营指令
由于lobby是异步定时存档的因此mysql数据可能不是最新的。这里实现方案是
* 玩家在lobby从对应lobby的内存中拉取玩家数据。
* 玩家不在lobby选择任意一个可用lobby从db中读取玩家数据。
处理过程如下所示:
![img](./images/wps13.jpg)
master主要接受请求然后转发核心代码如下所示
```python
class AwesomeMaster(MasterSystem):
def __init__(self, namespace, systemName):
MasterSystem.__init__(self, namespace, systemName)
# 注册gm指令
masterHttp.RegisterMasterHttp('/get-user-info', self, self.OnGetUserInfo) self.DefineEvent('GetUserInfoRequestEvent')
self.ListenForEvent(
'Minecraft', 'AwesomeLobby',
'GetUserInfoResponseEvent',
self, self.OnGetUserInfoResponse
)
def OnGetUserInfo(self, client_id, request_body):
'''
获取gm指令
'''
import ujson as json
request = json.loads(request_body)
uid = request['uid']
redis_key_player = "online_user_%d" % uid
#获取玩家在线状态
redisPool.AsyncHgetall(
redis_key_player,
lambda record:self._GetUserInfoCb(client_id, uid, record)
)
def _GetUserInfoCb(self, client_id, uid, record):
'''
回调函数。获取目标lobby向lobby请求在线人数。
'''
serverid = None
serverlistConf = masterConf.netgameConf['serverlist']
if record:
#若玩家在game中则随机从一个lobby获取在线人数。
serverid = record.get('serverid', None)
tmpServerConf = masterConf.serverListMap.get(serverid, None)
if not tmpServerConf or tmpServerConf['type'] != 'lobby':
serverid = None
if not serverid:
for serverConf in serverlistConf:
#服务器可用且是lobby
if serverConf['type'] == 'lobby' \
and serverManager.IsValidServer(serverConf['serverid']):
serverid = serverConf['serverid']
break
if not serverid:
response = self.makeFailResponse(master_http.HTTP_CODE_FAIL, 'no valid lobby.')
masterHttp.SendHttpResponse(client_id, response)
return
request_data = {'uid' : uid, 'client_id' : client_id}
self.NotifyToServerNode(serverid, 'GetUserInfoRequestEvent', request_data)
def OnGetUserInfoResponse(self, args):
'''
接受玩家数据返回http请求。
'''
client_id = args['client_id']
entity = args['user_info']
response = self.makeResponse(master_http.HTTP_CODE_SUCCESS, '', entity)
masterHttp.SendHttpResponse(client_id, response)
```
Lobby主要获取玩家数据核心代码如下所示
```python
class AwesomeServer(ServerSystem):
def __init__(self, namespace, systemName):
ServerSystem.__init__(self, namespace, systemName)
self.ListenForEvent(
modConfig.Minecraft, modConfig.MasterSystemName, modConfig.GetUserInfoRequestEvent,
self, self.OnGetUserInfoRequest)
def OnGetUserInfoRequest(self, args):
'''
获取玩家数据。
'''
uid = args['uid']
client_id = args['client_id']
player_data = self.mPlayerMap.get(uid, None)
if not player_data:
if self.mDBType == DbType.Mongo:
self.mMongoMgr.QueryPlayerData(
uid, uid,
lambda data: self._OnGetUserInfoRequestCb(client_id, data))
elif self.mDBType == DbType.Mysql:
self.mMysqlMgr.QueryPlayerData(
uid, uid,
lambda data: self._OnGetUserInfoRequestCb(client_id, data))
else:
self._GetUserInfoResponse(client_id, player_data.toSaveDict())
def _OnGetUserInfoRequestCb(self, client_id, record):
'''
回调函数处理db操作结果把玩家数据告知master。
'''
if record:
player_data = playerData.PlayerData()
player_data.initPlayer(-1, record)
self._GetUserInfoResponse(client_id, player_data.toSaveDict())
else:
self._GetUserInfoResponse(client_id, {})
def _GetUserInfoResponse(self, client_id, player_info):
'''
玩家数据告知master。
'''
response_data = {'client_id' : client_id, 'user_info' : player_info}
self.NotifyToMaster('GetUserInfoResponseEvent', response_data)
```
### 验证
登录到开发机然后给master发送curl请求即可获取结果如下图示
<img src="./images/wps14.jpg" alt="img" style="zoom:150%;" />
## 官方运营指令
查看“服务器MOD SDK”中【运营指令】部分里面介绍了常用的指令比如禁言、踢人等。
## 总结
- 运营指令的实现通常分为两个步骤:
- master接受响应指令将指令请求转发到其他服务器
- lobby/game/serivce实现指令功能。
- 官方实现了常见的运营指令具体可以查看“服务器MOD SDK”中【运营指令】部分。

View File

@@ -0,0 +1,118 @@
---
front:
hard: 进阶
time: 15分钟
---
# 优化和维护
优化部分介绍如何查找脚本层内存泄漏。维护部分介绍如何追查线上问题。
## 内存检查
下面结合简易网络服模板介绍如何检查内存泄漏。
### 制造内存泄漏问题
lobby玩家退出时不清理内存数据。按照下面方式修改代码
```python
def OnDelServerPlayer(self, args):
'''
清除玩家内存数据。
'''
player_id = args.get('id','-1')
uid = self.mPlayerid2uid.get(player_id, None)
if not uid:
return
del self.mPlayerid2uid[player_id]
# if uid in self.mPlayerMap:
# del self.mPlayerMap[uid]
# if uid in self.mUid2dimension:
# del self.mUid2dimension[uid]
```
### 检查内存泄漏
然后给master发送check-memory-run指令生成一个内存快照结果如下图示
<img src="./images/wps15.jpg" alt="img" style="zoom:150%;" />
查看lobby日志
![img](./images/wps16.jpg)
用MCStudio进入游戏然后退出再给master发送check-memory-run指令再生成一次内存快照结果如下图示
<img src="./images/wps17.jpg" alt="img" style="zoom:150%;" />
再次查看lobby日志
![img](./images/wps18.jpg)
分析:
\[Top 10 differences\]记录占用内存最多的10行代码。简单分析下面日志
<img src="./images/wps19.jpg" alt="img" style="zoom:150%;" />
obj_report.py文件43行占用内存最多占用48k内存实例个数为1两次内存快照间增加了一个实例平均每个实例占用48k。、
\[DIFF_MORE\]记录了内存变化最多的类型。简单分析下面日志:
<img src="./images/wps20.jpg" alt="img" style="zoom:150%;" />
两次内存快照间新增了一个PlayerData类型实例。
接着分析AwesomeGame内存泄漏问题。第一次内存快照时没有玩家登录接着玩家登录再登出然后再生成了一次内存快照。第二次内存快照时没有玩家在游戏中因此服务端内存是不会有玩家数据也即不会有PlayerData类型实例的。但是\[DIFF_MORE\]中显示内存中还是多了一个PlayerData类型实例这说明存在内存泄漏。
### 总结
* check-memory-run指令可以检测两次内存快照间内存变化。
* 内存泄漏检查要点:在游戏平稳时生成内存快照(比如没有玩家登录登出),然后分析内存变化。
## Hunter
开发者先阅读“服务器MOD SDK”中“Hunter调试命令”部分。下面结合“AwesomeGame”网络游戏介绍如何在线调试mod。
### 获取lobby服在线玩家信息
用MCStudio进入游戏然后给master发送/hunter-debug指令
<img src="./images/wps21.jpg" alt="img" style="zoom:150%;" />
实质是在lobby中执行下面代码
```python
import server.extraServerApi as serverApi
#获取AwesomeServer实例
mainSys=serverApi.GetSystem("Minecraft", "AwesomeLobby")
#打印玩家信息
print mainSys.mPlayerid2uid
```
执行结果需要查看lobby日志
<img src="./images/wps22.jpg" alt="img" style="zoom:150%;" />
日志说明lobby服只有一个玩家日志打印了该玩家的player id和uid。
### 清空lobby服玩家信息
然后给master发送/hunter-debug指令
<img src="./images/wps23.jpg" alt="img" style="zoom:150%;" />
实质是在lobby中执行下面代码
```python
import server.extraServerApi as serverApi
#获取AwesomeServer实例
mainSys=serverApi.GetSystem("Minecraft", "AwesomeLobby")
#清除玩家信息
mainSys.mplayerid2uid = {}
mainSys.mPlayerMap = {}
mainSys.mUid2dimension = {}
print clear ok
```
执行结果需要查看lobby日志
<img src="./images/wps24.jpg" alt="img" style="zoom:150%;" />
日志打印”clear ok”说明清除成功。
### 总结
hunter-debug指令支持在线执行一段python脚本使用该指令可以方便查看变量信息修改变量内容。

View File

@@ -0,0 +1,36 @@
---
front:
hard: 进阶
time: 5分钟
---
# 控制服务器Mod加载顺序
## 为每个Mod命名
* 在mod的根目录自主添加一个netease_require.json的文件举例来说对于【neteaseMonitor】这个Mod就应该在【developer_mods/neteaseMonitor/】目录下创建netease_require.json文件
* 文件举例
```json
{
"modName": "neteaseMonitor",
"modRequire": [
]
}
```
* 其中modName的值【neteaseMonitor】就是当前mod的名字而modRequire的值为空list说明当前Mod不需要其他任何前置Mod
* 假如json文件不存在当前Mod的名字默认等于插件根目录下developer_mods文件夹下的服务端mod文件夹的名字
![image.png](./images/hint001.png)
## 设置Mod的前置Mod
* 同样是在netease_require.json文件中通过修改modRequire属性来设置当前Mod的前置Mod服务器启动并加载某个Mod时会尽量保证这个Mod的前置Mod已经加载完成。假如设置中出现循环require那么会打印ERROR日志但是依旧会加载mod所有出现循环require的mod都会在最后加载
* 举例【neteaseMonitorSample】这个Mod依赖【neteaseMonitor】需要在【neteaseMonitor】加载完毕之后才加载【neteaseMonitorSample】那么【neteaseMonitorSample】的netease_require.json文件如下
```json
{
"modName":"neteaseMonitorSample",
"modRequire":[
"neteaseMonitor"
]
}
```
* modRequire中的neteaseMonitor说明当前Mod需要在【neteaseMonitor】之后加载
* 假如json文件不存在modRequire默认为空list也就是没有加载顺序要求。

View File

@@ -0,0 +1,45 @@
---
front:
hard: 进阶
time: 5分钟
---
# 性能数据
## 确定一台机器部署服务进程数量
mc的自由度非常大每种网络游戏的实现千差万别。不同的网络游戏使用不同的mod不同mod的性能消耗也不一样的。有的网络游戏玩法复杂一台物理机可能部署20个game有的网络游戏玩法简单一台物理机可能部署30个game。
我们建议网络游戏服主积极联系我们,我们会指导服主按照最佳方式部署。
## 评估游戏服的最大在线人数
- 服务器承载能力与在线玩家数平方成反比。在线玩家越多服务器消耗cpu越多。
- 地图大小主要影响服务器占用内存对服务器承载能力影响较弱。目前单服进程最大内存是固定8G。
- 服务器不会一次加载所有地图,它会按需加载区块,会将没使用到的区块存储到磁盘。
- 服务器性能消耗由mod和引擎组成mod性能消耗也是不能忽略的。服主优化mod就能提升网络游戏性能服主应尽力优化mod。
- 最大在线人数评估方法网络服正式上线前建议先进行限量删档测试在删档测试期间观察服务器性能得到lobby/game最大在线人数。具体过程初期将lobby最大在线人数设置50~100game最大在线人数设置10~30 接着观察服务器性能,然后不断滚动更新调整最大在线。
## 第五人格的性能数据
### 部署方式
第五人格使用的是低频主机40核128G内存主频2.3GHZ一共使用18台低频物理机。具体部署方式如下
- 1个master、2个service、10个game部署在一台物理机。
- 10个proxy部署在一台物理机。
- 20个lobby部署在一台物理机。
- 300个game部署到20台物理机每个物理机部署15个game。
- 每个服务器进程的内存限制为最大8G。
### 承载人数
服务器可以承载这么多人,要求满足以下条件:
- 服务器进程的主线程cpu大部分时间不超过80%。
- 进程占用内存不允许超过8G。
- 客户端不存在明显卡顿或延迟。
下面介绍第五人格各个服的承载能力:
- 一个proxy可以承载600人10个proxy可以承载6000人。
- 一个lobby可以承载200人20个lobby可以承载4000人。
- 一个game可以承载15人310个game可以承载4650人。第五人格一个game承载人数较低主要是玩法限制导致游戏一个game只能开3局一局只有5人。
结论第五人格使用18台物理机条件下部署proxy可以承载6000人lobby+game可以承载8650人取两者最小值第五人格可以承载6000人。