网络游戏数据笔记 游戏初始化步骤包括连接资源服务器获取资源包清单文件和本地资源包清单对比得到需要下载、需要删除的资源包如果要严谨本地再对着资源包清单校验一遍资源包摘要对着配表校验一遍资源包里的资源路径检查本地令牌有则自动登录没有则显示登录界面固有UI和AB包UI划分游戏打开后在准备好资源前AB包的UI是不保证存在的。此时在下载资源、连接服务器时要用到的UI吐司面板、连网忙碌标志、单按钮提示。放在Resources。UI管理器加载AB包UI配表、加载任何AB包UI不能在资源准备好之前执行。那么我们要么UI管理器可以在资源准备好之前初始化但读取AB包UI在准备好资源后执行为固有UI单独准备一套加载、隐藏方法以及缓存字典。校验资源路径和加载配表的顺序校验资源路径是依照配表需要先加载配表。日志、内部报错、外部提示程序内的报错应该留下日志而给玩家看的提示应该简单易懂不涉及程序内部逻辑。日志和报错是分开还是一个东西从本地存储数据到网络存储为了丝滑过渡弄了一个IGameLoader接口里面有读取、保存方法。然后发现当前的本地GameLoader还保有玩家的全部状态数据。玩家数据管理器除了全量读取、保存还有诸如更新金币这种部分更新操作。而且本地存储和网络存储还有重大区别本地存储通常玩家点击保存全量存储网络游戏玩家随时退出玩家数据要实时同步。现在要写网络存储中玩家进入游戏时加载数据。然后发现加载新游戏本地C#json和网络protobuf使用的参数类型不一样本地使用一个配置文件的路径网络使用一个消息类。然后开始把背包数据类从手写替换成protobuf生成。背包数据类业务层到处都在用这是一个大换血不可能抽象基类丝滑过渡。是的我们不可能在protobuf和其他序列化方案之间弄一个抽象层了。前期访问一些int float简单类型还可以一旦开始操作背包这种数据类要用protobuf只能all in让protobuf类出现在项目各处了。那么这个抽象层也没有意义了。玩家初始数据是放在客户端直接加载还是从服务器传过来如果是新玩家加载初始数据是放在客户端配置文件直接加载从服务器配置文件传过来从服务器数据库传过来初始数据包括玩家在地图的位置这种场景内的、不规则的数据。把这种位置作为数据库默认值比较奇怪。那么加载初始场景配置不管是客户端直接加载还是服务器传过来都需要先能从场景生成配置文件。那么我们现在写一个由摆好的初始场景生成protobuf消息的功能服务器传过来的是完整玩家数据包括场景内数据和非场景数据场景内数据由客户端摆好的场景生成那么非场景数据初始hp、金币也由客户端生成吧。那么就在编辑器窗口填。然后我们本地存储和网络存储用的数据类都不一样本地是手写的C#类网络是用protobuf生成类。现在游戏里有设置金币数的是直接用手写C#类需要抽象成接口的方法。那么所有数据都要封装属性是的。要么业务到处直接读写数据类要么用属性抽象。然后写了一个public class PlayerDataManager:MySingletonPlayerDataManager { IGameLoader _gameLoader; public IGameLoader GameLoader _gameLoader; public PlayerDataManager() { _gameLoadernew MyGameLoader_Local(); } }然后发现全量加载数据时对于网络存储需要输入消息类。而本地存储不是。加载玩家人物由玩家数据管理器还是游戏管理器做玩家人物由游戏管理器引用让其他类加载注入给游戏管理器还是玩家数据管理器把人物预制体加载交给游戏管理器克隆还是玩家数据管理器只把资源路径、位置给游戏管理器让它来做从数据类到业务管理器要分几层一个数据类里面几十个上百个字段管理器如果不用属性封装直接借助数据类访问字段如果数据类实现方法变了比如从手写C#到protobuf业务所有的访问全都要变。而如果用属性封装n个字段一共要写2n个get set属性。多一种数据类实现就再写2n个属性。只是写好后切换很方便。然后后面所有背包数据类要用protobuf类替换访问背包两种方案的数据类型都不一样已经不可能封装抽象层了。服务器返回玩家数据和加载游戏场景的关系点击进入游戏向服务器发送请求玩家数据收到玩家数据时进入游戏场景。但是不能任意情况下收到服务器的玩家数据都进入场景。如果按下进入游戏就加载场景等服务器返回后再加载数据那如果网络出错玩家会看到空场景如果等服务器返回数据再加载场景如果当前不是入口场景如果在其他时机服务器返回玩家数据导致切换场景那么就在发出请求玩家数据前监听收到玩家数据消息加载游戏场景完成后解除监听。玩家数据增量同步玩家的状态数据量较大为了少发数据我们只发变化的字段。为此在protobuf把值类型再封装成message通过给类字段置空表示这个字段没有变化。我们发现protobuf提供了包装器\protobuf-csharp-3.21.1\protobuf-3.21.1\src\google\protobuf\wrappers.proto。把里面的package改成自己的命名空间即可使用XXXValue。然后我们发现玩家数据消息类不能客户端持有一份有字段更新就直接发送了而需要有字段更新时临时组装一个只有更新的字段不为null的消息发送。进入游戏时服务器返回一个全量数据消息客户端维护着然后时不时发送只有部分要更新字段的玩家数据消息。然后发现全量存储玩家数据时需要把那些包装类全部先new再赋值。玩家数据在服务器的同步一个玩家的服务器会话需要和数据库连接去数据库根据玩家id查找修改存回数据库。对于高频的位置同步位置一更新就连接一次数据库查找数据库写入开销很大。对于一些重要数据比如交易需要立即写入。那么对于非重要数据需要有一个机制以较低的频率写入数据库。Connect后立即发消息服务器Session对象还没有构造完毕解决方法连接成功后服务器发握手消息客户端收到后发第一条请求。背包数据的同步首先定义背包一个物品和背包的protobuf消息。之前手写过背包的C#数据类。为了不写大量赋值代码打算把手写C#数据类都用protobuf数据类替换掉。背包数据类有加入物品、移出物品、搜索物品、消耗材料等方法。替换成protobuf类后它的类都写了partial可以再写扩展脚本写方法。场景数据转换成protobuf数据类protobuf数据类不使用列表使用RepeatedField而且是只读的不能让我们的存档函数返回RepeatedField而是一个个加进去。那么我们设计泛型ToData方法时把RepeatedField必须传进去。引入NoSQL数据库保存玩家背包数据我们可以弄一个物品表所有玩家的所有物品存这个表每条数据有玩家Id进入游戏时根据玩家Id把此玩家物品筛选出来。当我们想存玩家一个场景的数据有道具、怪物、NPC、任务进度SQL只能弄一个场景道具表一个怪物表一个NPC表一个任务进度表毕竟SQL记录嵌套数据的能力几乎为0.