惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Palo Alto Networks Blog
C
Cisco Blogs
The Hacker News
The Hacker News
T
Threatpost
S
Schneier on Security
K
Kaspersky official blog
Spread Privacy
Spread Privacy
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
NISL@THU
NISL@THU
量子位
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
Security Latest
Security Latest
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
博客园 - 叶小钗
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
News and Events Feed by Topic
爱范儿
爱范儿
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Cisco Talos Blog
Cisco Talos Blog
GbyAI
GbyAI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
T
Tenable Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
V
V2EX
Webroot Blog
Webroot Blog
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
M
MIT News - Artificial intelligence
Scott Helme
Scott Helme
Simon Willison's Weblog
Simon Willison's Weblog
L
LangChain Blog
W
WeLiveSecurity
Cloudbric
Cloudbric

风萧古道 - 勤学苦练,年复一年

游戏服务器开发经验(五)应对复杂需求 沉浸式体验东汉末年生活 - 《真三国无双 起源》玩后感 怒其不争!致2025年HLTV的Top18-NiKo Linux家用服务器维护指南 游戏服务器开发经验(四)避免写Bug的习惯、技巧和心态 游戏服务器开发经验(三)线上维护 游戏服务器开发经验(二)避免内存泄露 游戏服务器开发经验(一)道具防刷 35岁找不到工作,绝对不会是软件开发人员的结局 用爱发电项目开发两个月的心得体会 以魏延“子午谷奇谋”讨论软件需求可行性问题 MQTT协议中可变长度的具体计算方式(有计算过程解析) Java并发编程中上锁的几种方式 如何用C++分割一个字符串? CSAPP第二章-信息的表示与处理 我的自我介绍 Windows XP虚拟机中文版无需激活下载 Java TreeSet的一些用法和特性 Linux C++ Socket实战 传统软件服务器与游戏服务器架构区别 独立个人项目开发心得 - 任务切分、挑战性、实用性和半途而废 使用Python实现简单UDP Ping 使用Python开发一个简单的web服务器 Kotlin手动实现一个最简单的哈希表 Kotlin实现二叉堆、大顶堆、优先级队列 搭建Spark实战环境(3台linux虚拟机集群)(一)样板机的搭建 Springboot操作MongoDB,包括增改查及复杂操作 Unison在Linux下的安装与使用 Java实现类似WINSCP访问远程Linux服务器,执行命令、上传文件、下载文件 一个被废弃的项目——自动爬取信息然后发给我自己邮箱上 Python连接MongoDB和Oracle实战 MongoDB常用查询语句 vue和springboot项目部署到Linux服务器 Python的一些用法(可能不定时更新) java正则表达式 - 双反斜杠(\)和Pattern的matches()与find() 简述爬虫对两种网站的不同爬取方式 Vue的路由配置及手动改地址栏为啥又跳转回来?? [JavaScript]JS基础知识 [Mybatis]逆向工程中Select语句查询不出‘TEXT’字段 [编译原理]FIRST、FOLLOW和SELECT [Spring]Spring学习笔记 [算法]分布估计算法 - 一种求解多维背包问题的混合分布估计算法_王凌 [日常]我做独立博客的原因 人间值得 深入理解计算机系统 想想就开心! 最重要的事,只有一件 藏书 被讨厌的勇气 关于 简历 朋友 尊重自己:给予与接收的心灵艺术
关于游戏服务器配置表功能的探讨
JonathanLin · 2023-12-24 · via 风萧古道 - 勤学苦练,年复一年

简介

在游戏开发中,配置表是策划与程序员之间针对功能模块开发而搭建的桥梁。配置表在游戏开发中扮演非常重要的角色。策划需要向配置表填入各种数据,以完成功能数值的配置;程序员需要在项目代码中读取配置表,根据配置表和需求决定的业务逻辑来开发功能模块。

本文想探讨接触过的两个项目的配置表功能设计。在本文中,我不会泄露项目相关的具体信息,仅针对方案进行讨论。

两种配置表方案简介

此处先以表格的形式,列举两种方案之间的异同:

条目大型SLG手游小型MMO手游
策划配置方式使用Excel配置使用Excel配置
代码读取方式将Excel文件中的内容转换成Csv文件将Excel文件中的内容转换成Json文件
读取后存储位置在项目启动前,先读取Csv的内容到内存中,转成二进制文件写入Zookeeper,在项目启动时加载Zookeeper中的配置表信息到内存在项目加载时读取Json到内存中
热更新方案修改Zookeeper中的二进制文件,通过Zookeeper发布订阅机制,分布式系统中各个系统从Zookeeper中读取新的配置表信息先修改Json文件,然后读取Json文件到内存中
配置表检查在读取Csv到内存后,编写检查代码,检查内存对象之间的相关逻辑依赖

大型SLG手游

这是一个比较大型的SLG手游项目,使用了分布式的系统,服务器数量众多,因此需要一个统一的中心服务器来管理所有的配置信息。这个项目使用的是Zookeeper进行配置表的存储。

策划配置方式

策划使用Excel进行配置。每一列包含中文名、英文名、key类型(标记客户端、服务器或是客户端和服务器都用,且标记是否是key)、字段类型(数据data还是文字text)。

一张表最多只能有两个key,一个主key,一个副key。

比如,如果有一个可以领取宝箱的活动类型,活动可能有多个id,以满足不同的活动包装;每个活动都有多个宝箱。那么这张配置表的主key是活动id,副key是宝箱id。

读取配置表

首先将Excel文件中的内容转换成Csv文件,这一步会将客户端的配置列移除,仅保留服务器用到的配置列。然后使用Java代码读取Csv文件,将Csv文件中的数据构造成Java对象,然后再将Java对象序列化成二进制文件,上传到Zookeeper中的某个节点之下。

graph LR
A[Excel文件]-->|Python转换|B[Csv文件]-->|Java代码读取|C[Zookeeper节点下]-->|服务器启动读取Zookeeper|D[服务器内存对象]
  

服务器启动时会从Zookeeper的节点中加载配置表文件。

在后续的开发过程中,发生过因为策划失误,修改了Excel后忘记生成Csv,而导致Excel文件与Csv文件不一致的情况。在这个项目中,Csv并不是读取配置表过程中最后的输出产物,仅仅是一个中间产物,所以内部也讨论过是否可以直接从Excel文件中读取到内存中的可能。

热更新方案

当服务器启动读取完Zookeeper中的配置表后,订阅Zookeeper中配置表节点的变化事件。

当配置表Excel文件被修改后,依次执行读取流程到上传到Zookeeper节点,此时触发节点变化事件,Zookeeper会将变化事件发布给所有订阅此节点的服务器,服务器接收到配置表节点变化后,再次加载新的配置表文件信息。

配置表检查

当Csv读取到内存后,会对每一场表生成一个Java表管理对象,Java表管理对象与配置表一一对应。同时,这个Java表管理对象中,还可以进行多张配置表之间的逻辑校验。比如奖励表中的道具,必须在道具表中有配置,否则就会报错。

小型MMO手游

这是一个比较小的MMO手游,服务器进程数量比较少,配置表的数量也不多。因此它不需要使用类似Zookeeper那样的配置中心,只需要各个服务器进程读取相同内容的Json文件即可。

策划配置方式

策划使用Excel进行配置表的编辑。表头包含中文名,英文名,类型(数字,或者any任意结构)。 一张表必须要有数字id,且必须列在第一列中。其他的部分不做强制要求。

读取配置表

Excel文件会转换为Json文件。 在代码中,服务器会加载所有的Json文件。

程序员一般不需要针对某一张配置表单独编写解析代码。每一张表都有一个id,其他字段都默认读取为Golang的interface{}类型,每一张表都能读取成统一的格式,即:表名-id-其余各列数据。如果这张表是一张只有一个key的表,那么可以直接通过表名+id查找对应的数据。

但如果是不止一个key的表,那么可以在默认加载之后,再新开辟内存空间,使用自定义的结构进行存储。

热更新方案

策划修改配置表Excel文件后,转成Json文件,然后手动上传到服务器中。 通过RPC的方式,向网关服务器发送“重新读取配置表”消息,网关通知服务器集群中的所有服务器进程,重新读取Json文件到内存中,替换之前的配置表内存数据。

配置表检查

没有配置表提前检查,仅在使用配置的时候抛出err错误。

优缺点

类型大型SLG手游小型MMO手游
优点(1)热更新方便,一次发布到Zk上之后,所有服务器都更新
(2)有配置表检查,在加载配置表时校验配置错误信息
(1)所有表都有默认取值的方法,方便开发
(2)Excel转Json后,Json文件方便人类阅读
缺点(1)Excel生成的Csv可读性差,维护性差,不如XML,Json带有自己的结构信息
(2)开发繁琐,需要为每一张表定义类,并且预测配置表的关联,有可能会因为过度关联导致自由度下降
(3)在这个项目中,一个Excel对应一张配置表,导致Excel表数量过多,难以分类维护
(1)没有配置表校验,配错值只能等业务运行时抛出Error
(2)热更新时,需要到所有服务器手动上传配置表json文件,然后再RPC发送到服务器执行配置表json文件的重新读取。
(3)策划需要多维护一份Excel配置表生成客户端、服务器的不同Json的配置文件

配置表功能的一些思考

接下来是在两个项目中,和不同类型的策划沟通,使用完全不同的编程语言和设计方式开发之中,自己进行的一部分思考。

所有的值一定要灵活配置吗?

在SLG游戏中,由于是一个Java项目,团队里开发过程中非常忌讳“魔法数字”,如果功能要求不会改变某个值,领导一般会要求大家在枚举类或者常量中新增这个功能专属的常量。这本身没有什么问题。但是有时候问题出在策划身上,策划设计的时候并不清楚某个值是否有改变的可能。为了规避这种可能,我们开发时几乎将所有的可变的值,都做成了配置表中读取全局变量。

这会带来一个问题:是否有些值是不存在改变的可能性的,或者说,改变可能很小? 比如,有一个活动是领取2个宝箱,而且UI设计的时候已经设计好了两个宝箱在游戏屏幕中摆放的位置,那么如果这个值交给策划配置,策划也不敢去修改这个值。那么这个值就属于“没有必要放权给策划配置的值”。

你可能会说,如果下次开活动,策划要配置3个宝箱,那还是要改代码,不是吗?没错,但是此时客户端,美术UI都要修改,到时候QA还是要统一测,这时候服务器一起跟进修改一个写死的魔法数字,对整条链路上的人都没有影响:策划还是要增加第三个宝箱的奖励配置、美术UI要设计三个宝箱在屏幕中的比例,QA还是要完整跑一遍活动。因此就算这个宝箱数量的值交给策划配置,也不会让这一切有什么优化,而更有可能的是,由于没有服务器开发的参与,策划配错了值,回过头来还是服务器这边查问题。

配置表的key的约定

我们还是以刚才的例子,一个活动可以配置多个宝箱,并且同时可以开启多个活动。有两个活动,id为1和2,每个活动会开3个宝箱,id为1,2和3。

在SLG项目中,它的Excel配置表如下:

活动id宝箱id宝箱奖励
allmainkeyallchildkeyall
11xxx
12xxx
13xxx
21xxx
22xxx
23xxx

在这里,allmainkey是指这张表的主key是活动id,allchildkey是指这张表的副key是宝箱id。并且必须要按照这种格式配置,才能导出正确的Csv文件,执行后续的操作。

如果一张表需要三个key,则在目前的条件下,由于没有类似allthirdkey的约定,所以第三个key无法被标识。

在MMO项目中,它的Excel配置如下

id活动id宝箱id宝箱奖励
10111xxx
10212xxx
10313xxx
20121xxx
20222xxx
20323xxx

这张表在功能逻辑上,只有一个key: id。这个id的生成逻辑是: 活动id * 100 + 宝箱id。如此依赖,只要活动和宝箱没有重复,则id也不会有重复。并且也解决了上面的那个问题:如果需要三个key,则只需要往后加新的逻辑id列,然后对id的生成规则进行修改即可。

在SLG项目中,表的结构固定了只能拥有两个key,一主一从,导致大家不得不按照固定的写法去进行配置。但在MMO项目中,它更注重于逻辑上的设计,使用体验也更加灵活。

配置表需要注释吗?

先说结论:配置表需要注释,但是不应该写在配置表里。

在策划的工作过程中,策划不断修改配置表的值,以达到自己的设计目的。在此过程中,存在多次与程序商讨修改表结构/字段含义的过程。由于配置表并不直接完成功能,所以策划很难对配置值的使用方式有清楚的认知。在需求被修改、程序开发时提议修改优化、或是最后测试时遇到问题时,我们可能会急于完成功能而忽略策划表另一页的注释页,导致注释与实际效果不符合的情况。

我们终究是从代码层面来排查配置表的值是如何被使用的,因此我认为代码中的注释总体优于策划表的注释。

配置表究竟由程序员设计,还是由策划设计?

在大部分情况下,由策划设计。原因是功能设计者在设计的时候,对自己设计过程中的敏感数值有深刻的印象。因此配置表应该根据这些数值进行规划和设计。

但在部分情况下,如在之前的功能上进行所谓的优化,策划无法对旧有功能有全面认知的时候,容易出现新增重复功能的表、或是直接“复用”、“拓展”之前的表。有时这会让程序员感到莫名其妙。因此在这种情况下,程序员需要仔细倾听策划的需求,然后对旧有功能进行整理,商讨出新的解决方案。在这个过程中,可能是程序员进行策划表的设计。

总结

配置表是游戏开发中不可或缺的一部分,但是目前还找不到比Excel更方便的配置表管理编辑工具。策划可以用Excel做很多方便的计算,但是到了程序员这里,读取Excel之后的步骤,每一步都值得探讨。读取如何更高效、流程如何更健壮,更不容易出现人为的失误、热更如何更方便快捷、结构如何更简单易懂,易于维护。

我的经验如下:

  1. 从Excel到内存中,最好只有一个中间产物。在SLG项目中,由于存在Excel转CsvCsv读取后转二进制文件这两个步骤,导致每一次读取出错,都要排查Excel和Csv两个文件。、
  2. 热更新的功能,最好只有两个步骤:打包和上传。打包是指将Excel转成最后服务器可读取的文件,上传是指将生成的文件传入Zookeeper或者其他的配置中心,由配置中心通知服务器进行热更新操作。而不是由人工上传,人工执行热更新。
  3. 一张表只能有一个key,但是可以有多个逻辑上的id,这样可以保持一定的多key自由度,且可以通过唯一key找到某一行记录,方便客户端-服务器之间通信。
  4. 配置表不需要注释,只需要用简短清晰的词汇,对某一列的功能和使用场景表述清楚,但是程序代码中要对使用配置表的具体位置进行一些描述(当某些字段有特殊枚举含义/需要除以100转成小数之类)。、
  5. 配置表优先让策划进行设计,只有设计配置表的人,才知道功能的具体效果。(这个过程并不是要求一定要让策划把完整的策划表设计完,而是要求策划要尽可能全面的列出功能中所需要配置的字段。)如此一来,程序员才能对功能开发更有把握,可以从需要的字段中窥探策划的真实设计方向。