first commit

This commit is contained in:
boybook
2025-03-17 13:24:39 +08:00
commit 9a0334ee84
6410 changed files with 221907 additions and 0 deletions

View File

@@ -0,0 +1,246 @@
---
front: https://mc.163.com/dev/mcmanual/mc-dev/assets/img/1.385e53eb.png
hard: 进阶
time: 30分钟
---
# 联机大厅商品2.0文档
**《我的世界》手机版联机大厅** 现已开放 **商业化****数据储存** 接口,打破开发者的创作边界,为联机游戏开发提供体验式购买、玩家数据持久化等全新条件。
## 数据储存说明
对比资源中心组件,联机大厅的数据储存接口支持上报游戏数据至云端数据库,支持跨存档及跨房间存取数据信息。每创建一个联机大厅作品,皆可在 **自测阶段****线上阶段** 对全局与单个玩家数据进行单独托管。
| 接口类型 | 服务范围 | 有效范围 | 数量 |
| ---------------- | -------------------------- | -------------------------- | ---- |
| 自定义数据储存 | 资源中心组件\|联机大厅玩法 | 单个游戏存档 | 1 |
| 联机大厅数据储存 | 联机大厅玩法 | 同玩法内的全部联机大厅房间 | ∞+ |
使用联机大厅数据储存功能,开发者便可以根据规范的 **接口调用****数据结构格式** ,将游戏内的 **成长数值****货币金额** 、甚至 **背包信息** 等内容由玩家从一个房间带到另一个房间,与不同房间的玩家分享无限可能的乐趣。
| 常见的数据储存类型 | 应用方向 |
| ------------------ | ------------------------------- |
| 成长数值 | 经验\|等级\|签到天数\|RPG属性等 |
| 回合结算 | 小游戏\|回合制等 |
| 货币金额 | 装备交易\|购买商品等 |
| 背包信息 | 玩家背包\|额外行囊等 |
## 商品化说明
商品化允许开发者创建体验式购买,即玩家凭意愿和对玩法的认可程度,在联机大厅房间里购买增值服务与物品,让每个开发者的热爱有所回响。
| 常见的商品类型 | 应用内容 |
| -------------- | ------------------------- |
| 特殊外观 | 人物、武器、装备、道具 |
| 增值服务 | 喊话、称号、VIP权限、礼包 |
| 游戏通行证 | 唯一次购买的限量道具 |
## MODSDK的API与事件
### API
- <a href="../../mcdocs/1-ModAPI/接口/联机大厅.html#querylobbyuseritem">QueryLobbyUserItem</a>
查询还没发货的订单
- <a href="../../mcdocs/1-ModAPI/接口/联机大厅.html#lobbysetstorageanduseritem">LobbySetStorageAndUserItem</a>
设置订单已发货或者存数据
- <a href="../../mcdocs/1-ModAPI/接口/联机大厅.html#lobbygetstorage">LobbyGetStorage</a>
获取存储的数据
- <a href="../../mcdocs/1-ModAPI/接口/联机大厅.html#lobbygetstoragebysort">LobbyGetStorageBySort</a>
排序获取存储的数据在开发者平台上配置的可排序的key才可以查询
### 事件
- <a href="../../mcdocs/1-ModAPI/事件/联机大厅.html#lobbygoodbuysucserverevent">lobbyGoodBuySucServerEvent</a>
当玩家购买完商品或者玩家登录进联机大厅mod时触发
## 示例mod
[lobbyGoodsMod2.0](../20-玩法开发/13-模组SDK编程/60-Demo示例.html)展示了一个基本玩法:
- 玩法内设计了“金币”的虚拟货币使用key为money存储并且设置为可排序的key。
- 玩法上架了一个商品玩家购买后可获得100金币商品的实现指令为add_money_100
- 玩家可以通过聊天栏输入buysword获得初始装备“钻石剑”的7天有效期有效期内玩家每次进入新房间都会获得一把钻石剑。重复购买会叠加有效期。
- 玩家可以在战斗中每击杀一个僵尸获得1个金币获得的金币在战斗结束时统一结算。聊天栏输入endbattle触发战斗结算。
- demo的主要流程如下详情可阅读demo的代码注释
![](./picture/lobbyGoodsMod2.0/1.png)
## Q&A
### 为什么会有数据冲突的情况?
根本原因是可能会有多个联机大厅房间同时修改同一个玩家的数据同一个房间的get与set操作之间可能被插入了其他房间的set操作。
假设我们以这样的流程设置数据(注意这个流程是错的!):
1. 每个房间记录这一局战斗玩家获得的积分
2. 战斗结束时统一结算先获取玩家最新积分然后加上本局积分set回去
就有可能出现这样的情况:
1. 某个玩家在云端的积分是100
2. 这个玩家进入了房间A并且在这一局游戏内的积分是20
![qaa0](./images/qaa0.png)
2. 然后这个玩家先阵亡了于是退出了房间A这时房间A的战斗还没结束也还没有进行结算进了另一个房间B然后获得了10个积分
![qaa1](./images/qaa1.png)
3. 然后房间A和房间B同时结算了两个房间都去获取云端积分获取到的都是100
![qaa2](./images/qaa2.png)
4. 于是房间A会设置积分为120房间B设置积分为110。无论设置的先后顺序如何结果都是错的正确的积分应该是130
![qaa3](./images/qaa3.png)
### 那应该怎么保证数据的正确性?
为了防止上面所说的这种情况每次设置数据时都会附带一个“版本号”每次设置数据的版本都是上一次获取到的版本加一这样云端就可以根据版本号判断数据时候被别的容器修改过也就是冲突。这个版本号控制已经由引擎内部控制好不需要开发者操心您只需要按api文档里的规范处理好冲突即可。
我们推荐的数据控制流程如下(以积分为例):
1. 每个房间维护两个容器一个容器mPoint存放云端积分另一个容器mDeltaPoint存放这个房间产生的积分的差异例如+10分或者-10分
2. 玩家登录时get数据存放到mPoint。如果获取不到则把默认积分例如1000放入mPoint
3. 结算调用LobbySetStorageAndUserItem
4. 上传的积分为mPoint的积分加上mDeltaPoint的积分
```python
def getter():
return [
{
'key': 'point',
'value': self.mPoint[playerId] + self.mDeltaPoint[playerId]
}
]
```
5. 如果返回数据冲突则用返回的最新积分更新mPoint
6. 然后引擎会自动重试第4步
下面解释一下为何这个流程可以避免数据覆盖:
1. 某个玩家在云端的积分是100
2. 这个玩家进入了房间A房间获取到该玩家的云端积分是100同时也会获取到对应的“版本号”并且在这一局游戏内的积分是20
![qaa4](./images/qaa4.png)
3. 然后这个玩家先阵亡了于是退出了房间A这时房间A的战斗还没结束也还没有进行结算进了另一个房间B。
房间获取到该玩家的云端积分是100同时也会获取到对应的“版本号”然后玩家在这一局游戏内的积分是10
![qaa5](./images/qaa5.png)
4. 房间A和房间B同时结算房间A想要设置积分为100+20房间B想要设置积分为100+10。
假设房间A的请求先到达这时云端积分成功设置为120同时“版本号”+1
![qaa6](./images/qaa6.png)
5. 房间B的请求后到达这时候云端发现版本号不一致于是将最新的数据120返回给房间B。房间B收到后更新本地的mPoint
![qaa7](./images/qaa7.png)
6. 房间B的引擎重新调用getter并提交数据此次设置的积分为120+10设置成功最终数据正确。
![qaa8](./images/qaa8.png)
### 为什么demo的lobbyGoodsServerSystem.py_EndBattle函数中要求将callbackgetter以及接口的调用封装到一个子函数内
假设我们不封装,写成以下的样子:
```python
def _EndBattle(self):
for playerId in self.mBattleMoney.keys():
def getter():
return [
{
'key': 'money',
'value': self.mMoney[playerId] + self.mBattleMoney[playerId]
}
]
def callback(data):
print playerId
...
httpComp.LobbySetStorageAndUserItem(callback, self.mUid[playerId], None, getter)
```
因为LobbySetStorageAndUserItem的调用是异步的getter与callback可能不会马上被调用。
当他们被调用时这个for循环可能已经结束了而此时playerId的值是mBattleMoney中的最后一个
如果多一层函数调用则不会出现这种情况playerId的遍历不会影响到子函数的参数。
### 我的玩法不是按局计算数据会实时更新我应该如何减小set的频率
1. 每隔一定时间例如5分钟更新数据到云端
2. 玩家退出时更新数据到云端
set接口频率上限为50次/秒5分钟的间隔可以保证5\*60\*50=15000名以内的玩家同时在线以此类推
如果需要承载玩家数量的量级更高,则要舍弃定时上传,只在玩家退出时上传
### 我的玩法不是按局计算,数据会实时更新,我应该如何维护数据的正确性?
对于定时上传,可以参考以下写法:
```python
def callback(data):
if data:
code = data['code']
if code==0
# 设置成功
newData = {i["key"]: i["value"] for i in data["entity"]["data"]}
newPoint = newData.get('point', DEFAULT_POINT)
# 给mDeltaPoint减去上传成功的差量不能直接清零因为在调用返回之前mDeltaPoint可能会被更新
self.mDeltaPoint[playerId] -= newPoint - self.mPoint[playerId]
# 使用返回的新数据更新mPoint
self.mPoint[playerId] = newPoint
else if code==2:
# 数据冲突直接用返回的数据更新mPoint
newData = {i["key"]: i["value"] for i in data["entity"]["data"]}
self.mPoint[playerId] = newData.get('point', DEFAULT_POINT)
def getter():
return [
{
'key': 'point',
'value': self.mPoint[playerId] + self.mDeltaPoint[playerId]
}
]
```